pg.ddx.io pgsql-hackers@postgresql.org mailing list archive
help / color / mirror / Atom feedDeclarative partitioning
161+ messages / 27 participants
[nested] [flat]
* Declarative partitioning
@ 2015-08-18 10:30 Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
0 siblings, 10 replies; 161+ messages in thread
From: Amit Langote @ 2015-08-18 10:30 UTC (permalink / raw)
To: pgsql-hackers
Hi,
I would like propose $SUBJECT for this development cycle. Attached is a
WIP patch that implements most if not all of what's described below. Some
yet unaddressed parts are mentioned below, too. I'll add this to the CF-SEP.
Syntax
======
1. Creating a partitioned table
CREATE TABLE table_name
PARTITION BY {RANGE|LIST}
ON (column_list);
Where column_list consists of simple column names or expressions:
PARTITION BY LIST ON (name)
PARTITION BY RANGE ON (year, month)
PARTITION BY LIST ON ((lower(left(name, 2)))
PARTITION BY RANGE ON ((extract(year from d)), (extract(month from d)))
Note: LIST partition key supports only one column.
For each column, you could write operator class name:
PARTITION BY LIST/RANGE ON (colname [USING] opclass_name),
If not specified, the default btree operator class based on type of each
key column is used. If none of the available btree operator classes are
compatible with the partitioning strategy (list/range), error is thrown.
Built-in btree operator classes cover a good number of types for list and
range partitioning in practical scenarios.
A table created using this form is of proposed new relkind
RELKIND_PARTITIONED_REL. An entry in pg_partitioned_rel (see below) is
created to store partition key info.
Note: A table cannot be partitioned after-the-fact using ALTER TABLE.
Normal dependencies are created between the partitioned table and operator
classes, object in partition expressions like functions.
2. Creating a partition of a partitioned table
CREATE TABLE table_name
PARTITION OF partitioned_table_name
FOR VALUES values_spec;
Where values_spec is:
listvalues: [IN] (val1, ...)
rangevalues: START (col1min, ... ) END (col1max, ... )
| START (col1min, ... )
| END (col1max, ... )
A table created using this form has proposed pg_class.relispartition set
to true. An entry in pg_partition (see below) is created to store the
partition bound info.
The values_spec should match the partitioning strategy of the partitioned
table. In case of a range partition, the values in START and/or END should
match columns in the partition key.
Defining a list partition is fairly straightforward - just spell out the
list of comma-separated values. Error is thrown if the list of values
overlaps with one of the existing partitions' list.
CREATE TABLE persons_by_state (name text, state text)
PARTITION BY LIST ON (state);
CREATE TABLE persons_IL
PARTITION OF persons_by_state
FOR VALUES IN ('IL');
CREATE TABLE persons_fail
PARTITION OF persons_by_state
FOR VALUES IN ('IL');
ERROR: cannot create partition that overlaps with an existing one
For a range partition, there are more than one way:
Specify both START and END bounds: resulting range should not overlap with
the range(s) covered by existing partitions. Error is thrown otherwise.
Although rare in practice, gaps between ranges are OK.
CREATE TABLE measurement(logdate date NOT NULL)
PARTITION BY RANGE ON (logdate);
CREATE TABLE measurement_y2006m02
PARTITION OF measurement
FOR VALUES START ('2006-02-01') END ('2006-03-01'); --success
CREATE TABLE measurement_fail
PARTITION OF measurement
FOR VALUES START ('2006-02-15') END ('2006-03-01');
ERROR: cannot create partition that overlaps with an existing one
Specify only the START bound: add the partition on the left of some range
covered by existing partitions provided no overlap occurs (also
considering gaps between ranges, if any). If no such range exists, the new
partition will cover the range [START, +INFINITY) and become the rightmost
partition. Error is thrown if the specified START causes overlap.
CREATE TABLE measurement_y2006m01
PARTITION OF measurement
FOR VALUES START ('2006-01-01'); --success
CREATE TABLE measurement_fail
PARTITION OF measurement
FOR VALUES START ('2006-02-01'); --overlaps with measurement_y2006m02
ERROR: cannot create partition that overlaps with an existing one
Specify only the END bound: add the partition on the right of some range
covered by existing partitions provided no overlap occurs (also
considering gaps between ranges, if any). If no such range exists, the new
partition would cover the range (-INFINITY, END) and become the leftmost
partition. Error is thrown if the specified END causes overlap.
CREATE TABLE measurement_y2006m03
PARTITION OF measurement
FOR VALUES END ('2006-04-01'); --success
CREATE TABLE measurement_fail
PARTITION OF measurement
FOR VALUES END ('2006-03-01'); --overlaps with measurement_y2006m02
ERROR: cannot create partition that overlaps with an existing one
For each partition, START and END bound values are stored in the
catalog. Note that the lower bound is inclusive, whereas the upper bound
is exclusive.
Note: At most one range partition can have null min bound in which case it
covers the range (-INFINITY, END). Also, at most one range partition can
have null max bound in which case it covers the range [START, +INFINITY).
A normal dependency is created between the parent and the partition.
3. Multi-level partitioning
CREATE TABLE table_name
PARTITION OF partitioned_table_name
FOR VALUES values_spec
PARTITION BY {RANGE|LIST} ON (columns_list)
This variant implements a form of so called composite or sub-partitioning
with arbitrarily deep partitioning structure. A table created using this
form has both the relkind RELKIND_PARTITIONED_REL and
pg_class.relispartition set to true.
4. (yet unimplemented) Attach partition (from existing table)
ALTER TABLE partitioned_table
ATTACH PARTITION partition_name
FOR VALUES values_spec
USING [TABLE] table_name;
ALTER TABLE table_name
SET VALID PARTITION OF <parent>;
The first of the above pair of commands would attach table_name as a (yet)
'invalid' partition of partitioned_table (after confirming that it matches
the schema and does not overlap with other partitions per FOR VALUES
spec). It would also record the FOR VALUES part in the partition catalog
and set pg_class.relispartition to true for table_name.
After the first command is done, the second command would take exclusive
lock on table_name, scan the table to check if it contains any values
outside the boundaries defined by FOR VALUES clause defined previously,
throw error if so, mark as valid partition of parent if not.
Does that make sense?
5. Detach partition
ALTER TABLE partitioned_table
DETACH PARTITION partition_name [USING table_name]
This removes partition_name as partition of partitioned_table. The table
continues to exist with the same name or 'table_name', if specified.
pg_class.relispartition is set to false for the table, so it behaves like
a normal table.
System catalogs
===============
1. pg_partitioned_rel
CATALOG(pg_partitioned_rel,oid) BKI_WITHOUT_OIDS
{
Oid partrelid; /* partitioned table pg_class.oid */
char partstrategy; /* partitioning strategy 'l'/'r' */
int16 partnatts; /* number of partition columns */
int2vector partkey; /* column numbers of partition columns;
* 0 where specified column is an
* expresion */
oidvector partclass; /* operator class to compare keys */
pg_node_tree partexprs; /* expression trees for partition key
* members that are not simple column
* references; one for each zero entry
* in partkey[] */
};
2. pg_partition (omits partisvalid alluded to above)
CATALOG(pg_partition,oid) BKI_WITHOUT_OIDS
{
Oid partitionid; /* partition oid */
Oid partparent; /* parent oid */
anyarray partlistvalues; /* list of allowed values of the only
* partition column */
anyarray partrangebounds; /* list of bounds of ranges of
* allowed values per partition key
* column */
};
Further notes
=============
There are a number of restrictions on performing after-the-fact changes
using ALTER TABLE to partitions (ie, relispartition=true):
* Cannot add/drop column
* Cannot set/drop OIDs
* Cannot set/drop NOT NULL
* Cannot set/drop default
* Cannot alter column type
* Cannot add/drop alter constraint (table level)
* Cannot change persistence
* Cannot change inheritance
* Cannot link to a composite type
Such changes should be made to the topmost parent in the partitioning
hierarchy (hereafter referred to as just parent). These are recursively
applied to all the tables in the hierarchy. Although the last two items
cannot be performed on parent either.
Dropping a partition using DROP TABLE is not allowed. It needs to detached
using ALTER TABLE on parent before it can be dropped as a normal table.
Triggers on partitions are not allowed. They should be defined on the
parent. That said, I could not figure out a way to implement row-level
AFTER triggers on partitioned tables (more about that in a moment); so
they are currently not allowed:
CREATE TRIGGER audit_trig
AFTER INSERT ON persons
FOR EACH ROW EXECUTE PROCEDURE audit_func();
ERROR: Row-level AFTER triggers are not supported on partitioned tables
Column/table constraints on partitions are not allowed. They should be
defined on the parent. Foreign key constraints are not allowed due to
above limitation (no row-level after triggers).
A partitioning parent (RELKIND_PARTITIONED_REL) does not have storage.
Creating index on parent is not allowed. They should be defined on (leaf)
partitions. Because of this limitation, primary keys are not allowed on a
partitioned table. Perhaps, we should be able to just create a dummy
entry somewhere to represent an index on parent (which every partition
then copies.) Then by restricting primary key to contain all partition key
columns, we can implement unique constraint over the whole partitioned
table. That will in turn allow us to use partitioned tables as PK rels in
a foreign key constraint provided row-level AFTER trigger issue is resolved.
VACUUM/ANALYZE on individual partitions should work like normal tables.
I've not implemented something like inheritance tree sampling for
partitioning tree in this patch. Autovacuum has been taught to ignore
parent tables and vacuum/analyze partitions normally.
Dropping a partitioned table should (?) unconditionally drop all its
partitions probably but, currently the patch uses dependencies, so
requires to specify CASCADE to do the same.
What should TRUNCATE on partitioned table do?
Ownership, privileges/permissions, RLS should be managed through the
parent table although not comprehensively addressed in the patch.
There is no need to define tuple routing triggers. CopyFrom() and
ExecInsert() determine target partition just before performing
heap_insert() and ExecInsertIndexTuples(). IOW, any BR triggers and
constraints (on parent) are executed for tuple before being routed to a
partition. If no partition can be found, it's an error.
Because row-level AFTER triggers need to save ItemPointers in trigger
event data and defining triggers on partitions (which is where tuples
really go) is not allowed, I could not find a straightforward way to
implement them. So, perhaps we should allow (only) row-level AFTER
triggers on partitions or think of modifying trigger.c to know about this
twist explicitly.
Internal representations
========================
For a RELKIND_PARTITIONED_REL relations, RelationData gets a few new
fields to store the partitioning metadata. That includes partition key
tuple (pg_partitioned_rel) including some derived info (opfamily,
opcintype, compare proc FmgrInfos, partition expression trees).
Additionally, it also includes a PartitionInfo object which includes
partition OIDs array, partition bound arrays (if range partitioned,
rangemax is sorted in ascending order and OIDs are likewise ordered). It
is built from information in pg_partition catalog.
While RelationBuildDesc() initializes the basic key info, fields like
expression trees, PartitionInfo are built on demand and cached. For
example, InitResultRelInfo() builds the latter to populate the newly added
ri_PartitionKeyInfo and ri_Partitions fields, respectively.
PartitionInfo object is rebuilt on every cache invalidation of the rel
which includes when adding/attaching/detaching a new partition.
Planner and executor considerations
=====================================
The patch does not yet implement any planner changes for partitioned
tables, although I'm working on the same and post updates as soon as
possible. That means, it is not possible to run SELECT/UPDATE/DELETE
queries on partitioned tables without getting:
postgres=# SELECT * FROM persons;
ERROR: could not open file "base/13244/106975": No such file or directory
Given that there would be more direct ways of performing partition pruning
decisions with the proposed, it would be nice to utilize them.
Specifically, I would like to avoid having to rely on constraint exclusion
for partition pruning whereby subquery_planner() builds append_rel_list
and the later steps exclude useless partitions.
By extending RelOptInfo to include partitioning info for partitioned rels,
it might be possible to perform partition pruning directly without
previously having to expand them. Although, as things stand now, it's not
clear how that might work - when would partition RTEs be added to the
rtable? The rtable is assumed not to change after
setup_simple_rel_arrays() has done its job which is much earlier than when
it would be desirable for the partitioned table expansion (along with
partition pruning) to happen. Moreover, if that means we might not be able
to build RelOptInfo's for partitions, how to choose best paths for them
(index paths or not, etc.)?
I'm also hoping we don't require something like inheritance_planner() for
when partitioned tables are target rels. I assume considerations for why
the special processing is necessary for inheritance trees in that scenario
don't apply to partitioning trees. So, if grouping_planner() returns a
Append plan (among other options) for the partitioning tree, tacking a
ModifyTable node on top should do the trick?
Suggestions greatly welcome in this area.
Other items
===========
Will include the following once we start reaching consensus on main parts
of the proposed design/implementation:
* New regression tests
* Documentation updates
* pg_dump, psql, etc.
For reference, some immediately previous discussions:
* On partitioning *
http://www.postgresql.org/message-id/20140829155607.GF7705@eldon.alvh.no-ip.org
* Partitioning WIP patch *
http://www.postgresql.org/message-id/54EC32B6.9070605@lab.ntt.co.jp
Comments welcome!
Thanks,
Amit
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Attachments:
[text/x-diff] declarative-partitioning-wip-1.patch (219.5K, ../../55D3093C.5010800@lab.ntt.co.jp/2-declarative-partitioning-wip-1.patch)
download | inline diff:
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 7479d40..6322ad3 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -900,6 +900,7 @@ extractRelOptions(HeapTuple tuple, TupleDesc tupdesc, Oid amoptions)
case RELKIND_RELATION:
case RELKIND_TOASTVALUE:
case RELKIND_MATVIEW:
+ case RELKIND_PARTITIONED_REL:
options = heap_reloptions(classForm->relkind, datum, false);
break;
case RELKIND_VIEW:
@@ -1350,6 +1351,7 @@ heap_reloptions(char relkind, Datum reloptions, bool validate)
return (bytea *) rdopts;
case RELKIND_RELATION:
case RELKIND_MATVIEW:
+ case RELKIND_PARTITIONED_REL:
return default_reloptions(reloptions, validate, RELOPT_KIND_HEAP);
default:
/* other relkinds are not supported */
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index 25130ec..1d3db26 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -14,7 +14,7 @@ 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 \
pg_constraint.o pg_conversion.o \
pg_depend.o pg_enum.o pg_inherits.o pg_largeobject.o pg_namespace.o \
- pg_operator.o pg_proc.o pg_range.o pg_db_role_setting.o pg_shdepend.o \
+ pg_operator.o pg_partition.o pg_proc.o pg_range.o pg_db_role_setting.o pg_shdepend.o \
pg_type.o storage.o toasting.o
BKIFILES = postgres.bki postgres.description postgres.shdescription
@@ -41,8 +41,8 @@ POSTGRES_BKI_SRCS = $(addprefix $(top_srcdir)/src/include/catalog/,\
pg_foreign_data_wrapper.h pg_foreign_server.h pg_user_mapping.h \
pg_foreign_table.h pg_policy.h pg_replication_origin.h \
pg_default_acl.h pg_seclabel.h pg_shseclabel.h \
- pg_collation.h pg_range.h pg_transform.h \
- toasting.h indexing.h \
+ pg_collation.h pg_range.h pg_transform.h toasting.h indexing.h \
+ pg_partitioned_rel.h pg_partition.h\
)
# location of Catalog.pm
diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
index 50a00cf..062dad3 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -751,6 +751,8 @@ objectsInSchemaToOids(GrantObjectType objtype, List *nspnames)
case ACL_OBJECT_RELATION:
objs = getRelationsInNamespace(namespaceId, RELKIND_RELATION);
objects = list_concat(objects, objs);
+ objs = getRelationsInNamespace(namespaceId, RELKIND_PARTITIONED_REL);
+ objects = list_concat(objects, objs);
objs = getRelationsInNamespace(namespaceId, RELKIND_VIEW);
objects = list_concat(objects, objs);
objs = getRelationsInNamespace(namespaceId, RELKIND_MATVIEW);
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index d04e94d..98f9251 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -47,6 +47,9 @@
#include "catalog/pg_foreign_table.h"
#include "catalog/pg_inherits.h"
#include "catalog/pg_namespace.h"
+#include "catalog/pg_opclass.h"
+#include "catalog/pg_partition.h"
+#include "catalog/pg_partitioned_rel.h"
#include "catalog/pg_statistic.h"
#include "catalog/pg_tablespace.h"
#include "catalog/pg_type.h"
@@ -55,8 +58,11 @@
#include "catalog/storage_xlog.h"
#include "commands/tablecmds.h"
#include "commands/typecmds.h"
+#include "executor/executor.h"
#include "miscadmin.h"
+#include "nodes/execnodes.h"
#include "nodes/nodeFuncs.h"
+#include "nodes/parsenodes.h"
#include "optimizer/var.h"
#include "parser/parse_coerce.h"
#include "parser/parse_collate.h"
@@ -69,7 +75,9 @@
#include "utils/fmgroids.h"
#include "utils/inval.h"
#include "utils/lsyscache.h"
+#include "utils/partition.h"
#include "utils/rel.h"
+#include "utils/relcache.h"
#include "utils/ruleutils.h"
#include "utils/snapmgr.h"
#include "utils/syscache.h"
@@ -294,6 +302,14 @@ heap_create(const char *relname,
*/
reltablespace = InvalidOid;
break;
+ case RELKIND_PARTITIONED_REL:
+ /*
+ * Separated from the above because a partitioned table still has
+ * partitions with storage and will need to be in some tablespace,
+ * though theirs can override the parent's.
+ */
+ create_storage = false;
+ break;
case RELKIND_SEQUENCE:
create_storage = true;
@@ -803,6 +819,7 @@ InsertPgClassTuple(Relation pg_class_desc,
values[Anum_pg_class_relhastriggers - 1] = BoolGetDatum(rd_rel->relhastriggers);
values[Anum_pg_class_relrowsecurity - 1] = BoolGetDatum(rd_rel->relrowsecurity);
values[Anum_pg_class_relhassubclass - 1] = BoolGetDatum(rd_rel->relhassubclass);
+ values[Anum_pg_class_relispartition - 1] = BoolGetDatum(rd_rel->relispartition);
values[Anum_pg_class_relispopulated - 1] = BoolGetDatum(rd_rel->relispopulated);
values[Anum_pg_class_relreplident - 1] = CharGetDatum(rd_rel->relreplident);
values[Anum_pg_class_relfrozenxid - 1] = TransactionIdGetDatum(rd_rel->relfrozenxid);
@@ -861,6 +878,7 @@ AddNewRelationTuple(Relation pg_class_desc,
switch (relkind)
{
case RELKIND_RELATION:
+ case RELKIND_PARTITIONED_REL:
case RELKIND_MATVIEW:
case RELKIND_INDEX:
case RELKIND_TOASTVALUE:
@@ -885,6 +903,7 @@ AddNewRelationTuple(Relation pg_class_desc,
/* Initialize relfrozenxid and relminmxid */
if (relkind == RELKIND_RELATION ||
+ relkind == RELKIND_PARTITIONED_REL ||
relkind == RELKIND_MATVIEW ||
relkind == RELKIND_TOASTVALUE)
{
@@ -1099,9 +1118,10 @@ heap_create_with_catalog(const char *relname,
{
/* Use binary-upgrade override for pg_class.oid/relfilenode? */
if (IsBinaryUpgrade &&
- (relkind == RELKIND_RELATION || relkind == RELKIND_SEQUENCE ||
- relkind == RELKIND_VIEW || relkind == RELKIND_MATVIEW ||
- relkind == RELKIND_COMPOSITE_TYPE || relkind == RELKIND_FOREIGN_TABLE))
+ (relkind == RELKIND_RELATION || relkind == RELKIND_PARTITIONED_REL ||
+ relkind == RELKIND_SEQUENCE || relkind == RELKIND_VIEW ||
+ relkind == RELKIND_MATVIEW || relkind == RELKIND_COMPOSITE_TYPE ||
+ relkind == RELKIND_FOREIGN_TABLE))
{
if (!OidIsValid(binary_upgrade_next_heap_pg_class_oid))
ereport(ERROR,
@@ -1132,6 +1152,7 @@ heap_create_with_catalog(const char *relname,
switch (relkind)
{
case RELKIND_RELATION:
+ case RELKIND_PARTITIONED_REL:
case RELKIND_VIEW:
case RELKIND_MATVIEW:
case RELKIND_FOREIGN_TABLE:
@@ -1176,6 +1197,7 @@ heap_create_with_catalog(const char *relname,
* such is an implementation detail: toast tables, sequences and indexes.
*/
if (IsUnderPostmaster && (relkind == RELKIND_RELATION ||
+ relkind == RELKIND_PARTITIONED_REL ||
relkind == RELKIND_VIEW ||
relkind == RELKIND_MATVIEW ||
relkind == RELKIND_FOREIGN_TABLE ||
@@ -1348,7 +1370,11 @@ heap_create_with_catalog(const char *relname,
if (oncommit != ONCOMMIT_NOOP)
register_on_commit_action(relid, oncommit);
- if (relpersistence == RELPERSISTENCE_UNLOGGED)
+ /*
+ * Do not bother creating the init fork for a partitioned relation.
+ */
+ if (relpersistence == RELPERSISTENCE_UNLOGGED
+ && relkind != RELKIND_PARTITIONED_REL)
{
Assert(relkind == RELKIND_RELATION || relkind == RELKIND_MATVIEW ||
relkind == RELKIND_TOASTVALUE);
@@ -1798,11 +1824,29 @@ heap_drop_with_catalog(Oid relid)
}
/*
+ * Delete the pg_partitioned_rel tuple first.
+ */
+ if (rel->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+ RemovePartitionKeyByRelId(relid);
+
+ /*
+ * The pg_partition entry is otherwise removed only when the partition
+ * is detached from its parent table. There is no dependency subsystem-
+ * based tracking of such an entry. Usually, a user command reaching
+ * this would be a user dropping the whole partitioned table which in
+ * turn would cascade to its partitions which take with them their
+ * pg_partition entries, which is what the following is.
+ */
+ if (rel->rd_rel->relispartition)
+ RemovePartitionBoundByRelId(relid);
+
+ /*
* Schedule unlinking of the relation's physical files at commit.
*/
if (rel->rd_rel->relkind != RELKIND_VIEW &&
rel->rd_rel->relkind != RELKIND_COMPOSITE_TYPE &&
- rel->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
+ rel->rd_rel->relkind != RELKIND_FOREIGN_TABLE &&
+ rel->rd_rel->relkind != RELKIND_PARTITIONED_REL)
{
RelationDropStorage(rel);
}
@@ -2990,3 +3034,409 @@ insert_ordered_unique_oid(List *list, Oid datum)
lappend_cell_oid(list, prev, datum);
return list;
}
+
+/*
+ * StorePartitionKey
+ *
+ * Store the partition key of relation rel into system catalog
+ * pg_partitioned_rel.
+ */
+void
+StorePartitionKey(Relation rel, PartitionKeyInfo *pkinfo, Oid *partOpClassOids,
+ char strategy)
+{
+ int i;
+ int2vector *partkey;
+ oidvector *partclass;
+ Datum values[Natts_pg_partitioned_rel];
+ bool nulls[Natts_pg_partitioned_rel];
+ Datum partexprsDatum;
+ Relation pg_partitioned_rel;
+ HeapTuple tuple;
+ ObjectAddress myself;
+ ObjectAddress referenced;
+
+ /* Cannot happen but might as well. */
+ tuple = SearchSysCache(PARTITIONEDRELID,
+ ObjectIdGetDatum(RelationGetRelid(rel)), 0, 0, 0);
+ if (HeapTupleIsValid(tuple))
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("multiple partition keys for table \"%s\" are not allowed",
+ RelationGetRelationName(rel))));
+ ReleaseSysCache(tuple);
+ }
+
+ /*
+ * Copy the partition key, opclass info into arrays (should we
+ * make the caller pass them like this to start with?)
+ */
+ partkey = buildint2vector(pkinfo->pi_KeyAttrNumbers, pkinfo->pi_NumKeyAttrs);
+ partclass = buildoidvector(partOpClassOids, pkinfo->pi_NumKeyAttrs);
+
+ /* Convert the partition key expressions (if any) to a text datum. */
+ if (pkinfo->pi_Expressions)
+ {
+ char *exprsString;
+
+ exprsString = nodeToString(pkinfo->pi_Expressions);
+ partexprsDatum = CStringGetTextDatum(exprsString);
+ pfree(exprsString);
+ }
+ else
+ partexprsDatum = (Datum) 0;
+
+ pg_partitioned_rel = heap_open(PartitionedRelRelationId, RowExclusiveLock);
+
+ /* Build a pg_partitioned_rel tuple. */
+ MemSet(nulls, false, sizeof(nulls));
+
+ /* Only this can be NULL. */
+ if (!partexprsDatum)
+ nulls[Anum_pg_partitioned_rel_partexprs - 1] = true;
+
+ values[Anum_pg_partitioned_rel_partrelid - 1] = ObjectIdGetDatum(RelationGetRelid(rel));
+ values[Anum_pg_partitioned_rel_partstrategy - 1] = CharGetDatum(strategy);
+ values[Anum_pg_partitioned_rel_partnatts - 1] = Int16GetDatum(pkinfo->pi_NumKeyAttrs);
+ values[Anum_pg_partitioned_rel_partkey - 1] = PointerGetDatum(partkey);
+ values[Anum_pg_partitioned_rel_partclass - 1] = PointerGetDatum(partclass);
+ values[Anum_pg_partitioned_rel_partexprs - 1] = partexprsDatum;
+
+ tuple = heap_form_tuple(RelationGetDescr(pg_partitioned_rel), values, nulls);
+
+ simple_heap_insert(pg_partitioned_rel, tuple);
+
+ /* Update the indexes on pg_partitioned_rel. */
+ CatalogUpdateIndexes(pg_partitioned_rel, tuple);
+
+ /*
+ * Store dependencies.
+ *
+ * Make this relation dependent on a few things:
+ */
+ myself.classId = RelationRelationId;
+ myself.objectId = RelationGetRelid(rel);;
+ myself.objectSubId = 0;
+
+ /*
+ * 1) Operator class per key column.
+ */
+ for (i = 0; i < pkinfo->pi_NumKeyAttrs; i++)
+ {
+ referenced.classId = OperatorClassRelationId;
+ referenced.objectId = partOpClassOids[i];
+ referenced.objectSubId = 0;
+
+ recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+ }
+
+ /*
+ * 2) Things inside key expressions.
+ *
+ * XXX - an ugliness: normal dependencies also created on attribute
+ * references.
+ */
+ if (pkinfo->pi_Expressions)
+ {
+ recordDependencyOnSingleRelExpr(&myself,
+ (Node *) pkinfo->pi_Expressions,
+ RelationGetRelid(rel),
+ DEPENDENCY_NORMAL,
+ DEPENDENCY_NORMAL);
+ }
+
+ /* Tell the world about the key. */
+ CacheInvalidateRelcache(rel);
+
+ /* Close the catalog relation and free the tuple. */
+ heap_close(pg_partitioned_rel, RowExclusiveLock);
+ heap_freetuple(tuple);
+}
+
+/*
+ * RemovePartitionKeyByRelId
+ *
+ * Remove a pg_partitioned_rel entry.
+ */
+void
+RemovePartitionKeyByRelId(Oid relid)
+{
+ Relation pkeyrel;
+ HeapTuple tuple;
+
+ pkeyrel = heap_open(PartitionedRelRelationId, RowExclusiveLock);
+
+ tuple = SearchSysCache1(PARTITIONEDRELID, ObjectIdGetDatum(relid));
+
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "cache lookup failed for partition key of relation %u", relid);
+
+ simple_heap_delete(pkeyrel, &tuple->t_self);
+
+ ReleaseSysCache(tuple);
+ heap_close(pkeyrel, RowExclusiveLock);
+}
+
+/* ----------------
+ * BuildPartitionKeyInfo
+ * Construct a PartitionKeyInfo record for an open relation
+ *
+ * PartitionKeyInfo stores the information about the partition key that's
+ * needed when inserting tuples into a partitioned relations; especially
+ * partition expression state. Normally we build a PartitionKeyInfo for a
+ * partitioned relation just once per command, and then use it for
+ * (potentially) many tuples.
+ * ----------------
+ */
+PartitionKeyInfo *
+BuildPartitionKeyInfo(Relation rel)
+{
+ int i;
+ int numKeys;
+ Form_pg_partitioned_rel pkeyStruct = rel->rd_pkey;
+ PartitionKeyInfo *pi = makeNode(PartitionKeyInfo);
+
+ /*
+ * check the number of keys, and copy attr numbers into the result
+ */
+ numKeys = pkeyStruct->partnatts;
+ if (numKeys < 1 || numKeys > PARTITION_MAX_KEYS)
+ elog(ERROR, "invalid partnatts %d for partitioned relation %u",
+ numKeys, RelationGetRelid(rel));
+
+ pi->pi_NumKeyAttrs = numKeys;
+ for (i = 0; i < numKeys; i++)
+ pi->pi_KeyAttrNumbers[i] = pkeyStruct->partkey.values[i];
+
+ /* fetch any expressions needed for expressional partition attributes */
+ pi->pi_Expressions = RelationGetPartitionExpressions(rel);
+ pi->pi_ExpressionsState = NIL;
+
+ return pi;
+}
+
+/*
+ * StorePartitionBounds
+ *
+ * Store partition bounds of relation rel into system catalog
+ * pg_partition
+ */
+void
+StorePartitionBounds(Relation childrel,
+ Relation parentrel,
+ int listnvalues,
+ Datum *datum)
+{
+ Relation pg_partition;
+ HeapTuple tuple;
+ ArrayType *listvalues = NULL;
+ ArrayType *rangebounds = NULL;
+ char partstrategy;
+ int partnatts;
+
+ Datum values[Natts_pg_partition];
+ bool nulls[Natts_pg_partition];
+
+ PartitionKeyTypeInfo *typinfo = get_key_type_info(parentrel);
+
+ partstrategy = parentrel->rd_pkey->partstrategy;
+ partnatts = parentrel->rd_pkey->partnatts;
+
+ /* Enforced by DefineRelation(). */
+ Assert(partstrategy != PARTITION_STRAT_LIST || partnatts == 1);
+
+ /* Cannot happen but might as well. */
+ tuple = SearchSysCache(PARTITIONID,
+ ObjectIdGetDatum(RelationGetRelid(childrel)),
+ 0, 0, 0);
+
+ if (HeapTupleIsValid(tuple))
+ {
+ ereport(ERROR, (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("redefining partition bounds for relation \"%s\" is not allowed",
+ RelationGetRelationName(childrel))));
+ ReleaseSysCache(tuple);
+ }
+
+ pg_partition = heap_open(PartitionRelationId, RowExclusiveLock);
+
+ /* Build a pg_partition tuple. */
+ MemSet(nulls, false, sizeof(nulls));
+
+ switch (partstrategy)
+ {
+ case PARTITION_STRAT_LIST:
+ /* A simple 1-D array of values for the only partition column. */
+ listvalues = construct_array(datum,
+ listnvalues,
+ typinfo->typid[0],
+ typinfo->typlen[0],
+ typinfo->typbyval[0],
+ typinfo->typalign[0]);
+ /* No range bounds. */
+ nulls[Anum_pg_partition_partrangebounds - 1] = true;
+ break;
+
+ case PARTITION_STRAT_RANGE:
+ /* A 2-D array of range bounds per partition column. */
+ rangebounds = construct_array(datum,
+ partnatts,
+ ANYARRAYOID,
+ -1,
+ false,
+ 'd');
+
+ /* No list values. */
+ nulls[Anum_pg_partition_partlistvalues - 1] = true;
+ break;
+ }
+
+ values[Anum_pg_partition_partitionid - 1] = ObjectIdGetDatum(RelationGetRelid(childrel));
+ values[Anum_pg_partition_partparent - 1] = ObjectIdGetDatum(RelationGetRelid(parentrel));
+ values[Anum_pg_partition_partlistvalues - 1] = PointerGetDatum(listvalues);
+ values[Anum_pg_partition_partrangebounds - 1] = PointerGetDatum(rangebounds);
+
+ tuple = heap_form_tuple(RelationGetDescr(pg_partition), values, nulls);
+
+ simple_heap_insert(pg_partition, tuple);
+
+ /* Update the indexes on pg_partition. */
+ CatalogUpdateIndexes(pg_partition, tuple);
+
+ /* Close the relation and free the tuple. */
+ heap_close(pg_partition, RowExclusiveLock);
+ heap_freetuple(tuple);
+ pfree(typinfo);
+}
+
+/*
+ * RemovePartitionBoundByRelId
+ *
+ * Remove a pg_partition entry for a partition.
+ */
+void
+RemovePartitionBoundByRelId(Oid relid)
+{
+ Relation partrel;
+ HeapTuple tuple;
+
+ partrel = heap_open(PartitionRelationId, RowExclusiveLock);
+ tuple = SearchSysCache1(PARTITIONID, ObjectIdGetDatum(relid));
+
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "cache lookup failed for partition %u", relid);
+
+ simple_heap_delete(partrel, &tuple->t_self);
+
+ ReleaseSysCache(tuple);
+ heap_close(partrel, RowExclusiveLock);
+}
+
+/*
+ * SetRelationIsPartition
+ *
+ * Update relation's pg_class.relispartition.
+ *
+ * Caller had better hold exclusive lock on the relation.
+ *
+ * An important side effect is that a SI update message will be sent out for
+ * the pg_class tuple, which will force other backends to rebuild their
+ * relcache entries for the rel. Also, this backend will rebuild its
+ * own relcache entry at the next CommandCounterIncrement.
+ */
+void
+SetRelationIsPartition(Relation rel, bool ispartition)
+{
+ Relation relrel;
+ HeapTuple reltup;
+ Form_pg_class relStruct;
+
+ relrel = heap_open(RelationRelationId, RowExclusiveLock);
+ reltup = SearchSysCacheCopy1(RELOID,
+ ObjectIdGetDatum(RelationGetRelid(rel)));
+ if (!HeapTupleIsValid(reltup))
+ elog(ERROR, "cache lookup failed for relation %u",
+ RelationGetRelid(rel));
+
+ relStruct = (Form_pg_class) GETSTRUCT(reltup);
+
+ if (relStruct->relispartition != ispartition)
+ {
+ relStruct->relispartition = ispartition;
+
+ simple_heap_update(relrel, &reltup->t_self, reltup);
+
+ /* keep catalog indexes current */
+ CatalogUpdateIndexes(relrel, reltup);
+ }
+ else
+ {
+ /* Skip the disk update, but force relcache inval anyway */
+ CacheInvalidateRelcache(rel);
+ }
+
+ heap_freetuple(reltup);
+ heap_close(relrel, RowExclusiveLock);
+}
+
+/*
+ * FormPartitionKeyDatum - Construct values[] and isnull[] arrays for
+ * partition key columns off tuple.
+ */
+void
+FormPartitionKeyDatum(PartitionKeyInfo *pkinfo,
+ TupleTableSlot *slot,
+ EState *estate,
+ Datum *values,
+ bool *isnull)
+{
+ ListCell *partexpr_item;
+ int i;
+
+ if (pkinfo->pi_Expressions != NIL &&
+ pkinfo->pi_ExpressionsState == NIL)
+ {
+ /* First time through, set up expression evaluation state */
+ pkinfo->pi_ExpressionsState = (List *)
+ ExecPrepareExpr((Expr *) pkinfo->pi_Expressions,
+ estate);
+ /* Check caller has set up context correctly */
+ Assert(GetPerTupleExprContext(estate)->ecxt_scantuple == slot);
+ }
+ partexpr_item = list_head(pkinfo->pi_ExpressionsState);
+
+ for (i = 0; i < pkinfo->pi_NumKeyAttrs; i++)
+ {
+ AttrNumber keycol = pkinfo->pi_KeyAttrNumbers[i];
+ Datum pkDatum;
+ bool isNull;
+
+ if (keycol != 0)
+ {
+ /*
+ * Plain column; get the value we need directly from the heap
+ * tuple.
+ */
+ pkDatum = slot_getattr(slot, keycol, &isNull);
+ }
+ else
+ {
+ /*
+ * Partition Key expression --- need to evaluate it.
+ */
+ if (partexpr_item == NULL)
+ elog(ERROR, "wrong number of partition key expressions");
+ pkDatum = ExecEvalExprSwitchContext((ExprState *) lfirst(partexpr_item),
+ GetPerTupleExprContext(estate),
+ &isNull,
+ NULL);
+ partexpr_item = lnext(partexpr_item);
+ }
+ values[i] = pkDatum;
+ isnull[i] = isNull;
+ }
+
+ if (partexpr_item != NULL)
+ elog(ERROR, "wrong number of partition key expressions");
+}
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index e59b163..787475f 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -3445,6 +3445,14 @@ reindex_relation(Oid relid, int flags, int options)
*/
rel = heap_open(relid, ShareLock);
+ /*
+ * XXX - Reindexing a partitioned table not supported yet
+ */
+ if (rel->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+ ereport(ERROR,
+ (errmsg("cannot reindex partitioned table \"%s\"", RelationGetRelationName(rel)),
+ errhint("Run REINDEX on individual partitions one-by-one.")));
+
toast_relid = rel->rd_rel->reltoastrelid;
/*
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index 052aab1..2a78818 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -1152,7 +1152,8 @@ get_relation_by_qualified_name(ObjectType objtype, List *objname,
RelationGetRelationName(relation))));
break;
case OBJECT_TABLE:
- if (relation->rd_rel->relkind != RELKIND_RELATION)
+ if (relation->rd_rel->relkind != RELKIND_RELATION &&
+ relation->rd_rel->relkind != RELKIND_PARTITIONED_REL)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not a table",
@@ -3181,7 +3182,15 @@ getRelationDescription(StringInfo buffer, Oid relid)
switch (relForm->relkind)
{
case RELKIND_RELATION:
- appendStringInfo(buffer, _("table %s"),
+ if (relForm->relispartition)
+ appendStringInfo(buffer, _("partition %s"),
+ relname, get_rel_name(get_partition_parent(relid, false)));
+ else
+ appendStringInfo(buffer, _("table %s"),
+ relname);
+ break;
+ case RELKIND_PARTITIONED_REL:
+ appendStringInfo(buffer, _("partitioned table %s"),
relname);
break;
case RELKIND_INDEX:
@@ -3636,6 +3645,9 @@ getRelationTypeDescription(StringInfo buffer, Oid relid, int32 objectSubId)
case RELKIND_RELATION:
appendStringInfoString(buffer, "table");
break;
+ case RELKIND_PARTITIONED_REL:
+ appendStringInfoString(buffer, "partitioned table");
+ break;
case RELKIND_INDEX:
appendStringInfoString(buffer, "index");
break;
diff --git a/src/backend/catalog/pg_partition.c b/src/backend/catalog/pg_partition.c
new file mode 100644
index 0000000..608299a
--- /dev/null
+++ b/src/backend/catalog/pg_partition.c
@@ -0,0 +1,425 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_partition.c
+ * Routines to support manipulation of the pg_partition relation.
+ *
+ * Note: currently, this module only contains inquiry functions; the actual
+ * creation and deletion of pg_partition entries is done in heap.c. Perhaps
+ * someday that code should be moved here.
+ *
+ * Portions Copyright (c) 1996-2015, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ * src/backend/catalog/pg_partition.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "access/genam.h"
+#include "access/heapam.h"
+#include "access/htup_details.h"
+#include "catalog/heap.h"
+#include "catalog/indexing.h"
+#include "catalog/pg_collation.h"
+#include "catalog/pg_partition.h"
+#include "catalog/pg_partition_fn.h"
+#include "catalog/pg_type.h"
+#include "nodes/nodeFuncs.h"
+#include "nodes/execnodes.h"
+#include "parser/parse_type.h"
+#include "storage/lmgr.h"
+#include "utils/array.h"
+#include "utils/fmgroids.h"
+#include "utils/syscache.h"
+#include "utils/lsyscache.h"
+#include "utils/partition.h"
+#include "utils/tqual.h"
+#include "utils/typcache.h"
+
+static int oid_cmp(const void *p1, const void *p2);
+
+/*
+ * get_partition_parent
+ *
+ * Returns OID of the parent of partid.
+ */
+Oid
+get_partition_parent(Oid relid, bool recurse)
+{
+ HeapTuple tuple;
+ Form_pg_partition form;
+
+ tuple = SearchSysCache1(PARTITIONID, ObjectIdGetDatum(relid));
+
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "cache lookup failed for partition entry of relation %u", relid);
+
+ form = (Form_pg_partition) GETSTRUCT(tuple);
+ Assert(form->partparent != InvalidOid);
+
+ if (recurse && is_partition(form->partparent))
+ return get_partition_parent(form->partparent, true);
+
+ ReleaseSysCache(tuple);
+ return form->partparent;
+}
+
+/*
+ * IsPartition - is rel a partition?
+ */
+bool
+IsPartition(Relation rel)
+{
+ return rel->rd_rel->relispartition;
+}
+
+/*
+ * is_partition - is this a partition?
+ *
+ * Checks if the relation with OID 'relid' is a partition based
+ * on its relispartition flag.
+ *
+ * Doesn't really look like this belongs here.
+ */
+bool
+is_partition(Oid relid)
+{
+ HeapTuple tuple;
+ bool ispartition;
+
+ tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relid));
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "cache lookup failed for relation %u", relid);
+
+ ispartition = ((Form_pg_class) GETSTRUCT(tuple))->relispartition;
+ ReleaseSysCache(tuple);
+
+ return ispartition;
+}
+
+/*
+ * IsPartitionedRel - is rel partitioned?
+ */
+bool
+IsPartitionedRel(Relation rel)
+{
+ return rel->rd_rel->relkind == RELKIND_PARTITIONED_REL ? true : false;
+}
+
+/*
+ * is_partitioned - is relation with OID relid partitioned?
+ *
+ * Doesn't really look like this belongs here.
+ */
+bool
+is_partitioned(Oid relid)
+{
+ HeapTuple tuple;
+ char relkind;
+
+ tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relid));
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "cache lookup failed for relation %u", relid);
+
+ relkind = ((Form_pg_class) GETSTRUCT(tuple))->relkind;
+ ReleaseSysCache(tuple);
+
+ return relkind == RELKIND_PARTITIONED_REL ? true : false;
+}
+
+/*
+ * find_partitions
+ *
+ * Returns a list of direct partitions of partitioned table with OID 'relid'.
+ * Found partitions are locked in an order that follows the sorted order of
+ * their OIDs.
+ */
+List *
+find_partitions(Oid relid, LOCKMODE lockmode)
+{
+ List *list = NIL;
+ Relation partitionRel;
+ SysScanDesc scan;
+ ScanKeyData skey[1];
+ HeapTuple tuple;
+ Oid partrelid;
+ Oid *oidarr;
+ int maxoids,
+ numoids,
+ i;
+
+ /* Fairly certain this holds if we got here at all. */
+ Assert(is_partitioned(relid));
+
+ /* Scan pg_partition and build a working array of partition OIDs. */
+ maxoids = 32;
+ oidarr = (Oid *) palloc(maxoids * sizeof(Oid));
+ numoids = 0;
+
+ partitionRel = heap_open(PartitionRelationId, AccessShareLock);
+ ScanKeyInit(&skey[0],
+ Anum_pg_partition_partparent,
+ BTEqualStrategyNumber, F_OIDEQ,
+ relid);
+
+ scan = systable_beginscan(partitionRel, PartitionParentIndexId, true,
+ NULL, 1, skey);
+
+ while ((tuple = systable_getnext(scan)) != NULL)
+ {
+ partrelid = ((Form_pg_partition) GETSTRUCT(tuple))->partitionid;
+ if (numoids >= maxoids)
+ {
+ maxoids *= 2;
+ oidarr = (Oid *) repalloc(oidarr, maxoids * sizeof(Oid));
+ }
+ oidarr[numoids++] = partrelid;
+ }
+
+ systable_endscan(scan);
+
+ heap_close(partitionRel, AccessShareLock);
+
+ /*
+ * If we found more than one partition, sort them by OID. This is important
+ * since we need to be sure all backends lock partitions in the same order
+ * to avoid needless deadlocks.
+ */
+ if (numoids > 1)
+ qsort(oidarr, numoids, sizeof(Oid), oid_cmp);
+
+ /* Acquire locks and build the result list. */
+ for (i = 0; i < numoids; i++)
+ {
+ partrelid = oidarr[i];
+
+ if (lockmode != NoLock)
+ {
+ /* Get the lock to synchronize against concurrent drop */
+ LockRelationOid(partrelid, lockmode);
+
+ /*
+ * Now that we have the lock, double-check to see if the relation
+ * really exists or not. If not, assume it was dropped while we
+ * waited to acquire lock, and ignore it.
+ */
+ if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(partrelid)))
+ {
+ /* Release useless lock */
+ UnlockRelationOid(partrelid, lockmode);
+ /* And ignore this relation */
+ continue;
+ }
+ }
+
+ list = lappend_oid(list, partrelid);
+ }
+
+ pfree(oidarr);
+
+ return list;
+}
+
+/*
+ * find_all_partitions
+ *
+ * Finds all partitions of partitioned table with OID 'relid' including
+ * those of its partitions (if they are partitioned further).
+ */
+List *
+find_all_partitions(Oid relid, LOCKMODE lockmode)
+{
+ List *all_partitions = find_partitions(relid, lockmode);
+ ListCell *cell;
+
+ foreach (cell, all_partitions)
+ {
+ Oid relid = lfirst_oid(cell);
+ List *partitions;
+ ListCell *innercell;
+
+ /*
+ * If this relation itself is partitioned, queue its partitions
+ * for consideration.
+ */
+ if (is_partitioned(relid))
+ {
+ partitions = find_partitions(relid, lockmode);
+
+ foreach(innercell, partitions)
+ all_partitions = lappend_oid(all_partitions,
+ lfirst_oid(innercell));
+ }
+ }
+
+ return all_partitions;
+}
+
+/*
+ * free_partitions -
+ *
+ * pfree's the partitions in p that were created by GetPartitionBounds
+ */
+void
+free_partitions(PartitionBoundInfo **p, int count)
+{
+ int i;
+
+ for (i = 0; i < count; i++)
+ {
+ /*
+ * Ugly hack to delete only those that GetPartitionBounds() had found.
+ */
+ if (p[i]->oid != InvalidOid)
+ {
+ if (p[i]->rangemins)
+ pfree(p[i]->rangemins);
+ if (p[i]->rangemaxs)
+ pfree(p[i]->rangemaxs);
+ if (p[i]->listvalues)
+ pfree(p[i]->listvalues);
+
+ pfree(p[i]);
+ }
+ }
+ pfree(p);
+}
+
+/*
+ * GetPartitionBounds
+ *
+ * Returns a list of immediate partitions of rel including OIDs
+ * and bound info.
+ */
+PartitionBoundInfo **
+GetPartitionBounds(Relation rel, int *numparts)
+{
+ List *partoids;
+ PartitionBoundInfo **partitions = NULL;
+
+ partoids = find_partitions(RelationGetRelid(rel), NoLock);
+ *numparts = list_length(partoids);
+
+ /* Copy info into the array of Partition to return. */
+ if (*numparts)
+ {
+ int i;
+ int partnatts = rel->rd_pkey->partnatts;
+ ListCell *cell = NULL;
+ PartitionKeyTypeInfo *typinfo = get_key_type_info(rel);
+
+ partitions = (PartitionBoundInfo **) palloc0(*numparts
+ * sizeof(PartitionBoundInfo *));
+
+ i = 0;
+ foreach(cell, partoids)
+ {
+ HeapTuple tuple;
+ Form_pg_partition form;
+ int j;
+ int rangenbounds;
+ Datum *rangebounds;
+ Datum datum;
+ bool isnull;
+ Oid partitionid;
+ PartitionBoundInfo *result = NULL;
+
+ partitionid = lfirst_oid(cell);
+
+ tuple = SearchSysCache1(PARTITIONID, partitionid);
+
+ /* If no tuple found, it means the entry was just dropped. */
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "cache lookup failed for partition %u",
+ partitionid);
+
+ result = (PartitionBoundInfo *) palloc0(sizeof(PartitionBoundInfo));
+ result->oid = partitionid;
+ result->partnatts = partnatts;
+
+ form = (Form_pg_partition) GETSTRUCT(tuple);
+
+ datum = SysCacheGetAttr(PARTITIONID, tuple,
+ Anum_pg_partition_partlistvalues, &isnull);
+ if (!isnull)
+ {
+
+ deconstruct_array(DatumGetArrayTypeP(datum),
+ typinfo->typid[0],
+ typinfo->typlen[0],
+ typinfo->typbyval[0],
+ typinfo->typalign[0],
+ &result->listvalues, NULL, &result->listnvalues);
+
+ ReleaseSysCache(tuple);
+ partitions[i++] = result;
+ continue;
+ }
+
+ /* A range partition. */
+ datum = SysCacheGetAttr(PARTITIONID, tuple,
+ Anum_pg_partition_partrangebounds, &isnull);
+
+ /* This better not be NULL. */
+ Assert(!isnull);
+
+ /* There is a {min, max} array for every partition column. */
+ deconstruct_array(DatumGetArrayTypeP(datum),
+ ANYARRAYOID, -1, false, 'd',
+ &rangebounds, NULL, &rangenbounds);
+ /* Paranoia. */
+ Assert(rangenbounds = result->partnatts);
+
+ result->rangemins = (Datum *) palloc0(partnatts * sizeof(Datum));
+ result->rangemaxs = (Datum *) palloc0(partnatts * sizeof(Datum));
+
+ for (j = 0; j < result->partnatts; j++)
+ {
+ ArrayType *arr = DatumGetArrayTypeP(rangebounds[j]);
+ Datum *datum;
+ bool *nulls;
+ int dummy;
+
+ deconstruct_array(arr, typinfo->typid[j],
+ typinfo->typlen[j],
+ typinfo->typbyval[j],
+ typinfo->typalign[j],
+ &datum, &nulls, &dummy);
+
+ result->rangeminnull = nulls[0];
+ result->rangemaxnull = nulls[1];
+
+ if (!result->rangeminnull)
+ result->rangemins[j] = datum[0];
+
+ if (!result->rangemaxnull)
+ result->rangemaxs[j] = datum[1];
+ }
+
+ ReleaseSysCache(tuple);
+
+ partitions[i++] = result;
+ }
+
+ pfree(typinfo);
+ }
+
+ return partitions;
+}
+
+/* The qsort comparison function. */
+static int
+oid_cmp(const void *p1, const void *p2)
+{
+ Oid v1 = *((const Oid *) p1);
+ Oid v2 = *((const Oid *) p2);
+
+ if (v1 < v2)
+ return -1;
+ if (v1 > v2)
+ return 1;
+ return 0;
+}
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index 861048f..b9c54c9 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -196,6 +196,22 @@ analyze_rel(Oid relid, RangeVar *relation, int options,
}
/*
+ * XXX - Analyzing a partitioned table not supported yet
+ */
+ if (onerel->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+ {
+ if(!IsAutoVacuumWorkerProcess())
+ ereport(ERROR,
+ (errmsg("cannot analyze partitioned table \"%s\"", RelationGetRelationName(onerel)),
+ errdetail("ANALYZE on partitioned tables is not implemented yet."),
+ errhint("ANALYZE individual partitions separately.")));
+ else
+ ereport(WARNING,
+ (errmsg("skipping \"%s\" --- cannot analyze partitioned tables",
+ RelationGetRelationName(onerel))));
+ }
+
+ /*
* Check that it's a plain table, materialized view, or foreign table; we
* used to do this in get_rel_oids() but seems safer to check after we've
* locked the relation.
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 7ab4874..94d9e64 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -126,6 +126,11 @@ cluster(ClusterStmt *stmt, bool isTopLevel)
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot cluster temporary tables of other sessions")));
+ if (rel->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+ ereport(ERROR,
+ (errmsg("cannot cluster partitioned table \"%s\"", RelationGetRelationName(rel)),
+ errhint("Run CLUSTER on individual partitions one-by-one.")));
+
if (stmt->indexname == NULL)
{
ListCell *index;
diff --git a/src/backend/commands/comment.c b/src/backend/commands/comment.c
index 6d8c006..3a5e8da 100644
--- a/src/backend/commands/comment.c
+++ b/src/backend/commands/comment.c
@@ -92,6 +92,7 @@ CommentObject(CommentStmt *stmt)
* failures.
*/
if (relation->rd_rel->relkind != RELKIND_RELATION &&
+ relation->rd_rel->relkind != RELKIND_PARTITIONED_REL &&
relation->rd_rel->relkind != RELKIND_VIEW &&
relation->rd_rel->relkind != RELKIND_MATVIEW &&
relation->rd_rel->relkind != RELKIND_COMPOSITE_TYPE &&
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 8db1b35..8888566 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -1655,6 +1655,13 @@ BeginCopyTo(Relation rel,
if (rel != NULL && rel->rd_rel->relkind != RELKIND_RELATION)
{
+ /* a partitioned table itself does not support heap scan */
+ if (rel->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot copy from partitioned table \"%s\"",
+ RelationGetRelationName(rel)),
+ errhint("Try the COPY (SELECT ...) TO variant.")));
if (rel->rd_rel->relkind == RELKIND_VIEW)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
@@ -2170,6 +2177,7 @@ CopyFrom(CopyState cstate)
Datum *values;
bool *nulls;
ResultRelInfo *resultRelInfo;
+ ResultRelInfo *saved_resultRelInfo = NULL; /* for partitioned table case */
EState *estate = CreateExecutorState(); /* for ExecConstraints() */
ExprContext *econtext;
TupleTableSlot *myslot;
@@ -2178,7 +2186,7 @@ CopyFrom(CopyState cstate)
ErrorContextCallback errcallback;
CommandId mycid = GetCurrentCommandId(true);
int hi_options = 0; /* start with default heap_insert options */
- BulkInsertState bistate;
+ BulkInsertState bistate = NULL;
uint64 processed = 0;
bool useHeapMultiInsert;
int nBufferedTuples = 0;
@@ -2190,7 +2198,8 @@ CopyFrom(CopyState cstate)
Assert(cstate->rel);
- if (cstate->rel->rd_rel->relkind != RELKIND_RELATION)
+ if (cstate->rel->rd_rel->relkind != RELKIND_RELATION &&
+ cstate->rel->rd_rel->relkind != RELKIND_PARTITIONED_REL)
{
if (cstate->rel->rd_rel->relkind == RELKIND_VIEW)
ereport(ERROR,
@@ -2219,6 +2228,13 @@ CopyFrom(CopyState cstate)
RelationGetRelationName(cstate->rel))));
}
+ /* prevent copying to partitions directly */
+ if (cstate->rel->rd_rel->relispartition)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot copy to a partition of another table"),
+ errhint("Perform COPY on the parent instead.")));
+
tupDesc = RelationGetDescr(cstate->rel);
/*----------
@@ -2321,10 +2337,14 @@ CopyFrom(CopyState cstate)
* expressions. Such triggers or expressions might query the table we're
* inserting to, and act differently if the tuples that have already been
* processed and prepared for insertion are not there.
+ *
+ * Also, Do not use multi- mode if inserting into a partitioned table
+ * because we need to determine a partition for each row individually.
*/
if ((resultRelInfo->ri_TrigDesc != NULL &&
(resultRelInfo->ri_TrigDesc->trig_insert_before_row ||
resultRelInfo->ri_TrigDesc->trig_insert_instead_row)) ||
+ resultRelInfo->ri_Partitions != NULL ||
cstate->volatile_defexprs)
{
useHeapMultiInsert = false;
@@ -2349,7 +2369,10 @@ CopyFrom(CopyState cstate)
values = (Datum *) palloc(tupDesc->natts * sizeof(Datum));
nulls = (bool *) palloc(tupDesc->natts * sizeof(bool));
- bistate = GetBulkInsertState();
+ /* bistate confuses heap_insert() as to which relation's buffers to use */
+ if (!resultRelInfo->ri_Partitions)
+ bistate = GetBulkInsertState();
+
econtext = GetPerTupleExprContext(estate);
/* Set up callback to identify error line number */
@@ -2421,6 +2444,18 @@ CopyFrom(CopyState cstate)
if (cstate->rel->rd_att->constr)
ExecConstraints(resultRelInfo, slot, estate);
+ /*
+ * Switch resultRelInfo to one corresponding to the partition
+ * that this tuple maps to.
+ */
+ if (resultRelInfo->ri_Partitions)
+ {
+ saved_resultRelInfo = resultRelInfo;
+ econtext->ecxt_scantuple = slot; /* for partition key expressions */
+ resultRelInfo = ExecFindPartition(resultRelInfo, slot, estate);
+ estate->es_result_relation_info = resultRelInfo;
+ }
+
if (useHeapMultiInsert)
{
/* Add this tuple to the tuple buffer */
@@ -2451,7 +2486,8 @@ CopyFrom(CopyState cstate)
List *recheckIndexes = NIL;
/* OK, store the tuple and create index entries for it */
- heap_insert(cstate->rel, tuple, mycid, hi_options, bistate);
+ heap_insert(resultRelInfo->ri_RelationDesc,
+ tuple, mycid, hi_options, bistate);
if (resultRelInfo->ri_NumIndices > 0)
recheckIndexes = ExecInsertIndexTuples(slot, &(tuple->t_self),
@@ -2472,6 +2508,17 @@ CopyFrom(CopyState cstate)
*/
processed++;
}
+
+ /* Restore the resultRelInfo, if had been switched. */
+ if (saved_resultRelInfo)
+ {
+ heap_close(resultRelInfo->ri_RelationDesc, RowExclusiveLock);
+ ExecCloseIndices(resultRelInfo);
+ pfree(resultRelInfo);
+ resultRelInfo = saved_resultRelInfo;
+ estate->es_result_relation_info = resultRelInfo;
+ saved_resultRelInfo = NULL; /* aka, paranoia! */
+ }
}
/* Flush any remaining buffered tuples */
@@ -2484,7 +2531,8 @@ CopyFrom(CopyState cstate)
/* Done, clean up */
error_context_stack = errcallback.previous;
- FreeBulkInsertState(bistate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
MemoryContextSwitchTo(oldcontext);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index b450bcf..d376bda 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -65,8 +65,6 @@ static void ComputeIndexAttrs(IndexInfo *indexInfo,
char *accessMethodName, Oid accessMethodId,
bool amcanorder,
bool isconstraint);
-static Oid GetIndexOpClass(List *opclass, Oid attrType,
- char *accessMethodName, Oid accessMethodId);
static char *ChooseIndexName(const char *tabname, Oid namespaceId,
List *colnames, List *exclusionOpNames,
bool primary, bool isconstraint);
@@ -375,6 +373,11 @@ DefineIndex(Oid relationId,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot create index on foreign table \"%s\"",
RelationGetRelationName(rel))));
+ else if (rel->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot create index on partitioned table \"%s\"",
+ RelationGetRelationName(rel))));
else
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
@@ -1211,9 +1214,9 @@ ComputeIndexAttrs(IndexInfo *indexInfo,
/*
* Resolve possibly-defaulted operator class specification
*/
-static Oid
+Oid
GetIndexOpClass(List *opclass, Oid attrType,
- char *accessMethodName, Oid accessMethodId)
+ const char *accessMethodName, Oid accessMethodId)
{
char *schemaname;
char *opcname;
diff --git a/src/backend/commands/lockcmds.c b/src/backend/commands/lockcmds.c
index a167082..3897b78 100644
--- a/src/backend/commands/lockcmds.c
+++ b/src/backend/commands/lockcmds.c
@@ -88,7 +88,7 @@ RangeVarCallbackForLockTable(const RangeVar *rv, Oid relid, Oid oldrelid,
* check */
/* Currently, we only allow plain tables to be locked */
- if (relkind != RELKIND_RELATION)
+ if (relkind != RELKIND_RELATION && relkind != RELKIND_PARTITIONED_REL)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not a table",
diff --git a/src/backend/commands/policy.c b/src/backend/commands/policy.c
index bcf4a8f..e239b43 100644
--- a/src/backend/commands/policy.c
+++ b/src/backend/commands/policy.c
@@ -88,7 +88,7 @@ RangeVarCallbackForPolicy(const RangeVar *rv, Oid relid, Oid oldrelid,
rv->relname)));
/* Relation type MUST be a table. */
- if (relkind != RELKIND_RELATION)
+ if (relkind != RELKIND_RELATION && relkind != RELKIND_PARTITIONED_REL)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not a table", rv->relname)));
diff --git a/src/backend/commands/seclabel.c b/src/backend/commands/seclabel.c
index 1ef98ce..8df2e23 100644
--- a/src/backend/commands/seclabel.c
+++ b/src/backend/commands/seclabel.c
@@ -107,6 +107,7 @@ ExecSecLabelStmt(SecLabelStmt *stmt)
* are the only relkinds for which pg_dump will dump labels).
*/
if (relation->rd_rel->relkind != RELKIND_RELATION &&
+ relation->rd_rel->relkind != RELKIND_PARTITIONED_REL &&
relation->rd_rel->relkind != RELKIND_VIEW &&
relation->rd_rel->relkind != RELKIND_MATVIEW &&
relation->rd_rel->relkind != RELKIND_COMPOSITE_TYPE &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 126b119..9036075 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -37,6 +37,9 @@
#include "catalog/pg_inherits_fn.h"
#include "catalog/pg_namespace.h"
#include "catalog/pg_opclass.h"
+#include "catalog/pg_partition.h"
+#include "catalog/pg_partition_fn.h"
+#include "catalog/pg_partitioned_rel.h"
#include "catalog/pg_tablespace.h"
#include "catalog/pg_trigger.h"
#include "catalog/pg_type.h"
@@ -82,10 +85,12 @@
#include "storage/smgr.h"
#include "utils/acl.h"
#include "utils/builtins.h"
+#include "utils/datum.h"
#include "utils/fmgroids.h"
#include "utils/inval.h"
#include "utils/lsyscache.h"
#include "utils/memutils.h"
+#include "utils/partition.h"
#include "utils/relcache.h"
#include "utils/ruleutils.h"
#include "utils/snapmgr.h"
@@ -259,6 +264,12 @@ struct DropRelationCallbackState
bool concurrent;
};
+/* for find_att_reference_walker */
+typedef struct
+{
+ AttrNumber attnum;
+} find_att_reference_context;
+
/* Alter table target-type flags for ATSimplePermissions */
#define ATT_TABLE 0x0001
#define ATT_VIEW 0x0002
@@ -322,6 +333,8 @@ static void ATSimpleRecursion(List **wqueue, Relation rel,
AlterTableCmd *cmd, bool recurse, LOCKMODE lockmode);
static void ATTypedTableRecursion(List **wqueue, Relation rel, AlterTableCmd *cmd,
LOCKMODE lockmode);
+static void ATPartitionedTableRecursion(List **wqueue, Relation rel, AlterTableCmd *cmd,
+ LOCKMODE lockmode);
static List *find_typed_table_dependencies(Oid typeOid, const char *typeName,
DropBehavior behavior);
static void ATPrepAddColumn(List **wqueue, Relation rel, bool recurse, bool recursing,
@@ -398,7 +411,7 @@ static void change_owner_recurse_to_sequences(Oid relationOid,
static ObjectAddress ATExecClusterOn(Relation rel, const char *indexName,
LOCKMODE lockmode);
static void ATExecDropCluster(Relation rel, LOCKMODE lockmode);
-static bool ATPrepChangePersistence(Relation rel, bool toLogged);
+static bool ATPrepChangePersistence(Relation rel, bool toLogged, bool recursing);
static void ATPrepSetTableSpace(AlteredTableInfo *tab, Relation rel,
char *tablespacename, LOCKMODE lockmode);
static void ATExecSetTableSpace(Oid tableOid, Oid newTableSpace, LOCKMODE lockmode);
@@ -419,6 +432,8 @@ static void ATExecReplicaIdentity(Relation rel, ReplicaIdentityStmt *stmt, LOCKM
static void ATExecGenericOptions(Relation rel, List *options);
static void ATExecEnableRowSecurity(Relation rel);
static void ATExecDisableRowSecurity(Relation rel);
+static void ATExecAttachPartition(Relation parent, PartitionDef *partition);
+static void ATExecDetachPartition(Relation parent, PartitionDef *partition);
static void copy_relation_data(SMgrRelation rel, SMgrRelation dst,
ForkNumber forkNum, char relpersistence);
@@ -428,6 +443,28 @@ static void RangeVarCallbackForDropRelation(const RangeVar *rel, Oid relOid,
Oid oldRelOid, void *arg);
static void RangeVarCallbackForAlterRelation(const RangeVar *rv, Oid relid,
Oid oldrelid, void *arg);
+static PartitionBy *transformPartitionBy(Relation rel, PartitionBy *partitionby);
+static PartitionKeyInfo *ComputePartitionAttrs(PartitionKeyInfo *pkinfo,
+ Oid *partClassOids, Oid relid, List *partParams);
+static PartitionBoundInfo *EvalPartitionBound(Relation parent,
+ PartitionDef *partdef, int *listnvalues);
+static Datum *evalPartitionListBound(List *values, PartitionKeyTypeInfo *typinfo,
+ int nvalues);
+static Datum *evalPartitionRangeBound(List *values, PartitionKeyTypeInfo *typinfo,
+ int partnatts);
+static void CheckPartitionAgainstExisting(Relation parent, PartitionBoundInfo *new,
+ Datum **final);
+static void ATPrepColumnDefault(Relation rel, bool recursing);
+static void ATPrepDropNotNull(Relation rel, bool recursing);
+static void ATPrepSetNotNull(Relation rel, bool recursing);
+static void ATPrepAddConstraint(Relation rel);
+static void ATPrepDropConstraint(Relation rel);
+static void ATPrepAlterConstraint(Relation rel);
+static void ATExecChangePersistence(Relation rel, char persistence);
+static void SetRelationPersistence(Oid relationId, char persistence);
+static void remove_partitioning_dependency(Oid partitionOid, Oid parentOid);
+static Oid RangeVarGetTablespace(RangeVar *relation);
+static void SetRelationSpcFileNode(Relation rel, Oid newTableSpace, Oid newFileNode);
/* ----------------------------------------------------------------
@@ -471,6 +508,8 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
static char *validnsps[] = HEAP_RELOPT_NAMESPACES;
Oid ofTypeId;
ObjectAddress address;
+ PartitionKeyInfo *pkinfo = NULL;
+ Oid partOpClassOids[PARTITION_MAX_KEYS];
/*
* Truncate relname to appropriate length (probably a waste of time, as
@@ -515,6 +554,11 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
{
tablespaceId = get_tablespace_oid(stmt->tablespacename, false);
}
+ else if (stmt->partitionOf)
+ {
+ tablespaceId = RangeVarGetTablespace(stmt->partitionOf);
+ /* note InvalidOid is OK in this case */
+ }
else
{
tablespaceId = GetDefaultTablespace(stmt->relation->relpersistence);
@@ -705,6 +749,48 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
AddRelationNewConstraints(rel, rawDefaults, stmt->constraints,
true, true, false);
+ /*
+ * Store the partition key into pg_partitioned_rel. It consists of columns
+ * constituting the key and optional opclass to use with it along with the
+ * partitioning strategy (range or list).
+ */
+ if (stmt->partitionby)
+ {
+ int numPartitionAttrs;
+
+ Assert(rel->rd_rel->relkind == RELKIND_PARTITIONED_REL);
+
+ /* sanity */
+ numPartitionAttrs = list_length(stmt->partitionby->partParams);
+ if (numPartitionAttrs < 1)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("must specify at least one column in partition key")));
+ if (numPartitionAttrs > PARTITION_MAX_KEYS)
+ ereport(ERROR,
+ (errcode(ERRCODE_TOO_MANY_COLUMNS),
+ errmsg("cannot use more than %d columns in partition key",
+ INDEX_MAX_KEYS)));
+ if (stmt->partitionby->strategy == PARTITION_STRAT_LIST
+ && numPartitionAttrs > 1)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("cannot use more than one column in partition key"),
+ errdetail("Only one column allowed when using the list strategy.")));
+
+ /*
+ * Take care of any expressions in the partition key. This should ideally
+ * have been done in parse_utilcmd.c but could not be done in this case
+ * because we need "rel" to be able to look at the key columns.
+ */
+ stmt->partitionby = transformPartitionBy(rel, stmt->partitionby);
+
+ pkinfo = ComputePartitionAttrs(pkinfo, partOpClassOids, relationId,
+ stmt->partitionby->partParams);
+
+ StorePartitionKey(rel, pkinfo, partOpClassOids, stmt->partitionby->strategy);
+ }
+
ObjectAddressSet(address, RelationRelationId, relationId);
/*
@@ -950,7 +1036,25 @@ RangeVarCallbackForDropRelation(const RangeVar *rel, Oid relOid, Oid oldRelOid,
return; /* concurrently dropped, so nothing to do */
classform = (Form_pg_class) GETSTRUCT(tuple);
- if (classform->relkind != relkind)
+ /*
+ * Reject dropping tables marked as partitions. They need to be detached
+ * from their partitioning parent before they can be safely dropped.
+ */
+ if (classform->relispartition)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot drop table \"%s\"", rel->relname),
+ errdetail("\"%s\" is a partition of table \"%s\".", rel->relname,
+ get_rel_name(get_partition_parent(relOid, false))),
+ errhint("You will need to detach the partition first.")));
+
+ /*
+ * Both normal and partitioned tables are dropped using DROP TABLE; so
+ * both relkinds are possible but the caller can only pass one.
+ */
+ if (classform->relkind != relkind &&
+ (relkind == RELKIND_RELATION &&
+ classform->relkind != RELKIND_PARTITIONED_REL))
DropErrorMsgWrongType(rel->relname, classform->relkind, relkind);
/* Allow DROP to either table owner or schema owner */
@@ -1516,6 +1620,18 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
*/
relation = heap_openrv(parent, ShareUpdateExclusiveLock);
+ /* special case partitioned tables and partitions */
+ if (relation->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot inherit from partitioned table \"%s\"",
+ parent->relname)));
+ if (relation->rd_rel->relispartition)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot inherit from partition \"%s\"",
+ parent->relname)));
+
if (relation->rd_rel->relkind != RELKIND_RELATION &&
relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
ereport(ERROR,
@@ -2153,6 +2269,7 @@ renameatt_check(Oid myrelid, Form_pg_class classform, bool recursing)
* restriction.
*/
if (relkind != RELKIND_RELATION &&
+ relkind != RELKIND_PARTITIONED_REL &&
relkind != RELKIND_VIEW &&
relkind != RELKIND_MATVIEW &&
relkind != RELKIND_COMPOSITE_TYPE &&
@@ -3046,6 +3163,15 @@ AlterTableGetLockLevel(List *cmds)
cmd_lockmode = AlterTableGetRelOptionsLockLevel((List *) cmd->def);
break;
+ /*
+ * The following lock refers to one taken on the parent for
+ * this ALTER TABLE invocation.
+ */
+ case AT_AttachPartition:
+ case AT_DetachPartition:
+ cmd_lockmode = AccessExclusiveLock;
+ break;
+
default: /* oops */
elog(ERROR, "unrecognized alter table type: %d",
(int) cmd->subtype);
@@ -3121,14 +3247,16 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
cmd = copyObject(cmd);
/*
- * Do permissions checking, recursion to child tables if needed, and any
- * additional phase-1 processing needed.
+ * Do permissions checking, recursion to child tables or partitions if
+ * needed, and any additional phase-1 processing needed.
*/
switch (cmd->subtype)
{
case AT_AddColumn: /* ADD COLUMN */
ATSimplePermissions(rel,
ATT_TABLE | ATT_COMPOSITE_TYPE | ATT_FOREIGN_TABLE);
+ /* Take care of partitions of this relation if partitioned */
+ ATPartitionedTableRecursion(wqueue, rel, cmd, lockmode);
ATPrepAddColumn(wqueue, rel, recurse, recursing, false, cmd,
lockmode);
/* Recursion occurs during execution phase */
@@ -3152,23 +3280,31 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
*/
ATSimplePermissions(rel, ATT_TABLE | ATT_VIEW | ATT_FOREIGN_TABLE);
ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode);
- /* No command-specific prep needed */
+ /* Take care of partitions of this relation if partitioned */
+ ATPartitionedTableRecursion(wqueue, rel, cmd, lockmode);
+ ATPrepColumnDefault(rel, recursing);
pass = cmd->def ? AT_PASS_ADD_CONSTR : AT_PASS_DROP;
break;
case AT_DropNotNull: /* ALTER COLUMN DROP NOT NULL */
ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode);
- /* No command-specific prep needed */
+ /* Take care of partitions of this relation if partitioned */
+ ATPartitionedTableRecursion(wqueue, rel, cmd, lockmode);
+ ATPrepDropNotNull(rel, recursing);
pass = AT_PASS_DROP;
break;
case AT_SetNotNull: /* ALTER COLUMN SET NOT NULL */
ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode);
- /* No command-specific prep needed */
+ /* Take care of partitions of this relation if partitioned */
+ ATPartitionedTableRecursion(wqueue, rel, cmd, lockmode);
+ ATPrepSetNotNull(rel, recursing);
pass = AT_PASS_ADD_CONSTR;
break;
case AT_SetStatistics: /* ALTER COLUMN SET STATISTICS */
ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode);
+ /* Take care of partitions of this relation if partitioned */
+ ATPartitionedTableRecursion(wqueue, rel, cmd, lockmode);
/* Performs own permission checks */
ATPrepSetStatistics(rel, cmd->name, cmd->def, lockmode);
pass = AT_PASS_MISC;
@@ -3188,6 +3324,8 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
case AT_DropColumn: /* DROP COLUMN */
ATSimplePermissions(rel,
ATT_TABLE | ATT_COMPOSITE_TYPE | ATT_FOREIGN_TABLE);
+ /* Take care of partitions of this relation if partitioned */
+ ATPartitionedTableRecursion(wqueue, rel, cmd, lockmode);
ATPrepDropColumn(wqueue, rel, recurse, recursing, cmd, lockmode);
/* Recursion occurs during execution phase */
pass = AT_PASS_DROP;
@@ -3200,8 +3338,10 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
break;
case AT_AddConstraint: /* ADD CONSTRAINT */
ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
- /* Recursion occurs during execution phase */
- /* No command-specific prep needed except saving recurse flag */
+ /* The partitioned table recursion does not occur at all */
+ /* Simple recursion occurs during execution phase */
+ ATPrepAddConstraint(rel);
+ /* Save recurse flag */
if (recurse)
cmd->subtype = AT_AddConstraintRecurse;
pass = AT_PASS_ADD_CONSTR;
@@ -3214,8 +3354,10 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
break;
case AT_DropConstraint: /* DROP CONSTRAINT */
ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
- /* Recursion occurs during execution phase */
- /* No command-specific prep needed except saving recurse flag */
+ /* The partitioned table recursion does not occur at all */
+ /* Simple recursion occurs during execution phase */
+ ATPrepDropConstraint(rel);
+ /* Save recurse flag */
if (recurse)
cmd->subtype = AT_DropConstraintRecurse;
pass = AT_PASS_DROP;
@@ -3223,8 +3365,13 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
case AT_AlterColumnType: /* ALTER COLUMN TYPE */
ATSimplePermissions(rel,
ATT_TABLE | ATT_COMPOSITE_TYPE | ATT_FOREIGN_TABLE);
- /* Performs own recursion */
+ /* Performs own (simple) recursion */
ATPrepAlterColumnType(wqueue, tab, rel, recurse, recursing, cmd, lockmode);
+ /*
+ * Take care of partitions of this relation if partitioned
+ * XXX - unlike other places, after ATPrep* is done.
+ */
+ ATPartitionedTableRecursion(wqueue, rel, cmd, lockmode);
pass = AT_PASS_ALTER_TYPE;
break;
case AT_AlterColumnGenericOptions:
@@ -3234,7 +3381,8 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
pass = AT_PASS_MISC;
break;
case AT_ChangeOwner: /* ALTER OWNER */
- /* This command never recurses */
+ /* Take care of partitions of this relation if partitioned */
+ ATPartitionedTableRecursion(wqueue, rel, cmd, lockmode);
/* No command-specific prep needed */
pass = AT_PASS_MISC;
break;
@@ -3247,7 +3395,9 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
break;
case AT_SetLogged: /* SET LOGGED */
ATSimplePermissions(rel, ATT_TABLE);
- tab->chgPersistence = ATPrepChangePersistence(rel, true);
+ /* Take care of partitions of this relation if partitioned */
+ ATPartitionedTableRecursion(wqueue, rel, cmd, lockmode);
+ tab->chgPersistence = ATPrepChangePersistence(rel, true, recursing);
/* force rewrite if necessary; see comment in ATRewriteTables */
if (tab->chgPersistence)
{
@@ -3258,7 +3408,9 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
break;
case AT_SetUnLogged: /* SET UNLOGGED */
ATSimplePermissions(rel, ATT_TABLE);
- tab->chgPersistence = ATPrepChangePersistence(rel, false);
+ /* Take care of partitions of this relation if partitioned */
+ ATPartitionedTableRecursion(wqueue, rel, cmd, lockmode);
+ tab->chgPersistence = ATPrepChangePersistence(rel, false, recursing);
/* force rewrite if necessary; see comment in ATRewriteTables */
if (tab->chgPersistence)
{
@@ -3269,13 +3421,17 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
break;
case AT_AddOids: /* SET WITH OIDS */
ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
+ /* Take care of partitions of this relation if partitioned */
+ ATPartitionedTableRecursion(wqueue, rel, cmd, lockmode);
if (!rel->rd_rel->relhasoids || recursing)
ATPrepAddOids(wqueue, rel, recurse, cmd, lockmode);
- /* Recursion occurs during execution phase */
+ /* Simple recursion occurs during execution phase */
pass = AT_PASS_ADD_COL;
break;
case AT_DropOids: /* SET WITHOUT OIDS */
ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
+ /* Take care of partitions of this relation if partitioned */
+ ATPartitionedTableRecursion(wqueue, rel, cmd, lockmode);
/* Performs own recursion */
if (rel->rd_rel->relhasoids)
{
@@ -3290,6 +3446,8 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
break;
case AT_SetTableSpace: /* SET TABLESPACE */
ATSimplePermissions(rel, ATT_TABLE | ATT_MATVIEW | ATT_INDEX);
+ /* Take care of partitions of this relation if partitioned. */
+ ATPartitionedTableRecursion(wqueue, rel, cmd, lockmode);
/* This command never recurses */
ATPrepSetTableSpace(tab, rel, cmd->name, lockmode);
pass = AT_PASS_MISC; /* doesn't actually matter */
@@ -3316,11 +3474,15 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
break;
case AT_AlterConstraint: /* ALTER CONSTRAINT */
ATSimplePermissions(rel, ATT_TABLE);
+ /* The partitioned table recursion does not occur at all */
+ ATPrepAlterConstraint(rel);
pass = AT_PASS_MISC;
break;
case AT_ValidateConstraint: /* VALIDATE CONSTRAINT */
ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
- /* Recursion occurs during execution phase */
+ /* Take care of partitions of this relation if partitioned */
+ ATPartitionedTableRecursion(wqueue, rel, cmd, lockmode);
+ /* Simple recursion occurs during execution phase */
/* No command-specific prep needed except saving recurse flag */
if (recurse)
cmd->subtype = AT_ValidateConstraintRecurse;
@@ -3330,7 +3492,6 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
ATSimplePermissions(rel, ATT_TABLE | ATT_MATVIEW);
pass = AT_PASS_MISC;
/* This command never recurses */
- /* No command-specific prep needed */
break;
case AT_EnableTrig: /* ENABLE TRIGGER variants */
case AT_EnableAlwaysTrig:
@@ -3353,7 +3514,6 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
case AT_DisableRowSecurity:
ATSimplePermissions(rel, ATT_TABLE);
/* These commands never recurse */
- /* No command-specific prep needed */
pass = AT_PASS_MISC;
break;
case AT_GenericOptions:
@@ -3361,6 +3521,12 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
/* No command-specific prep needed */
pass = AT_PASS_MISC;
break;
+ case AT_AttachPartition:
+ case AT_DetachPartition:
+ ATSimplePermissions(rel, ATT_TABLE);
+ /* No command-specific prep needed */
+ pass = AT_PASS_MISC;
+ break;
default: /* oops */
elog(ERROR, "unrecognized alter table type: %d",
(int) cmd->subtype);
@@ -3562,7 +3728,10 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
ATExecDropCluster(rel, lockmode);
break;
case AT_SetLogged: /* SET LOGGED */
+ ATExecChangePersistence(rel, RELPERSISTENCE_PERMANENT);
+ break;
case AT_SetUnLogged: /* SET UNLOGGED */
+ ATExecChangePersistence(rel, RELPERSISTENCE_UNLOGGED);
break;
case AT_AddOids: /* SET WITH OIDS */
/* Use the ADD COLUMN code, unless prep decided to do nothing */
@@ -3670,6 +3839,12 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
case AT_GenericOptions:
ATExecGenericOptions(rel, (List *) cmd->def);
break;
+ case AT_AttachPartition:
+ ATExecAttachPartition(rel, (PartitionDef *) cmd->def);
+ break;
+ case AT_DetachPartition:
+ ATExecDetachPartition(rel, (PartitionDef *) cmd->def);
+ break;
default: /* oops */
elog(ERROR, "unrecognized alter table type: %d",
(int) cmd->subtype);
@@ -3701,8 +3876,12 @@ ATRewriteTables(AlterTableStmt *parsetree, List **wqueue, LOCKMODE lockmode)
{
AlteredTableInfo *tab = (AlteredTableInfo *) lfirst(ltab);
- /* Foreign tables have no storage. */
- if (tab->relkind == RELKIND_FOREIGN_TABLE)
+ /*
+ * Foreign tables and partitioned tables have no storage; though in
+ * the latter case, allow SET TABLESPACE.
+ */
+ if (tab->relkind == RELKIND_FOREIGN_TABLE ||
+ (tab->relkind == RELKIND_PARTITIONED_REL && !tab->newTableSpace))
continue;
/*
@@ -4268,6 +4447,7 @@ ATSimplePermissions(Relation rel, int allowed_targets)
switch (rel->rd_rel->relkind)
{
case RELKIND_RELATION:
+ case RELKIND_PARTITIONED_REL:
actual_target = ATT_TABLE;
break;
case RELKIND_VIEW:
@@ -4439,6 +4619,40 @@ ATTypedTableRecursion(List **wqueue, Relation rel, AlterTableCmd *cmd,
}
}
+/*
+ * ATPartitionedTableRecursion
+ *
+ * Propagate an ALTER TABLE command to all partitions of this relation
+ * including those of its partitions.
+ */
+static void
+ATPartitionedTableRecursion(List **wqueue, Relation rel, AlterTableCmd *cmd,
+ LOCKMODE lockmode)
+{
+ /* Note that the recursion happens at the topmost parent only. */
+ if(rel->rd_rel->relkind == RELKIND_PARTITIONED_REL &&
+ !rel->rd_rel->relispartition)
+ {
+ ListCell *cell;
+ List *partitions;
+
+ partitions = find_all_partitions(RelationGetRelid(rel), lockmode);
+
+ foreach(cell, partitions)
+ {
+ Oid partid = lfirst_oid(cell);
+ Relation partrel;
+
+ partrel = relation_open(partid, lockmode);
+
+ Assert(partrel->rd_rel->relispartition);
+
+ CheckTableNotInUse(partrel, "ALTER TABLE");
+ ATPrepCmd(wqueue, partrel, cmd, false, true, lockmode);
+ relation_close(partrel, NoLock);
+ }
+ }
+}
/*
* find_composite_type_dependencies
@@ -4498,6 +4712,7 @@ find_composite_type_dependencies(Oid typeOid, Relation origRelation,
att = rel->rd_att->attrs[pg_depend->objsubid - 1];
if (rel->rd_rel->relkind == RELKIND_RELATION ||
+ rel->rd_rel->relkind == RELKIND_PARTITIONED_REL ||
rel->rd_rel->relkind == RELKIND_MATVIEW)
{
if (origTypeName)
@@ -4660,6 +4875,20 @@ ATPrepAddColumn(List **wqueue, Relation rel, bool recurse, bool recursing,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot add column to typed table")));
+ if (rel->rd_rel->relispartition && !recursing)
+ {
+ if(!strcmp(((ColumnDef *) cmd->def)->colname, "oid"))
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot add OID column to a partition of another table"),
+ errhint("Perform SET WITH OIDS on the parent instead.")));
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot add column to a partition of another table"),
+ errhint("Perform ADD COLUMN on the parent instead.")));
+ }
+
if (rel->rd_rel->relkind == RELKIND_COMPOSITE_TYPE)
ATTypedTableRecursion(wqueue, rel, cmd, lockmode);
@@ -5138,6 +5367,16 @@ ATPrepAddOids(List **wqueue, Relation rel, bool recurse, AlterTableCmd *cmd, LOC
cmd->subtype = AT_AddOidsRecurse;
}
+static void
+ATPrepDropNotNull(Relation rel, bool recursing)
+{
+ if (rel->rd_rel->relispartition && !recursing)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot alter column of a partition of another table"),
+ errhint("Perform ALTER COLUMN on the parent instead.")));
+}
+
/*
* ALTER TABLE ALTER COLUMN DROP NOT NULL
*
@@ -5245,6 +5484,16 @@ ATExecDropNotNull(Relation rel, const char *colName, LOCKMODE lockmode)
return address;
}
+static void
+ATPrepSetNotNull(Relation rel, bool recursing)
+{
+ if (rel->rd_rel->relispartition && !recursing)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot alter column of a partition of another table"),
+ errhint("Perform ALTER COLUMN on the parent instead.")));
+}
+
/*
* ALTER TABLE ALTER COLUMN SET NOT NULL
*
@@ -5311,6 +5560,16 @@ ATExecSetNotNull(AlteredTableInfo *tab, Relation rel,
return address;
}
+static void
+ATPrepColumnDefault(Relation rel, bool recursing)
+{
+ if (rel->rd_rel->relispartition && !recursing)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot alter column of a partition of another table"),
+ errhint("Perform ALTER COLUMN on the parent instead.")));
+}
+
/*
* ALTER TABLE ALTER COLUMN SET/DROP DEFAULT
*
@@ -5387,6 +5646,7 @@ ATPrepSetStatistics(Relation rel, const char *colName, Node *newValue, LOCKMODE
* allowSystemTableMods to be turned on.
*/
if (rel->rd_rel->relkind != RELKIND_RELATION &&
+ rel->rd_rel->relkind != RELKIND_PARTITIONED_REL &&
rel->rd_rel->relkind != RELKIND_MATVIEW &&
rel->rd_rel->relkind != RELKIND_INDEX &&
rel->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
@@ -5654,6 +5914,20 @@ ATPrepDropColumn(List **wqueue, Relation rel, bool recurse, bool recursing,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot drop column from typed table")));
+ if (rel->rd_rel->relispartition && !recursing)
+ {
+ if(!strcmp(cmd->name, "oid"))
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot drop OID column of a partition of another table"),
+ errhint("Perform SET WITHOUT OIDS on the parent instead.")));
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot drop column of a partition of another table"),
+ errhint("Perform DROP COLUMN on the parent instead.")));
+ }
+
if (rel->rd_rel->relkind == RELKIND_COMPOSITE_TYPE)
ATTypedTableRecursion(wqueue, rel, cmd, lockmode);
@@ -5661,6 +5935,24 @@ ATPrepDropColumn(List **wqueue, Relation rel, bool recurse, bool recursing,
cmd->subtype = AT_DropColumnRecurse;
}
+static bool
+find_att_reference_walker(Node *node, find_att_reference_context *context)
+{
+ if (node == NULL)
+ return false;
+
+ if (IsA(node, Var))
+ {
+ Var *variable = (Var *) node;
+ AttrNumber attnum = variable->varattno;
+
+ if (attnum == context->attnum)
+ return true;
+ }
+
+ return expression_tree_walker(node, find_att_reference_walker, context);
+}
+
/*
* Return value is that of the dropped column.
*/
@@ -5719,6 +6011,41 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
errmsg("cannot drop inherited column \"%s\"",
colName)));
+ /* Don't drop if used in partition key */
+ if (rel->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+ {
+ PartitionKeyInfo *key = BuildPartitionKeyInfo(rel);
+ int i;
+ ListCell *partexpr;
+
+ partexpr = list_head(key->pi_Expressions);
+ for (i = 0; i < key->pi_NumKeyAttrs; i++)
+ {
+ AttrNumber partatt = key->pi_KeyAttrNumbers[i];
+
+ if(partatt != 0)
+ {
+ if (attnum == partatt)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("cannot drop partition key column \"%s\"",
+ colName)));
+ }
+ else
+ {
+ find_att_reference_context context;
+
+ context.attnum = attnum;
+ if (find_att_reference_walker(lfirst(partexpr), &context))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("cannot drop column referenced in partition key expressions")));
+
+ partexpr = lnext(partexpr);
+ }
+ }
+ }
+
ReleaseSysCache(tuple);
/*
@@ -5990,6 +6317,24 @@ ATExecAddIndexConstraint(AlteredTableInfo *tab, Relation rel,
return address;
}
+static void
+ATPrepAddConstraint(Relation rel)
+{
+ /*
+ * It seems OK to disallow CHECK constraints on partitions. Any that
+ * are necessary should be added to the parent. ExecConstraints()
+ * should only ever look at the parent relation's constraints. Also,
+ * validateConstraint() is taught to use parent's constraint tuples
+ * during a ALTER TABLE VALIDATE CONSTRAINT on a partition or its
+ * recursive application when performing the command on parent.
+ */
+ if (rel->rd_rel->relispartition)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot add constraints to a partition of another table"),
+ errhint("Perform ADD CONSTRAINT on the parent instead.")));
+}
+
/*
* ALTER TABLE ADD CONSTRAINT
*
@@ -6246,6 +6591,12 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
* Validity checks (permission checks wait till we have the column
* numbers)
*/
+ if (pkrel->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot reference relation \"%s\"", RelationGetRelationName(pkrel)),
+ errdetail("Referencing partitioned tables in foreign key constraints is not supported.")));
+
if (pkrel->rd_rel->relkind != RELKIND_RELATION)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
@@ -6613,6 +6964,20 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
return address;
}
+static void
+ATPrepAlterConstraint(Relation rel)
+{
+ /*
+ * Remember there are no table constraints allowed on partitions
+ * at all. There is nothing to alter in the first place.
+ */
+ if (rel->rd_rel->relispartition)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot alter constraints of a partition of another table"),
+ errhint("Perform ALTER CONSTRAINT on the parent instead.")));
+}
+
/*
* ALTER TABLE ALTER CONSTRAINT
*
@@ -6791,16 +7156,26 @@ ATExecValidateConstraint(Relation rel, char *constrName, bool recurse,
Form_pg_constraint con = NULL;
bool found = false;
ObjectAddress address;
+ Oid conrelid;
conrel = heap_open(ConstraintRelationId, RowExclusiveLock);
/*
+ * A partition's table constraints are same as the parent's. So, scan
+ * pg_constraint using parent OID instead of its own.
+ */
+ if (rel->rd_rel->relispartition)
+ conrelid = get_partition_parent(rel->rd_id, true);
+ else
+ conrelid = RelationGetRelid(rel);
+
+ /*
* Find and check the target constraint
*/
ScanKeyInit(&key,
Anum_pg_constraint_conrelid,
BTEqualStrategyNumber, F_OIDEQ,
- ObjectIdGetDatum(RelationGetRelid(rel)));
+ ObjectIdGetDatum(conrelid));
scan = systable_beginscan(conrel, ConstraintRelidIndexId,
true, NULL, 1, &key);
@@ -7302,6 +7677,14 @@ validateCheckConstraint(Relation rel, HeapTuple constrtup)
if (rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
return;
+ /*
+ * There is nothing to do for a partitioned relation itself. If invoked
+ * using ALTER TABLE VALIDATE CONSTRAINT, it has been arranged that the
+ * command is applied to individual partitions where this could be done.
+ */
+ if (rel->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+ return;
+
constrForm = (Form_pg_constraint) GETSTRUCT(constrtup);
estate = CreateExecutorState();
@@ -7374,6 +7757,14 @@ validateForeignKeyConstraint(char *conname,
Trigger trig;
Snapshot snapshot;
+ /*
+ * There is nothing to do for a partitioned relation itself. If invoked
+ * using ALTER TABLE VALIDATE CONSTRAINT, it has been arranged that the
+ * command is applied to individual partitions where this could be done.
+ */
+ if(rel->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+ return;
+
ereport(DEBUG1,
(errmsg("validating foreign key constraint \"%s\"", conname)));
@@ -7621,6 +8012,20 @@ createForeignKeyTriggers(Relation rel, Oid refRelOid, Constraint *fkconstraint,
indexOid, false);
}
+static void
+ATPrepDropConstraint(Relation rel)
+{
+ /*
+ * Remember there are no table constraints allowed on partitions
+ * at all. There is nothing to drop in the first place.
+ */
+ if (rel->rd_rel->relispartition)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot drop constraints of a partition of another table"),
+ errhint("Perform DROP CONSTRAINT on the parent instead.")));
+}
+
/*
* ALTER TABLE DROP CONSTRAINT
*
@@ -7846,6 +8251,12 @@ ATPrepAlterColumnType(List **wqueue,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot alter column type of typed table")));
+ if (rel->rd_rel->relispartition && !recursing)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot alter column type of a partition of another table"),
+ errhint("Perform ALTER COLUMN TYPE on the parent instead.")));
+
/* lookup the attribute so we can check inheritance status */
tuple = SearchSysCacheAttName(RelationGetRelid(rel), colName);
if (!HeapTupleIsValid(tuple))
@@ -7870,6 +8281,41 @@ ATPrepAlterColumnType(List **wqueue,
errmsg("cannot alter inherited column \"%s\"",
colName)));
+ if (rel->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+ {
+ PartitionKeyInfo *key = BuildPartitionKeyInfo(rel);
+ int i;
+ ListCell *partexpr;
+
+ partexpr = list_head(key->pi_Expressions);
+ for (i = 0; i < key->pi_NumKeyAttrs; i++)
+ {
+ AttrNumber partatt = key->pi_KeyAttrNumbers[i];
+
+ if(partatt != 0)
+ {
+ if (attnum == partatt)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("cannot alter type of partition key column \"%s\"",
+ colName)));
+ }
+ else
+ {
+ find_att_reference_context context;
+
+ context.attnum = attnum;
+ if (find_att_reference_walker(lfirst(partexpr), &context))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("cannot alter type of a column referenced in partition key expressions")));
+
+ partexpr = lnext(partexpr);
+ }
+ }
+ }
+
+
/* Look up the target type */
typenameTypeIdAndMod(NULL, typeName, &targettype, &targettypmod);
@@ -7885,7 +8331,7 @@ ATPrepAlterColumnType(List **wqueue,
list_make1_oid(rel->rd_rel->reltype),
false);
- if (tab->relkind == RELKIND_RELATION)
+ if (tab->relkind == RELKIND_RELATION || tab->relkind == RELKIND_PARTITIONED_REL)
{
/*
* Set up an expression to transform the old data value to the new
@@ -8892,6 +9338,7 @@ ATExecChangeOwner(Oid relationOid, Oid newOwnerId, bool recursing, LOCKMODE lock
switch (tuple_class->relkind)
{
case RELKIND_RELATION:
+ case RELKIND_PARTITIONED_REL:
case RELKIND_VIEW:
case RELKIND_MATVIEW:
case RELKIND_FOREIGN_TABLE:
@@ -9355,6 +9802,7 @@ ATExecSetRelOptions(Relation rel, List *defList, AlterTableType operation,
switch (rel->rd_rel->relkind)
{
case RELKIND_RELATION:
+ case RELKIND_PARTITIONED_REL:
case RELKIND_TOASTVALUE:
case RELKIND_MATVIEW:
(void) heap_reloptions(rel->rd_rel->relkind, newOptions, true);
@@ -9514,9 +9962,6 @@ ATExecSetTableSpace(Oid tableOid, Oid newTableSpace, LOCKMODE lockmode)
Oid newrelfilenode;
RelFileNode newrnode;
SMgrRelation dstrel;
- Relation pg_class;
- HeapTuple tuple;
- Form_pg_class rd_rel;
ForkNumber forkNum;
List *reltoastidxids = NIL;
ListCell *lc;
@@ -9541,6 +9986,18 @@ ATExecSetTableSpace(Oid tableOid, Oid newTableSpace, LOCKMODE lockmode)
}
/*
+ * For a partitioned table, setting pg_class.reltablespace suffices.
+ */
+ if(rel->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+ {
+ /* XXX - keeping the relfilenode does not make any difference. */
+ SetRelationSpcFileNode(rel, newTableSpace, rel->rd_rel->relfilenode);
+ relation_close(rel, lockmode);
+
+ return;
+ }
+
+ /*
* We cannot support moving mapped relations into different tablespaces.
* (In particular this eliminates all shared catalogs.)
*/
@@ -9575,14 +10032,6 @@ ATExecSetTableSpace(Oid tableOid, Oid newTableSpace, LOCKMODE lockmode)
relation_close(toastRel, lockmode);
}
- /* Get a modifiable copy of the relation's pg_class row */
- pg_class = heap_open(RelationRelationId, RowExclusiveLock);
-
- tuple = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(tableOid));
- if (!HeapTupleIsValid(tuple))
- elog(ERROR, "cache lookup failed for relation %u", tableOid);
- rd_rel = (Form_pg_class) GETSTRUCT(tuple);
-
/*
* Since we copy the file directly without looking at the shared buffers,
* we'd better first flush out any pages of the source relation that are
@@ -9634,18 +10083,11 @@ ATExecSetTableSpace(Oid tableOid, Oid newTableSpace, LOCKMODE lockmode)
RelationDropStorage(rel);
smgrclose(dstrel);
- /* update the pg_class row */
- rd_rel->reltablespace = (newTableSpace == MyDatabaseTableSpace) ? InvalidOid : newTableSpace;
- rd_rel->relfilenode = newrelfilenode;
- simple_heap_update(pg_class, &tuple->t_self, tuple);
- CatalogUpdateIndexes(pg_class, tuple);
+ /* Now update pg_class.reltablespace */
+ SetRelationSpcFileNode(rel, newTableSpace, newrelfilenode);
InvokeObjectPostAlterHook(RelationRelationId, RelationGetRelid(rel), 0);
- heap_freetuple(tuple);
-
- heap_close(pg_class, RowExclusiveLock);
-
relation_close(rel, NoLock);
/* Make sure the reltablespace change is visible */
@@ -9662,6 +10104,38 @@ ATExecSetTableSpace(Oid tableOid, Oid newTableSpace, LOCKMODE lockmode)
}
/*
+ * SetRelationTablespace - updates rel's reltablespace.
+ */
+static void
+SetRelationSpcFileNode(Relation rel, Oid newTableSpace, Oid newFileNode)
+{
+ Relation pg_class;
+ HeapTuple tuple;
+ Form_pg_class rd_rel;
+
+ /* Get a modifiable copy of the relation's pg_class row */
+ pg_class = heap_open(RelationRelationId, RowExclusiveLock);
+
+ tuple = SearchSysCacheCopy1(RELOID,
+ ObjectIdGetDatum(RelationGetRelid(rel)));
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "cache lookup failed for relation %u",
+ RelationGetRelid(rel));
+ rd_rel = (Form_pg_class) GETSTRUCT(tuple);
+
+ /* update the pg_class row */
+ rd_rel->reltablespace = (newTableSpace == MyDatabaseTableSpace)
+ ? InvalidOid : newTableSpace;
+ rd_rel->relfilenode = newFileNode;
+ simple_heap_update(pg_class, &tuple->t_self, tuple);
+ CatalogUpdateIndexes(pg_class, tuple);
+
+ heap_freetuple(tuple);
+ heap_close(pg_class, RowExclusiveLock);
+
+}
+
+/*
* Alter Table ALL ... SET TABLESPACE
*
* Allows a user to move all objects of some type in a given tablespace in the
@@ -9768,7 +10242,8 @@ AlterTableMoveAll(AlterTableMoveAllStmt *stmt)
/* Only move the object type requested */
if ((stmt->objtype == OBJECT_TABLE &&
- relForm->relkind != RELKIND_RELATION) ||
+ relForm->relkind != RELKIND_RELATION &&
+ relForm->relkind != RELKIND_PARTITIONED_REL) ||
(stmt->objtype == OBJECT_INDEX &&
relForm->relkind != RELKIND_INDEX) ||
(stmt->objtype == OBJECT_MATVIEW &&
@@ -9957,6 +10432,16 @@ ATPrepAddInherit(Relation child_rel)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot change inheritance of typed table")));
+
+ if (child_rel->rd_rel->relispartition)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot change inheritance of a partition of another table")));
+
+ if (child_rel->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot change inheritance of partitioned table")));
}
/*
@@ -10008,6 +10493,17 @@ ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode)
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot inherit to temporary relation of another session")));
+ /* special case partitioned tables and partitions */
+ if (parent_rel->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot inherit from partitioned table \"%s\"", parent->relname)));
+
+ if (parent_rel->rd_rel->relispartition)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot inherit from partition \"%s\"", parent->relname)));
+
/*
* Check for duplicates in the list of parents, and determine the highest
* inhseqno already present; we'll use the next one for the new parent.
@@ -10643,6 +11139,16 @@ ATExecAddOf(Relation rel, const TypeName *ofTypename, LOCKMODE lockmode)
typeobj;
HeapTuple classtuple;
+ if (rel->rd_rel->relispartition)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot attach a partition of another table to composite type")));
+
+ if (rel->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot attach a partitioned table to composite type")));
+
/* Validate the type. */
typetuple = typenameType(NULL, ofTypename, NULL);
check_of_type(typetuple);
@@ -11067,6 +11573,189 @@ ATExecDisableRowSecurity(Relation rel)
}
/*
+ * ATExecAttachPartition
+ *
+ * CREATE TABLE ... PARTITION OF <parent> FOR VALUES ...
+ */
+static void
+ATExecAttachPartition(Relation parent, PartitionDef *partdef)
+{
+ Relation child;
+ Datum *values;
+ int listnvalues;
+ PartitionBoundInfo *partition = NULL;
+ ObjectAddress childobject,
+ parentobject;
+
+ child = heap_openrv(partdef->name, AccessExclusiveLock);
+
+ /* Cannot attach a partition to a non-partitioned table. */
+ if (parent->rd_rel->relkind != RELKIND_PARTITIONED_REL)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot create as partition of non-partitioned table \"%s\"",
+ RelationGetRelationName(parent))));
+ /*
+ * Some persistence rules:
+ *
+ * 1. A permanent rel cannot be partition of a temporary one.
+ */
+ if (parent->rd_rel->relpersistence == RELPERSISTENCE_TEMP &&
+ child->rd_rel->relpersistence != RELPERSISTENCE_TEMP)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("cannot create as partition of temporary table \"%s\"",
+ RelationGetRelationName(parent))));
+
+ /* ... and if parent rel is temp, it must belong to this session. */
+ if (parent->rd_rel->relpersistence == RELPERSISTENCE_TEMP &&
+ !parent->rd_islocaltemp)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("cannot create as partition of a temporary table of another session")));
+
+ /* 2. A permanent rel cannot be partition of an unlogged one. */
+ if (parent->rd_rel->relpersistence == RELPERSISTENCE_UNLOGGED &&
+ child->rd_rel->relpersistence == RELPERSISTENCE_PERMANENT)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("cannot create as partition of unlogged table \"%s\"",
+ RelationGetRelationName(parent))));
+
+ /* 3. An unlogged rel cannot be partitin of a logged one. */
+ if (parent->rd_rel->relpersistence == RELPERSISTENCE_PERMANENT &&
+ child->rd_rel->relpersistence == RELPERSISTENCE_UNLOGGED)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("cannot create as partition of logged table \"%s\"",
+ RelationGetRelationName(parent))));
+
+ /*
+ * Now perform evaluation and initial validation of supplied
+ * partition values
+ */
+ partition = EvalPartitionBound(parent, partdef, &listnvalues);
+
+ /*
+ * A new partition should not have overlapping definition with
+ * an existing partition. If it does, error out.
+ */
+ CheckPartitionAgainstExisting(parent, partition, &values);
+
+ StorePartitionBounds(child, parent, listnvalues, values);
+ SetRelationIsPartition(child, true);
+
+ /* Tell the world that child is now a partition of parent. */
+ CacheInvalidateRelcache(parent);
+
+ /*
+ * Store a dependency too. This is to prevent parent from being
+ * dropped without taking me too.
+ */
+ parentobject.classId = RelationRelationId;
+ parentobject.objectId = RelationGetRelid(parent);
+ parentobject.objectSubId = 0;
+ childobject.classId = RelationRelationId;
+ childobject.objectId = RelationGetRelid(child);
+ childobject.objectSubId = 0;
+ recordDependencyOn(&childobject, &parentobject, DEPENDENCY_NORMAL);
+
+ heap_close(child, AccessExclusiveLock);
+}
+
+/*
+ * ATExecDetachPartition
+ *
+ * ALTER TABLE parent DETACH PARTITION partition;
+ */
+static void
+ATExecDetachPartition(Relation parent, PartitionDef *partition)
+{
+ Relation child;
+ Oid parentOid;
+ Oid childOid;
+
+ /* cannot detach from a non-partitioned table */
+ if (parent->rd_rel->relkind != RELKIND_PARTITIONED_REL)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot detach from non-partitioned relation \"%s\"",
+ RelationGetRelationName(parent))));
+
+ child = heap_openrv(partition->name, AccessExclusiveLock);
+
+ if (!child->rd_rel->relispartition)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot detach relation \"%s\"", RelationGetRelationName(child)),
+ errdetail("\"%s\" is not a partition.", RelationGetRelationName(child))));
+
+ childOid = RelationGetRelid(child);
+ parentOid = RelationGetRelid(parent);
+
+ if (get_partition_parent(childOid, false) != parentOid)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot detach relation \"%s\"", RelationGetRelationName(child)),
+ errdetail("\"%s\" is not a partition of \"%s\"",
+ RelationGetRelationName(child), RelationGetRelationName(parent))));
+
+ RemovePartitionBoundByRelId(childOid);
+ SetRelationIsPartition(child, false);
+ remove_partitioning_dependency(childOid, parentOid);
+
+ /* Tell the world that child is not a partition of parent anymore. */
+ CacheInvalidateRelcache(parent);
+
+
+ heap_close(child, AccessExclusiveLock);
+}
+
+/*
+ * remove_partition_dependency - removes dependency of parent on a partition
+ */
+static void
+remove_partitioning_dependency(Oid partitionOid, Oid parentOid)
+{
+ Relation catalogRelation;
+ SysScanDesc scan;
+ ScanKeyData key[3];
+ HeapTuple depTuple;
+
+ catalogRelation = heap_open(DependRelationId, RowExclusiveLock);
+
+ ScanKeyInit(&key[0],
+ Anum_pg_depend_classid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(RelationRelationId));
+ ScanKeyInit(&key[1],
+ Anum_pg_depend_objid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(partitionOid));
+ ScanKeyInit(&key[2],
+ Anum_pg_depend_objsubid,
+ BTEqualStrategyNumber, F_INT4EQ,
+ Int32GetDatum(0));
+
+ scan = systable_beginscan(catalogRelation, DependDependerIndexId, true,
+ NULL, 3, key);
+
+ while (HeapTupleIsValid(depTuple = systable_getnext(scan)))
+ {
+ Form_pg_depend dep = (Form_pg_depend) GETSTRUCT(depTuple);
+
+ if (dep->refclassid == RelationRelationId &&
+ dep->refobjid == parentOid &&
+ dep->refobjsubid == 0 &&
+ dep->deptype == DEPENDENCY_NORMAL)
+ simple_heap_delete(catalogRelation, &depTuple->t_self);
+ }
+
+ systable_endscan(scan);
+ heap_close(catalogRelation, RowExclusiveLock);
+}
+
+/*
* ALTER FOREIGN TABLE <name> OPTIONS (...)
*/
static void
@@ -11150,7 +11839,7 @@ ATExecGenericOptions(Relation rel, List *options)
* checks are skipped), otherwise true.
*/
static bool
-ATPrepChangePersistence(Relation rel, bool toLogged)
+ATPrepChangePersistence(Relation rel, bool toLogged, bool recursing)
{
Relation pg_constraint;
HeapTuple tuple;
@@ -11158,6 +11847,15 @@ ATPrepChangePersistence(Relation rel, bool toLogged)
ScanKeyData skey[1];
/*
+ * Cannot change persistence of partitions directly.
+ */
+ if (rel->rd_rel->relispartition && !recursing)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("cannot change persistence of a partition of another table"),
+ errhint("Perform SET LOGGED/UNLOGGED on the parent instead.")));
+
+ /*
* Disallow changing status for a temp table. Also verify whether we can
* get away with doing nothing; in such cases we don't need to run the
* checks below, either.
@@ -11260,6 +11958,57 @@ ATPrepChangePersistence(Relation rel, bool toLogged)
}
/*
+ * ATExecChangePersistence - change pg_class.relpersistence of rel
+ *
+ * RELKIND_PARTITIONED_REL relations have no storage, so performing
+ * persistence change in ATRewriteTables step is not straightforward.
+ * Simply changing the relpersistence should suffice (?).
+ */
+static void
+ATExecChangePersistence(Relation rel, char persistence)
+{
+ if (rel->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+ SetRelationPersistence(RelationGetRelid(rel), persistence);
+}
+
+/*
+ * SetRelationPersistence - set relpersistence
+ */
+static void
+SetRelationPersistence(Oid relationId, char persistence)
+{
+ Relation relationRelation;
+ HeapTuple tuple;
+ Form_pg_class classtuple;
+
+ /*
+ * Fetch a modifiable copy of the tuple, modify it, update pg_class.
+ */
+ relationRelation = heap_open(RelationRelationId, RowExclusiveLock);
+ tuple = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(relationId));
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "cache lookup failed for relation %u", relationId);
+ classtuple = (Form_pg_class) GETSTRUCT(tuple);
+
+ if (classtuple->relpersistence != persistence)
+ {
+ classtuple->relpersistence = persistence;
+ simple_heap_update(relationRelation, &tuple->t_self, tuple);
+
+ /* keep the catalog indexes up to date */
+ CatalogUpdateIndexes(relationRelation, tuple);
+ }
+ else
+ {
+ /* no need to change tuple, but force relcache rebuild anyway */
+ CacheInvalidateRelcacheByTuple(tuple);
+ }
+
+ heap_freetuple(tuple);
+ heap_close(relationRelation, RowExclusiveLock);
+}
+
+/*
* Execute ALTER TABLE SET SCHEMA
*/
ObjectAddress
@@ -11352,6 +12101,7 @@ AlterTableNamespaceInternal(Relation rel, Oid oldNspOid, Oid nspOid,
/* Fix other dependent stuff */
if (rel->rd_rel->relkind == RELKIND_RELATION ||
+ rel->rd_rel->relkind == RELKIND_PARTITIONED_REL ||
rel->rd_rel->relkind == RELKIND_MATVIEW)
{
AlterIndexNamespaces(classRel, rel, oldNspOid, nspOid, objsMoved);
@@ -11794,8 +12544,8 @@ RangeVarCallbackOwnsTable(const RangeVar *relation,
relkind = get_rel_relkind(relId);
if (!relkind)
return;
- if (relkind != RELKIND_RELATION && relkind != RELKIND_TOASTVALUE &&
- relkind != RELKIND_MATVIEW)
+ if (relkind != RELKIND_RELATION && relkind != RELKIND_PARTITIONED_REL &&
+ relkind != RELKIND_TOASTVALUE && relkind != RELKIND_MATVIEW)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not a table or materialized view", relation->relname)));
@@ -11949,6 +12699,7 @@ RangeVarCallbackForAlterRelation(const RangeVar *rv, Oid relid, Oid oldrelid,
*/
if (IsA(stmt, AlterObjectSchemaStmt) &&
relkind != RELKIND_RELATION &&
+ relkind != RELKIND_PARTITIONED_REL &&
relkind != RELKIND_VIEW &&
relkind != RELKIND_MATVIEW &&
relkind != RELKIND_SEQUENCE &&
@@ -11960,3 +12711,844 @@ RangeVarCallbackForAlterRelation(const RangeVar *rv, Oid relid, Oid oldrelid,
ReleaseSysCache(tuple);
}
+
+/*
+ * transformPartitionBy
+ *
+ * Transform any expressions present in the partition key.
+ */
+static PartitionBy *
+transformPartitionBy(Relation rel, PartitionBy *partitionby)
+{
+ ParseState *pstate = NULL;
+ RangeTblEntry *rte;
+ ListCell *l;
+ PartitionBy *partby = NULL;
+
+ partby = (PartitionBy *) makeNode(PartitionBy);
+
+ partby->strategy = partitionby->strategy;
+ partby->location = partitionby->location;
+ partby->partParams = NIL;
+
+ /*
+ * Create a dummy ParseState and insert the target relation as its sole
+ * rangetable entry. We need a ParseState for transformExpr.
+ */
+ pstate = make_parsestate(NULL);
+ rte = addRangeTableEntryForRelation(pstate,
+ rel,
+ NULL,
+ false,
+ true);
+ addRTEtoQuery(pstate, rte, false, true, true);
+
+ /* take care of any partition expressions */
+ foreach(l, partitionby->partParams)
+ {
+ ListCell *column;
+ PartitionElem *pelem = (PartitionElem *) lfirst(l);
+
+ /* Check for PARTITION BY ... ON (foo, foo) */
+ foreach(column, partby->partParams)
+ {
+ PartitionElem *pparam = (PartitionElem *) lfirst(column);
+ if (pparam->name && strcmp(pelem->name, pparam->name) == 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_COLUMN),
+ errmsg("column \"%s\" appears twice in partition key", pelem->name),
+ parser_errposition(pstate, pelem->location)));
+ }
+
+ if (pelem->expr)
+ {
+ /* Now do parse transformation of the expression */
+ pelem->expr = transformExpr(pstate, pelem->expr,
+ EXPR_KIND_PARTKEY_EXPRESSION);
+
+ /* we have to fix its collations too */
+ assign_expr_collations(pstate, pelem->expr);
+
+ /*
+ * transformExpr() should have already rejected subqueries,
+ * aggregates, and window functions, based on the EXPR_KIND_ for
+ * a partition key expression.
+ *
+ * Also reject expressions returning sets; this is for consistency
+ * DefineRelation() will make more checks.
+ */
+ if (expression_returns_set(pelem->expr))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("partition key expression cannot return a set"),
+ parser_errposition(pstate, pelem->location)));
+ }
+
+ partby->partParams = lappend(partby->partParams, pelem);
+ }
+
+ return partby;
+}
+
+/*
+ * ComputePartitionAttrs
+ *
+ * Compute per-partition-column information viz. partition column
+ * attribute numbers, expression, opclasses, etc.
+ */
+static PartitionKeyInfo *
+ComputePartitionAttrs(PartitionKeyInfo *pkinfo, Oid *partOpClassOids,
+ Oid relid, List *partParams)
+{
+ int attn;
+ ListCell *lc;
+
+ /* Initialize. */
+ pkinfo = makeNode(PartitionKeyInfo);
+
+ /* Process partColumns list */
+ attn = 0;
+ foreach(lc, partParams)
+ {
+ PartitionElem *pelem = (PartitionElem *) lfirst(lc);
+ Oid atttype;
+
+ if (pelem->name != NULL)
+ {
+ HeapTuple atttuple;
+ Form_pg_attribute attform;
+
+ atttuple = SearchSysCacheAttName(relid, pelem->name);
+ if (!HeapTupleIsValid(atttuple))
+ {
+ /* difference in error message spellings is historical */
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_COLUMN),
+ errmsg("column \"%s\" named in partition key does not exist",
+ pelem->name)));
+ }
+ attform = (Form_pg_attribute) GETSTRUCT(atttuple);
+
+ if (attform->attnum <= 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_COLUMN),
+ errmsg("cannot use system column \"%s\" in partition key",
+ pelem->name)));
+
+ pkinfo->pi_KeyAttrNumbers[attn] = attform->attnum;
+ atttype = attform->atttypid;
+ ReleaseSysCache(atttuple);
+ }
+ else
+ {
+ /* Partition key expression */
+ Node *expr = pelem->expr;
+
+ Assert(expr != NULL);
+ atttype = exprType(expr);
+
+ if (IsA(expr, CollateExpr))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("cannot use COLLATE in partition key expression")));
+
+ if (IsA(expr, Var) &&
+ ((Var *) expr)->varattno != InvalidAttrNumber)
+ {
+ /*
+ * User wrote "(column)" or "(column COLLATE something)".
+ * Treat it like simple attribute anyway.
+ */
+ pkinfo->pi_KeyAttrNumbers[attn] = ((Var *) expr)->varattno;
+ }
+ else
+ {
+ pkinfo->pi_KeyAttrNumbers[attn] = 0; /* marks expression */
+ pkinfo->pi_Expressions = lappend(pkinfo->pi_Expressions, expr);
+
+ /*
+ * transformExpr() should have already rejected subqueries,
+ * aggregates, and window functions, based on the EXPR_KIND_
+ * for a partition key expression.
+ */
+
+ /*
+ * An expression using mutable functions is probably wrong even
+ * even to use in a partition key
+ */
+ expr = (Node *) expression_planner((Expr *) expr);
+
+ if (IsA(expr, Const))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("cannot use a constant expression as partition key")));
+
+ if (contain_mutable_functions(expr))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("functions in partition key expression must be marked IMMUTABLE")));
+ }
+ }
+
+ /*
+ * Identify the opclass to use. At the moment, we use "btree" operators
+ * that seems enough for list and range partitioning.
+ * XXX - To be truly extensible, we should provide means of specifying
+ * which AM to use (along with the opclass).
+ */
+ partOpClassOids[attn++] = GetIndexOpClass(pelem->opclass,
+ atttype,
+ "btree",
+ BTREE_AM_OID);
+ }
+
+ pkinfo->pi_NumKeyAttrs = attn;
+ return pkinfo;
+}
+
+/*
+ * EvalPartitionBound - validate and evalulate partition bounds specified
+ * in FOR VALUES clause of a partition definition.
+ */
+static PartitionBoundInfo *
+EvalPartitionBound(Relation parent, PartitionDef *partdef, int *listnvalues)
+{
+ int rangenmins, rangenmaxs;
+ char partstrategy;
+ int partnatts;
+ FmgrInfo *partattcmpfn;
+ int32 cmpval;
+ PartitionValues *values;
+ PartitionBoundInfo *result;
+ PartitionKeyTypeInfo *typinfo = get_key_type_info(parent);
+
+ partstrategy = parent->rd_pkey->partstrategy;
+ partnatts = parent->rd_pkey->partnatts;
+ partattcmpfn = parent->rd_partsupfuncs;
+
+ result = (PartitionBoundInfo *) palloc0(sizeof(PartitionBoundInfo));
+ result->oid = InvalidOid; /* as yet, let's assume */
+ result->partnatts = partnatts;
+
+ /* Transform and evaluate value expressions for this partition */
+ values = partdef->values;
+ switch (partstrategy)
+ {
+ case PARTITION_STRAT_LIST:
+ /* CREATE TABLE ... PARTITION BY LIST ON ... enforces */
+ Assert(partnatts == 1);
+
+ if (!values->listvalues)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("invalid FOR VALUES specification for a list partition")));
+
+ result->listnvalues = *listnvalues = list_length(values->listvalues);
+
+ Assert (result->listnvalues >= 1); /* Grammar enforces. */
+
+ /*
+ * Currently only one column is supported here.
+ *
+ * datum[0] - list of allowed values for the only key column
+ */
+
+ result->listvalues = evalPartitionListBound(values->listvalues,
+ typinfo, result->listnvalues);
+
+ /* TODO: uniqueify(result->listvalues) */
+ break;
+
+ case PARTITION_STRAT_RANGE:
+ if (!values->rangemins && !values->rangemaxs)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("invalid FOR VALUES specification for a range partition")));
+
+ rangenmins = list_length(values->rangemins);
+ rangenmaxs = list_length(values->rangemaxs);
+
+ if ((values->rangemins && rangenmins != partnatts) ||
+ (values->rangemaxs && rangenmaxs != partnatts))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("range partition FOR VALUES specification does not match the partition key")));
+
+ result->rangeminnull = (values->rangemins == NIL);
+ result->rangemaxnull = (values->rangemaxs == NIL);
+
+ result->rangemins = evalPartitionRangeBound(values->rangemins, typinfo, partnatts);
+ result->rangemaxs = evalPartitionRangeBound(values->rangemaxs, typinfo, partnatts);
+
+ /*
+ * Safeguard against empty partition definitions
+ */
+ if (result->rangemins && result->rangemaxs)
+ {
+ cmpval = range_partition_cmp_bounds(result->rangemins,
+ result->rangemaxs,
+ partnatts, partattcmpfn);
+ if (cmpval >= 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("cannot define partition with an empty range")));
+ }
+ break;
+ }
+
+ pfree(typinfo);
+ return result;
+}
+
+/*
+ * evalPartitionListBound - evaluate a list of expressions to build a datum
+ * array corresponding to a list partition bound.
+ */
+static Datum *
+evalPartitionListBound(List *values, PartitionKeyTypeInfo *typinfo, int nvalues)
+{
+ ListCell *cell;
+ ParseState *pstate;
+ EState *estate;
+ ExprContext *ecxt;
+ int i;
+ Datum *datum;
+
+ if (!values)
+ return NULL;
+
+ datum = (Datum *) palloc(nvalues * sizeof(Datum));
+ pstate = make_parsestate(NULL);
+ estate = CreateExecutorState();
+ ecxt = GetPerTupleExprContext(estate);
+
+ i = 0;
+ foreach(cell, values)
+ {
+ Node *value = (Node *) lfirst(cell);
+ bool isnull;
+ ExprState *expr;
+ MemoryContext oldcxt;
+ Oid valuetype;
+
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
+
+ valuetype = exprType(value);
+ value = coerce_to_target_type(NULL,
+ value, valuetype,
+ typinfo->typid[0],
+ typinfo->typmod[0],
+ COERCION_ASSIGNMENT,
+ COERCE_IMPLICIT_CAST,
+ -1);
+ if (value == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("specified values in FOR VALUES do not match the partition key"),
+ errhint("You will need to rewrite or cast the expression.")));
+
+ expr = ExecPrepareExpr((Expr *) value, estate);
+
+ datum[i] = ExecEvalExpr(expr, ecxt, &isnull, NULL);
+ if (isnull)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("a partition bound value must not be NULL")));
+
+ MemoryContextSwitchTo(oldcxt);
+
+ if (!typinfo->typbyval[0])
+ {
+ if (typinfo->typlen[0] == -1)
+ datum[i] = PointerGetDatum(PG_DETOAST_DATUM_COPY(datum[i]));
+ else
+ datum[i] = datumCopy(datum[i], false, typinfo->typlen[0]);
+ }
+
+ ResetPerTupleExprContext(estate);
+ i++;
+ }
+
+ return datum;
+}
+
+/*
+ * evalPartitionRangeBound - evaluate a list of expressions to build a datum
+ * array corresponding to a range partition bound.
+ */
+static Datum *
+evalPartitionRangeBound(List *values, PartitionKeyTypeInfo *typinfo, int partnatts)
+{
+ ListCell *cell;
+ ParseState *pstate;
+ EState *estate;
+ ExprContext *ecxt;
+ int i;
+ Datum *datum;
+
+ if (!values)
+ return NULL;
+
+ datum = (Datum *) palloc(partnatts * sizeof(Datum));
+ pstate = make_parsestate(NULL);
+ estate = CreateExecutorState();
+ ecxt = GetPerTupleExprContext(estate);
+
+ i = 0;
+ foreach(cell, values)
+ {
+ Node *value = (Node *) lfirst(cell);
+ bool isnull;
+ ExprState *expr;
+ MemoryContext oldcxt;
+ Oid valuetype;
+
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
+
+ valuetype = exprType(value);
+
+ value = coerce_to_target_type(NULL,
+ value, valuetype,
+ typinfo->typid[i],
+ typinfo->typmod[i],
+ COERCION_ASSIGNMENT,
+ COERCE_IMPLICIT_CAST,
+ -1);
+ if (value == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("specified values in FOR VALUES do not match the partition key"),
+ errhint("You will need to rewrite or cast the expression.")));
+
+ expr = ExecPrepareExpr((Expr *) value, estate);
+
+ datum[i] = ExecEvalExpr(expr, ecxt, &isnull, NULL);
+ if (isnull)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("a partition value must not be NULL")));
+
+ MemoryContextSwitchTo(oldcxt);
+
+ if (!typinfo->typbyval[i])
+ {
+ if (typinfo->typlen[i] == -1)
+ datum[i] = PointerGetDatum(PG_DETOAST_DATUM_COPY(datum[i]));
+ else
+ datum[i] = datumCopy(datum[i], false, typinfo->typlen[i]);
+ }
+
+ ResetPerTupleExprContext(estate);
+ i++;
+ }
+
+ return datum;
+}
+
+/* typinfo->typbyval[], typinfo->typlen[] better be in place */
+#define COPY_RANGE_PARTITION_BOUND(dest, src, natts) \
+do { \
+ int j; \
+ (dest) = (Datum *) palloc0((natts) * sizeof(Datum)); \
+ for (j = 0; j < (natts); j++) \
+ { \
+ (dest)[j] = typinfo->typbyval[j] \
+ ? (src)[j] \
+ : ((typinfo->typlen[j] == -1) \
+ ? PointerGetDatum(PG_DETOAST_DATUM_COPY((src)[j])) \
+ : datumCopy((src)[j], false, typinfo->typlen[j])); \
+ } \
+} while(0);
+
+/*
+ * CheckPartitionAgainstExisting
+ *
+ * Does the new partition overlap with an existing partition of parent?
+ *
+ * Also if either new->rangemins or new->rangemaxs are omitted, we
+ * substitute the missing bound by locating a proper adjacent partition
+ * and use one of its bounds.
+ */
+static void
+CheckPartitionAgainstExisting(Relation parent,
+ PartitionBoundInfo *new,
+ Datum **final)
+{
+ int numparts;
+
+ char strategy = parent->rd_pkey->partstrategy;
+ int partnatts = parent->rd_pkey->partnatts;
+ FmgrInfo *partattcmpfn = parent->rd_partsupfuncs;
+ PartitionBoundInfo **existing = GetPartitionBounds(parent, &numparts);
+ PartitionBoundInfo **newlist = NULL;
+
+ /* Type info for partition key attributes */
+ PartitionKeyTypeInfo *typinfo = get_key_type_info(parent);
+
+ Datum *newbound;
+ int i, newpos;
+ int32 cmpval;
+ bool overlaps = false;
+ Oid overlapsWith = InvalidOid;
+
+ /* Trivial when new is the first partition of parent. */
+ if (!existing)
+ goto cleanup;
+
+ /* Pretty trivial. */
+ if (strategy == PARTITION_STRAT_LIST)
+ {
+ Assert(new->listvalues);
+
+ for (i = 0; i < numparts; i++)
+ {
+ if (list_partition_overlaps(new, existing[i], partattcmpfn))
+ {
+ overlaps = true;
+ overlapsWith = existing[i]->oid;
+ goto cleanup;
+ }
+ }
+
+ /* No existing partition had an overlapping list of values. */
+ goto cleanup;
+ }
+
+ /* Range partitioning case is bit more complicated. */
+ newlist = (PartitionBoundInfo **) repalloc(existing, (numparts + 1)
+ * sizeof(PartitionBoundInfo *));
+ newlist[numparts] = new;
+
+ /*
+ * Sorting the newlist would put the new partition into its would-be
+ * position. Code below then checks if being in that positions causes
+ * it to overlap with an adjacent partition. If it does, complain!
+ *
+ * Existing partitions are known ordered such that:
+ *
+ * (i-1).min < (i-1).max <= (i).min < (i).max <= (i+1).min < (i+1).max
+ *
+ * Position assigned here depends on the ascending order of either
+ * max or min. Code below makes sure that the remaining bound doesn't
+ * cross a bound to cause an overlap. In other words, based on whether
+ * min or max bound was used for ordering partitions above, the following
+ * is implied: new.min <= right.min and new.max >= left.max, respectively.
+ *
+ * Note: qsort_arg()'s range partition ordering callback functions are
+ * written such that if new.max == some_existing.max, new is ordered
+ * before the existing. Reverse for the new.min == some_existing.min case.
+ * It uses the fact that new.oid = InvalidOid to make that call. That is
+ * is simply to make qsort's ordering result look less than arbitrary for
+ * such cases. That behavior determines the result of some overlap tests
+ * below.
+ */
+ if (!new->rangemaxnull)
+ qsort_arg(newlist, numparts+1, sizeof(PartitionBoundInfo *),
+ range_partition_cmp_max,
+ partattcmpfn);
+ else
+ qsort_arg(newlist, numparts+1, sizeof(PartitionBoundInfo *),
+ range_partition_cmp_min,
+ partattcmpfn);
+ for (i = 0; i < numparts+1; i++)
+ {
+ if (newlist[i]->oid == InvalidOid)
+ {
+ newpos = i;
+ break;
+ }
+ }
+
+ /*
+ * Compare new with adjacents for overlap; new overlaps if:
+ *
+ * On left: new.max == left.max || new.min < left.max
+ * On right: new.min == right.min || new.max > right.min
+ */
+
+ /*
+ * newbound is a shorthand for which of new's bounds is used in overlap
+ * tests below. It is new->rangemaxs unless the new partition is defined
+ * to have null rangemax.
+ */
+ newbound = !new->rangemaxnull ? new->rangemaxs : new->rangemins;
+
+ /*
+ * Cases (new.maxnull AND left.maxnull) and (new.minnull AND right.minnull)
+ * can be dealt with quickly. We allow only one leftmost partition with
+ * NULL min and only one rightmost partition with NULL max.
+ *
+ * Case 1: It's possible that the new partition of definition [min, NULL)
+ * overlaps with the rightmost partition with NULL max bound.
+ */
+ if (new->rangemaxnull
+ && (newpos > 0) && newlist[newpos-1]->rangemaxnull)
+ {
+ overlaps = true;
+ overlapsWith = newlist[newpos-1]->oid;
+ goto cleanup;
+ }
+
+ /*
+ * Case 2: It's possible that the new partition of definition (NULL, max]
+ * overlaps with the leftmost partition with NULL min bound.
+ */
+ if (new->rangeminnull
+ && (newpos <= (numparts-1)) && newlist[newpos+1]->rangeminnull)
+ {
+ overlaps = true;
+ overlapsWith = newlist[newpos+1]->oid;
+ goto cleanup;
+ }
+
+ /*
+ * Overlap the left adjacent partition?
+ * If new.minnull, we might be able to find a value for it here.
+ */
+ if (newpos > 0 )
+ {
+ cmpval = range_partition_cmp_bounds(newbound,
+ newlist[newpos-1]->rangemaxs,
+ partnatts, partattcmpfn);
+ /* new.min/max == left.max */
+ if (!cmpval && !new->rangemaxnull)
+ {
+ overlaps = true;
+ overlapsWith = newlist[newpos-1]->oid;
+ goto cleanup;
+ }
+
+ /* new.min < left.max */
+ if (cmpval < 0)
+ {
+ overlaps = true;
+ overlapsWith = newlist[newpos-1]->oid;
+ goto cleanup;
+ }
+
+ /* new.min/max > left.max */
+ if (new->rangeminnull)
+ {
+ /*
+ * If there is room between newpos-1 and newpos+1, acquire
+ * (newpos-1)'s rangemaxs as rangemins for the new partition.
+ */
+ cmpval = (newpos < numparts) ?
+ range_partition_cmp_bounds(newlist[newpos-1]->rangemaxs,
+ newlist[newpos+1]->rangemins,
+ partnatts, partattcmpfn)
+ /* new is placed last. */
+ : range_partition_cmp_bounds(newbound,
+ newlist[newpos-1]->rangemaxs,
+ partnatts, partattcmpfn);
+ if (!cmpval)
+ {
+ /* There is no room at all to fill. */
+ overlaps = true;
+ overlapsWith = newlist[newpos+1]->oid;
+ goto cleanup;
+ }
+ else
+ {
+ /*
+ * new.max > left.max OR left.max < right.min.
+ *
+ * So, we can use left.max as new.min unless new.max happens
+ * to overlap with right which we check later.
+ */
+ COPY_RANGE_PARTITION_BOUND(new->rangemins,
+ newlist[newpos-1]->rangemaxs,
+ partnatts);
+ new->rangeminnull = false;
+ }
+ }
+ else
+ {
+ /*
+ * Better make sure new->min doesn't enter (newpos-1)'s range.
+ */
+ cmpval = range_partition_cmp_bounds(newlist[newpos]->rangemins,
+ newlist[newpos-1]->rangemaxs,
+ partnatts, partattcmpfn);
+
+ if (cmpval < 0)
+ {
+ overlaps = true;
+ overlapsWith = newlist[newpos-1]->oid;
+ goto cleanup;
+ }
+ }
+ }
+
+ /*
+ * Overlap the right adjacent partition?
+ * If new.maxnull, we might be able to find a value for it here.
+ */
+ if (newpos < numparts)
+ {
+ cmpval = range_partition_cmp_bounds(newbound,
+ newlist[newpos+1]->rangemins,
+ partnatts, partattcmpfn);
+
+ /* new.min/max == right.min */
+ if (!cmpval && new->rangemaxnull)
+ {
+ overlaps = true;
+ overlapsWith = newlist[newpos+1]->oid;
+ goto cleanup;
+ }
+
+ /* new.max > right.min */
+ if (cmpval > 0)
+ {
+ overlaps = true;
+ overlapsWith = newlist[newpos+1]->oid;
+ goto cleanup;
+ }
+
+ /* new.min/max < right.min */
+ if (new->rangemaxnull)
+ {
+ /*
+ * If there is room between newpos-1 and newpos+1, acquire
+ * (newpos+1)'s rangemins as rangemaxs for the new partition.
+ */
+ cmpval = (newpos > 0) ?
+ range_partition_cmp_bounds(newlist[newpos-1]->rangemaxs,
+ newlist[newpos+1]->rangemins,
+ partnatts, partattcmpfn)
+ /* new is placed first. */
+ : range_partition_cmp_bounds(newbound,
+ newlist[newpos+1]->rangemins,
+ partnatts, partattcmpfn);
+ if (!cmpval)
+ {
+ /* There is no room at all to fill. */
+ overlaps = true;
+ overlapsWith = newlist[newpos-1]->oid;
+ goto cleanup;
+ }
+ else
+ {
+ /*
+ * new.min < right.min OR left.max < right.min.
+ *
+ * So, we can use right.max as new.max unless new.min happens
+ * to overlap with left which is something we ruled out above.
+ */
+ COPY_RANGE_PARTITION_BOUND(new->rangemaxs,
+ newlist[newpos+1]->rangemins,
+ partnatts);
+ new->rangemaxnull = false;
+ }
+ }
+ else
+ {
+ /*
+ * Better make sure new->max doesn't enter (newpos+1)'s range.
+ */
+ cmpval = range_partition_cmp_bounds(newlist[newpos]->rangemaxs,
+ newlist[newpos+1]->rangemins,
+ partnatts, partattcmpfn);
+ if (cmpval > 0)
+ {
+ overlaps = true;
+ overlapsWith = newlist[newpos+1]->oid;
+ goto cleanup;
+ }
+ }
+ }
+
+cleanup:
+ if (existing)
+ {
+ if (newlist)
+ free_partitions(newlist, numparts+1);
+ else
+ free_partitions(existing, numparts);
+ }
+
+ if (overlaps)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("cannot create partition that overlaps with an existing one"),
+ errdetail("New partition's definition overlaps with existing partition \"%s\" of \"%s\".",
+ get_rel_name(overlapsWith), RelationGetRelationName(parent))));
+
+ /*
+ * Now construct the datum array that will be stored into pg_partition.
+ */
+ switch (strategy)
+ {
+ case PARTITION_STRAT_LIST:
+ *final = (Datum *) palloc0(new->listnvalues * sizeof(Datum));
+
+ for (i = 0; i < new->listnvalues; i++)
+ (*final)[i] = new->listvalues[i];
+
+ pfree(new->listvalues);
+ break;
+
+ case PARTITION_STRAT_RANGE:
+ *final = (Datum *) palloc0(partnatts * sizeof(Datum));
+ for (i = 0; i < partnatts; i++)
+ {
+ Datum datums[2];
+ bool nulls[2];
+ int dims[1];
+ int lbs[1];
+ ArrayType *rangebounds;
+
+ if (new->rangeminnull)
+ nulls[0] = true;
+ else
+ {
+ datums[0] = new->rangemins[i];
+ nulls[0] = false;
+ }
+
+ if (new->rangemaxnull)
+ nulls[1] = true;
+ else
+ {
+ datums[1] = new->rangemaxs[i];
+ nulls[1] = false;
+ }
+
+ dims[0] = 2;
+ lbs[0] = 1;
+
+ rangebounds = construct_md_array(datums, nulls, 1, dims, lbs,
+ typinfo->typid[i], typinfo->typlen[i],
+ typinfo->typbyval[i], typinfo->typalign[i]);
+
+ (*final)[i] = PointerGetDatum(rangebounds);
+ }
+
+ if (new->rangemins)
+ pfree(new->rangemins);
+ if (new->rangemaxs)
+ pfree(new->rangemaxs);
+
+ break;
+ }
+
+ pfree(new);
+ pfree(typinfo);
+}
+
+/*
+ * RangeVarGetTablespace - returns relation's tablespace OID.
+ */
+static Oid
+RangeVarGetTablespace(RangeVar *relation)
+{
+ Relation rel;
+ Oid tablespaceId;
+
+ rel = heap_openrv(relation, NoLock);
+ tablespaceId = rel->rd_rel->reltablespace;
+ heap_close(rel, NoLock);
+
+ return tablespaceId;
+}
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index d169027..cac8934 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -169,11 +169,17 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
else
rel = heap_openrv(stmt->relation, ShareRowExclusiveLock);
+ if (rel->rd_rel->relispartition)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot define triggers on a partition of another table"),
+ errhint("Perform CREATE TRIGGER on the parent instead.")));
/*
* Triggers must be on tables or views, and there are additional
* relation-type-specific restrictions.
*/
- if (rel->rd_rel->relkind == RELKIND_RELATION)
+ if (rel->rd_rel->relkind == RELKIND_RELATION ||
+ rel->rd_rel->relkind == RELKIND_PARTITIONED_REL)
{
/* Tables can't have INSTEAD OF triggers */
if (stmt->timing != TRIGGER_TYPE_BEFORE &&
@@ -183,6 +189,13 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
errmsg("\"%s\" is a table",
RelationGetRelationName(rel)),
errdetail("Tables cannot have INSTEAD OF triggers.")));
+
+ /* Disallow row-level AFTER triggers on partitioned tables, *yet*. */
+ if (rel->rd_rel->relkind == RELKIND_PARTITIONED_REL &&
+ stmt->timing == TRIGGER_TYPE_AFTER && stmt->row)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("Row-level AFTER triggers are not supported on partitioned tables")));
}
else if (rel->rd_rel->relkind == RELKIND_VIEW)
{
@@ -1113,6 +1126,7 @@ RemoveTriggerById(Oid trigOid)
rel = heap_open(relid, AccessExclusiveLock);
if (rel->rd_rel->relkind != RELKIND_RELATION &&
+ rel->rd_rel->relkind != RELKIND_PARTITIONED_REL &&
rel->rd_rel->relkind != RELKIND_VIEW &&
rel->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
ereport(ERROR,
@@ -1218,7 +1232,9 @@ RangeVarCallbackForRenameTrigger(const RangeVar *rv, Oid relid, Oid oldrelid,
form = (Form_pg_class) GETSTRUCT(tuple);
/* only tables and views can have triggers */
- if (form->relkind != RELKIND_RELATION && form->relkind != RELKIND_VIEW &&
+ if (form->relkind != RELKIND_RELATION &&
+ form->relkind != RELKIND_PARTITIONED_REL &&
+ form->relkind != RELKIND_VIEW &&
form->relkind != RELKIND_FOREIGN_TABLE)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c
index de91353..6478a97 100644
--- a/src/backend/commands/typecmds.c
+++ b/src/backend/commands/typecmds.c
@@ -2900,6 +2900,7 @@ get_rels_with_domain(Oid domainOid, LOCKMODE lockmode)
* index columns.
*/
if (rel->rd_rel->relkind != RELKIND_RELATION &&
+ rel->rd_rel->relkind != RELKIND_PARTITIONED_REL &&
rel->rd_rel->relkind != RELKIND_MATVIEW)
{
relation_close(rel, lockmode);
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 85b0483..6f71ffb 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -1292,6 +1292,21 @@ vacuum_rel(Oid relid, RangeVar *relation, int options, VacuumParams *params)
}
/*
+ * XXX - Vacuuming a partitioned table not supported yet
+ */
+ if (onerel->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+ {
+ if(!IsAutoVacuumWorkerProcess())
+ ereport(ERROR,
+ (errmsg("cannot vacuum partitioned table \"%s\"", RelationGetRelationName(onerel)),
+ errhint("Run VACUUM on individual partitions one-by-one.")));
+ else
+ ereport(WARNING,
+ (errmsg("skipping \"%s\" --- cannot vacuum partitioned tables",
+ RelationGetRelationName(onerel))));
+ }
+
+ /*
* Check that it's a vacuumable relation; we used to do this in
* get_rel_oids() but seems safer to check after we've locked the
* relation.
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 2c65a90..3973c4d 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -56,6 +56,7 @@
#include "utils/acl.h"
#include "utils/lsyscache.h"
#include "utils/memutils.h"
+#include "utils/partition.h"
#include "utils/rls.h"
#include "utils/snapmgr.h"
#include "utils/tqual.h"
@@ -1017,6 +1018,36 @@ CheckValidResultRel(Relation resultRel, CmdType operation)
switch (resultRel->rd_rel->relkind)
{
case RELKIND_RELATION:
+ /*
+ * prevent direct modifications on partitions; we have no way
+ * of ensuring at partition level that paritioning rules allow
+ * the result row to be in resultRel at all
+ */
+ if (resultRel->rd_rel->relispartition)
+ {
+ switch (operation)
+ {
+ case CMD_INSERT:
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot insert into a partition of another table",
+ RelationGetRelationName(resultRel)),
+ errhint("Perform INSERT on the parent instead.")));
+ break;
+ case CMD_UPDATE:
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot update a partition of another table",
+ RelationGetRelationName(resultRel)),
+ errhint("Perform UPDATE on the parent instead.")));
+ break;
+ default:
+ /* CMD_DELETE doesn't hurt */
+ break;
+ }
+ }
+ break;
+ case RELKIND_PARTITIONED_REL:
/* OK */
break;
case RELKIND_SEQUENCE:
@@ -1152,6 +1183,9 @@ CheckValidRowMarkRel(Relation rel, RowMarkType markType)
case RELKIND_RELATION:
/* OK */
break;
+ case RELKIND_PARTITIONED_REL:
+ /* OK? should never be? */
+ break;
case RELKIND_SEQUENCE:
/* Must disallow this because we don't vacuum sequences */
ereport(ERROR,
@@ -1246,6 +1280,11 @@ InitResultRelInfo(ResultRelInfo *resultRelInfo,
resultRelInfo->ri_ConstraintExprs = NULL;
resultRelInfo->ri_junkFilter = NULL;
resultRelInfo->ri_projectReturning = NULL;
+ if (resultRelationDesc->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+ {
+ resultRelInfo->ri_PartitionKeyInfo = BuildPartitionKeyInfo(resultRelationDesc);
+ resultRelInfo->ri_Partitions = RelationGetPartitionInfo(resultRelationDesc);
+ }
}
/*
@@ -2880,3 +2919,62 @@ EvalPlanQualEnd(EPQState *epqstate)
epqstate->planstate = NULL;
epqstate->origslot = NULL;
}
+
+/*
+ * ExecFindPartition - determine the partition for tuple contained in slot
+ *
+ * Based on resultRelInfo->ri_Partitions, find_partition_for_tuple() determines
+ * oid of the target partition. If it turns out to be InvalidOid, complain that
+ * the tuple cannot be inserted in this relation.
+ */
+ResultRelInfo *
+ExecFindPartition(ResultRelInfo *resultRelInfo, TupleTableSlot *slot,
+ EState *estate)
+{
+ Oid partOid;
+ Relation parentRelDesc = resultRelInfo->ri_RelationDesc;
+ Relation partRelDesc;
+ ResultRelInfo *partition;
+
+ partOid = find_partition_for_tuple(parentRelDesc,
+ resultRelInfo->ri_PartitionKeyInfo,
+ resultRelInfo->ri_Partitions,
+ slot,
+ estate);
+
+ if (partOid != InvalidOid)
+ {
+ partRelDesc = heap_open(partOid, RowExclusiveLock);
+ partition = makeNode(ResultRelInfo);
+ InitResultRelInfo(partition,
+ partRelDesc,
+ 1, /* dummy */
+ 0);
+
+ ExecOpenIndices(partition, false);
+ }
+ else
+ {
+ char *val_desc;
+ Bitmapset *modifiedCols;
+ Bitmapset *insertedCols;
+ Bitmapset *updatedCols;
+
+ insertedCols = GetInsertedColumns(resultRelInfo, estate);
+ updatedCols = GetUpdatedColumns(resultRelInfo, estate);
+ modifiedCols = bms_union(insertedCols, updatedCols);
+ val_desc = ExecBuildSlotValueDescription(RelationGetRelid(parentRelDesc),
+ slot,
+ RelationGetDescr(parentRelDesc),
+ modifiedCols,
+ 64);
+
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("cannot find partition for the new row for relation \"%s\"",
+ RelationGetRelationName(parentRelDesc)),
+ val_desc ? errdetail("Failing row contains %s.", val_desc) : 0));
+ }
+
+ return partition;
+}
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 1ef76d0..f1cc8ed 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -226,6 +226,8 @@ ExecInsert(ModifyTableState *mtstate,
{
HeapTuple tuple;
ResultRelInfo *resultRelInfo;
+ ResultRelInfo *saved_resultRelInfo = NULL; /* for partitioned table case */
+ ExprContext *econtext;
Relation resultRelationDesc;
Oid newId;
List *recheckIndexes = NIL;
@@ -334,6 +336,20 @@ ExecInsert(ModifyTableState *mtstate,
if (resultRelationDesc->rd_att->constr)
ExecConstraints(resultRelInfo, slot, estate);
+ /*
+ * Switch resultRelInfo to one corresponding to the partition
+ * that this tuple maps to.
+ */
+ if (resultRelInfo->ri_Partitions)
+ {
+ saved_resultRelInfo = resultRelInfo;
+ /* for partition key expressions */
+ econtext = GetPerTupleExprContext(estate);
+ econtext->ecxt_scantuple = slot;
+ resultRelInfo = ExecFindPartition(resultRelInfo, slot, estate);
+ estate->es_result_relation_info = resultRelInfo;
+ }
+
if (onconflict != ONCONFLICT_NONE && resultRelInfo->ri_NumIndices > 0)
{
/* Perform a speculative insertion. */
@@ -449,7 +465,7 @@ ExecInsert(ModifyTableState *mtstate,
* Note: heap_insert returns the tid (location) of the new tuple
* in the t_self field.
*/
- newId = heap_insert(resultRelationDesc, tuple,
+ newId = heap_insert(resultRelInfo->ri_RelationDesc, tuple,
estate->es_output_cid,
0, NULL);
@@ -471,6 +487,17 @@ ExecInsert(ModifyTableState *mtstate,
/* AFTER ROW INSERT Triggers */
ExecARInsertTriggers(estate, resultRelInfo, tuple, recheckIndexes);
+ /* Restore the resultRelInfo, if had been switched. */
+ if (saved_resultRelInfo)
+ {
+ heap_close(resultRelInfo->ri_RelationDesc, RowExclusiveLock);
+ ExecCloseIndices(resultRelInfo);
+ pfree(resultRelInfo);
+ resultRelInfo = saved_resultRelInfo;
+ estate->es_result_relation_info = resultRelInfo;
+ saved_resultRelInfo = NULL; /* aka, paranoia! */
+ }
+
list_free(recheckIndexes);
/*
@@ -1364,7 +1391,9 @@ ExecModifyTable(ModifyTableState *node)
bool isNull;
relkind = resultRelInfo->ri_RelationDesc->rd_rel->relkind;
- if (relkind == RELKIND_RELATION || relkind == RELKIND_MATVIEW)
+ if (relkind == RELKIND_RELATION ||
+ relkind == RELKIND_PARTITIONED_REL ||
+ relkind == RELKIND_MATVIEW)
{
datum = ExecGetJunkAttribute(slot,
junkfilter->jf_junkAttNo,
@@ -1800,6 +1829,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
relkind = resultRelInfo->ri_RelationDesc->rd_rel->relkind;
if (relkind == RELKIND_RELATION ||
+ relkind == RELKIND_PARTITIONED_REL ||
relkind == RELKIND_MATVIEW)
{
j->jf_junkAttNo = ExecFindJunkAttribute(j, "ctid");
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 1c8425d..cee3473 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2593,6 +2593,57 @@ _copyColumnDef(const ColumnDef *from)
return newnode;
}
+static PartitionBy *
+_copyPartitionBy(const PartitionBy *from)
+{
+
+ PartitionBy *newnode = makeNode(PartitionBy);
+
+ COPY_SCALAR_FIELD(strategy);
+ COPY_NODE_FIELD(partParams);
+ COPY_LOCATION_FIELD(location);
+
+ return newnode;
+}
+
+static PartitionElem *
+_copyPartitionElem(const PartitionElem *from)
+{
+ PartitionElem *newnode = makeNode(PartitionElem);
+
+ COPY_STRING_FIELD(name);
+ COPY_NODE_FIELD(expr);
+ COPY_NODE_FIELD(opclass);
+ COPY_LOCATION_FIELD(location);
+
+ return newnode;
+}
+
+static PartitionValues *
+_copyPartitionValues(const PartitionValues *from)
+{
+ PartitionValues *newnode = makeNode(PartitionValues);
+
+ COPY_NODE_FIELD(listvalues);
+ COPY_NODE_FIELD(rangemins);
+ COPY_NODE_FIELD(rangemaxs);
+ COPY_LOCATION_FIELD(location);
+
+ return newnode;
+}
+
+static PartitionDef *
+_copyPartitionDef(const PartitionDef *from)
+{
+ PartitionDef *newnode = makeNode(PartitionDef);
+
+ COPY_NODE_FIELD(name);
+ COPY_NODE_FIELD(parent);
+ COPY_NODE_FIELD(values);
+
+ return newnode;
+}
+
static Constraint *
_copyConstraint(const Constraint *from)
{
@@ -2828,6 +2879,7 @@ _copyAlterTableCmd(const AlterTableCmd *from)
COPY_STRING_FIELD(name);
COPY_NODE_FIELD(newowner);
COPY_NODE_FIELD(def);
+ COPY_NODE_FIELD(using_table);
COPY_SCALAR_FIELD(behavior);
COPY_SCALAR_FIELD(missing_ok);
@@ -2974,8 +3026,11 @@ static void
CopyCreateStmtFields(const CreateStmt *from, CreateStmt *newnode)
{
COPY_NODE_FIELD(relation);
+ COPY_NODE_FIELD(partitionOf);
COPY_NODE_FIELD(tableElts);
COPY_NODE_FIELD(inhRelations);
+ COPY_NODE_FIELD(partValues);
+ COPY_NODE_FIELD(partitionby);
COPY_NODE_FIELD(ofTypename);
COPY_NODE_FIELD(constraints);
COPY_NODE_FIELD(options);
@@ -4907,6 +4962,18 @@ copyObject(const void *from)
case T_ColumnDef:
retval = _copyColumnDef(from);
break;
+ case T_PartitionBy:
+ retval = _copyPartitionBy(from);
+ break;
+ case T_PartitionElem:
+ retval = _copyPartitionElem(from);
+ break;
+ case T_PartitionValues:
+ retval = _copyPartitionValues(from);
+ break;
+ case T_PartitionDef:
+ retval = _copyPartitionDef(from);
+ break;
case T_Constraint:
retval = _copyConstraint(from);
break;
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 1d6c43c..27e9420 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1020,6 +1020,7 @@ _equalAlterTableCmd(const AlterTableCmd *a, const AlterTableCmd *b)
COMPARE_STRING_FIELD(name);
COMPARE_NODE_FIELD(newowner);
COMPARE_NODE_FIELD(def);
+ COMPARE_NODE_FIELD(using_table);
COMPARE_SCALAR_FIELD(behavior);
COMPARE_SCALAR_FIELD(missing_ok);
@@ -1140,8 +1141,11 @@ static bool
_equalCreateStmt(const CreateStmt *a, const CreateStmt *b)
{
COMPARE_NODE_FIELD(relation);
+ COMPARE_NODE_FIELD(partitionOf);
COMPARE_NODE_FIELD(tableElts);
COMPARE_NODE_FIELD(inhRelations);
+ COMPARE_NODE_FIELD(partValues);
+ COMPARE_NODE_FIELD(partitionby);
COMPARE_NODE_FIELD(ofTypename);
COMPARE_NODE_FIELD(constraints);
COMPARE_NODE_FIELD(options);
@@ -2338,6 +2342,48 @@ _equalColumnDef(const ColumnDef *a, const ColumnDef *b)
}
static bool
+_equalPartitionBy(const PartitionBy *a, const PartitionBy *b)
+{
+ COMPARE_SCALAR_FIELD(strategy);
+ COMPARE_NODE_FIELD(partParams);
+ COMPARE_LOCATION_FIELD(location);
+
+ return true;
+}
+
+static bool
+_equalPartitionElem(const PartitionElem *a, const PartitionElem *b)
+{
+ COMPARE_STRING_FIELD(name);
+ COMPARE_NODE_FIELD(expr);
+ COMPARE_NODE_FIELD(opclass);
+ COMPARE_LOCATION_FIELD(location);
+
+ return true;
+}
+
+static bool
+_equalPartitionValues(const PartitionValues *a, const PartitionValues *b)
+{
+ COMPARE_NODE_FIELD(listvalues);
+ COMPARE_NODE_FIELD(rangemins);
+ COMPARE_NODE_FIELD(rangemaxs);
+ COMPARE_LOCATION_FIELD(location);
+
+ return true;
+}
+
+static bool
+_equalPartitionDef(const PartitionDef *a, const PartitionDef *b)
+{
+ COMPARE_NODE_FIELD(name);
+ COMPARE_NODE_FIELD(parent);
+ COMPARE_NODE_FIELD(values);
+
+ return true;
+}
+
+static bool
_equalConstraint(const Constraint *a, const Constraint *b)
{
COMPARE_SCALAR_FIELD(contype);
@@ -3264,6 +3310,18 @@ equal(const void *a, const void *b)
case T_ColumnDef:
retval = _equalColumnDef(a, b);
break;
+ case T_PartitionBy:
+ retval = _equalPartitionBy(a, b);
+ break;
+ case T_PartitionElem:
+ retval = _equalPartitionElem(a, b);
+ break;
+ case T_PartitionValues:
+ retval = _equalPartitionValues(a, b);
+ break;
+ case T_PartitionDef:
+ retval = _equalPartitionDef(a, b);
+ break;
case T_Constraint:
retval = _equalConstraint(a, b);
break;
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index a878498..ca79fba 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2092,8 +2092,11 @@ static void
_outCreateStmtInfo(StringInfo str, const CreateStmt *node)
{
WRITE_NODE_FIELD(relation);
+ WRITE_NODE_FIELD(partitionOf);
WRITE_NODE_FIELD(tableElts);
WRITE_NODE_FIELD(inhRelations);
+ WRITE_NODE_FIELD(partValues);
+ WRITE_NODE_FIELD(partitionby);
WRITE_NODE_FIELD(ofTypename);
WRITE_NODE_FIELD(constraints);
WRITE_NODE_FIELD(options);
@@ -2333,6 +2336,48 @@ _outIndexElem(StringInfo str, const IndexElem *node)
}
static void
+_outPartitionBy(StringInfo str, const PartitionBy *node)
+{
+ WRITE_NODE_TYPE("PARTITIONBY");
+
+ WRITE_CHAR_FIELD(strategy);
+ WRITE_NODE_FIELD(partParams);
+ WRITE_LOCATION_FIELD(location);
+}
+
+static void
+_outPartitionElem(StringInfo str, const PartitionElem *node)
+{
+ WRITE_NODE_TYPE("PARTITIONELEM");
+
+ WRITE_STRING_FIELD(name);
+ WRITE_NODE_FIELD(expr);
+ WRITE_NODE_FIELD(opclass);
+ WRITE_LOCATION_FIELD(location);
+}
+
+static void
+_outPartitionValues(StringInfo str, const PartitionValues *node)
+{
+ WRITE_NODE_TYPE("PARTITIONVALUES");
+
+ WRITE_NODE_FIELD(listvalues);
+ WRITE_NODE_FIELD(rangemins);
+ WRITE_NODE_FIELD(rangemaxs);
+ WRITE_LOCATION_FIELD(location);
+}
+
+static void
+_outPartitionDef(StringInfo str, const PartitionDef *node)
+{
+ WRITE_NODE_TYPE("PARTITIONDEF");
+
+ WRITE_NODE_FIELD(name);
+ WRITE_NODE_FIELD(parent);
+ WRITE_NODE_FIELD(values);
+}
+
+static void
_outQuery(StringInfo str, const Query *node)
{
WRITE_NODE_TYPE("QUERY");
@@ -3366,6 +3411,18 @@ _outNode(StringInfo str, const void *obj)
case T_IndexElem:
_outIndexElem(str, obj);
break;
+ case T_PartitionBy:
+ _outPartitionBy(str, obj);
+ break;
+ case T_PartitionElem:
+ _outPartitionElem(str, obj);
+ break;
+ case T_PartitionValues:
+ _outPartitionValues(str, obj);
+ break;
+ case T_PartitionDef:
+ _outPartitionDef(str, obj);
+ break;
case T_Query:
_outQuery(str, obj);
break;
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 426a09d..df52df5 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -226,6 +226,9 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
struct ImportQual *importqual;
InsertStmt *istmt;
VariableSetStmt *vsetstmt;
+ PartitionElem *pelem;
+ PartitionValues *pvalues;
+ PartitionBy *partby;
}
%type <node> stmt schema_stmt
@@ -321,6 +324,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
opt_collate
%type <range> qualified_name insert_target OptConstrFromTable
+ using_table opt_using_table
%type <str> all_Op MathOp
@@ -537,6 +541,13 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
opt_frame_clause frame_extent frame_bound
%type <str> opt_existing_window_name
%type <boolean> opt_if_not_exists
+%type <partby> OptPartitionBy PartitionBy
+%type <list> part_params
+%type <pelem> part_elem
+%type <pvalues> PartitionValues
+%type <pvalues> ListValues
+%type <pvalues> RangeValues
+%type <list> ValuesList
/*
* Non-keyword token types. These are hard-wired into the "flex" lexer.
@@ -562,7 +573,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
/* ordinary key words in alphabetical order */
%token <keyword> ABORT_P ABSOLUTE_P ACCESS ACTION ADD_P ADMIN AFTER
AGGREGATE ALL ALSO ALTER ALWAYS ANALYSE ANALYZE AND ANY ARRAY AS ASC
- ASSERTION ASSIGNMENT ASYMMETRIC AT ATTRIBUTE AUTHORIZATION
+ ASSERTION ASSIGNMENT ASYMMETRIC AT ATTACH ATTRIBUTE AUTHORIZATION
BACKWARD BEFORE BEGIN_P BETWEEN BIGINT BINARY BIT
BOOLEAN_P BOTH BY
@@ -577,7 +588,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
CURRENT_TIME CURRENT_TIMESTAMP CURRENT_USER CURSOR CYCLE
DATA_P DATABASE DAY_P DEALLOCATE DEC DECIMAL_P DECLARE DEFAULT DEFAULTS
- DEFERRABLE DEFERRED DEFINER DELETE_P DELIMITER DELIMITERS DESC
+ DEFERRABLE DEFERRED DEFINER DELETE_P DELIMITER DELIMITERS DESC DETACH
DICTIONARY DISABLE_P DISCARD DISTINCT DO DOCUMENT_P DOMAIN_P DOUBLE_P DROP
EACH ELSE ENABLE_P ENCODING ENCRYPTED END_P ENUM_P ESCAPE EVENT EXCEPT
@@ -601,7 +612,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
KEY
LABEL LANGUAGE LARGE_P LAST_P LATERAL_P
- LEADING LEAKPROOF LEAST LEFT LEVEL LIKE LIMIT LISTEN LOAD LOCAL
+ LEADING LEAKPROOF LEAST LEFT LEVEL LIKE LIMIT LIST LISTEN LOAD LOCAL
LOCALTIME LOCALTIMESTAMP LOCATION LOCK_P LOCKED LOGGED
MAPPING MATCH MATERIALIZED MAXVALUE MINUTE_P MINVALUE MODE MONTH_P MOVE
@@ -2360,6 +2371,44 @@ alter_table_cmd:
n->def = (Node *)$1;
$$ = (Node *) n;
}
+ /*
+ * ALTER TABLE <parent> ATTACH PARTITION <name>
+ * FOR VALUES (...) USING [TABLE] <table_name>
+ */
+ | ATTACH PARTITION qualified_name PartitionValues using_table
+ {
+ AlterTableCmd *n = makeNode(AlterTableCmd);
+ PartitionDef *def = makeNode(PartitionDef);
+ n->subtype = AT_AttachPartition;
+ def->name = $3;
+ def->values = $4;
+ n->def = (Node *) def;
+ n->using_table = $5;
+ $$ = (Node *) n;
+ }
+ /*
+ * ALTER TABLE <parent> DETACH PARTITION <name>
+ * [ USING [ TABLE ] <table_name> ]
+ */
+ | DETACH PARTITION qualified_name opt_using_table
+ {
+ AlterTableCmd *n = makeNode(AlterTableCmd);
+ PartitionDef *def = makeNode(PartitionDef);
+ n->subtype = AT_DetachPartition;
+ def->name = $3;
+ n->def = (Node *) def;
+ n->using_table = $4;
+ $$ = (Node *) n;
+ }
+ ;
+
+opt_using_table:
+ using_table { $$ = $1; }
+ | /* EMPTY */ { $$ = NULL; }
+ ;
+using_table:
+ /* USING [ TABLE ] qualified_name */
+ USING opt_table qualified_name { $$ = $3; }
;
alter_column_default:
@@ -2797,7 +2846,7 @@ copy_generic_opt_arg_list_item:
*****************************************************************************/
CreateStmt: CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
- OptInherit OptWith OnCommitOption OptTableSpace
+ OptInherit OptPartitionBy OptWith OnCommitOption OptTableSpace
{
CreateStmt *n = makeNode(CreateStmt);
$4->relpersistence = $2;
@@ -2805,15 +2854,16 @@ CreateStmt: CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
n->tableElts = $6;
n->inhRelations = $8;
n->ofTypename = NULL;
+ n->partitionby = $9;
n->constraints = NIL;
- n->options = $9;
- n->oncommit = $10;
- n->tablespacename = $11;
+ n->options = $10;
+ n->oncommit = $11;
+ n->tablespacename = $12;
n->if_not_exists = false;
$$ = (Node *)n;
}
| CREATE OptTemp TABLE IF_P NOT EXISTS qualified_name '('
- OptTableElementList ')' OptInherit OptWith OnCommitOption
+ OptTableElementList ')' OptInherit OptPartitionBy OptWith OnCommitOption
OptTableSpace
{
CreateStmt *n = makeNode(CreateStmt);
@@ -2822,15 +2872,16 @@ CreateStmt: CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
n->tableElts = $9;
n->inhRelations = $11;
n->ofTypename = NULL;
+ n->partitionby = $12;
n->constraints = NIL;
- n->options = $12;
- n->oncommit = $13;
- n->tablespacename = $14;
+ n->options = $13;
+ n->oncommit = $14;
+ n->tablespacename = $15;
n->if_not_exists = true;
$$ = (Node *)n;
}
| CREATE OptTemp TABLE qualified_name OF any_name
- OptTypedTableElementList OptWith OnCommitOption OptTableSpace
+ OptTypedTableElementList OptPartitionBy OptWith OnCommitOption OptTableSpace
{
CreateStmt *n = makeNode(CreateStmt);
$4->relpersistence = $2;
@@ -2839,15 +2890,16 @@ CreateStmt: CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
n->inhRelations = NIL;
n->ofTypename = makeTypeNameFromNameList($6);
n->ofTypename->location = @6;
+ n->partitionby = $8;
n->constraints = NIL;
- n->options = $8;
- n->oncommit = $9;
- n->tablespacename = $10;
+ n->options = $9;
+ n->oncommit = $10;
+ n->tablespacename = $11;
n->if_not_exists = false;
$$ = (Node *)n;
}
| CREATE OptTemp TABLE IF_P NOT EXISTS qualified_name OF any_name
- OptTypedTableElementList OptWith OnCommitOption OptTableSpace
+ OptTypedTableElementList OptPartitionBy OptWith OnCommitOption OptTableSpace
{
CreateStmt *n = makeNode(CreateStmt);
$7->relpersistence = $2;
@@ -2856,10 +2908,43 @@ CreateStmt: CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
n->inhRelations = NIL;
n->ofTypename = makeTypeNameFromNameList($9);
n->ofTypename->location = @9;
+ n->partitionby = $11;
n->constraints = NIL;
- n->options = $11;
- n->oncommit = $12;
- n->tablespacename = $13;
+ n->options = $12;
+ n->oncommit = $13;
+ n->tablespacename = $14;
+ n->if_not_exists = true;
+ $$ = (Node *)n;
+ }
+ | CREATE OptTemp TABLE qualified_name PARTITION OF qualified_name
+ PartitionValues OptPartitionBy OptWith OnCommitOption OptTableSpace
+ {
+ CreateStmt *n = makeNode(CreateStmt);
+ $4->relpersistence = $2;
+ n->relation = $4;
+ n->partitionOf = $7;
+ n->partValues = $8;
+ n->partitionby = $9;
+ n->constraints = NIL;
+ n->options = $10;
+ n->oncommit = $11;
+ n->tablespacename = $12;
+ n->if_not_exists = false;
+ $$ = (Node *)n;
+ }
+ | CREATE OptTemp TABLE IF_P NOT EXISTS qualified_name PARTITION OF qualified_name
+ PartitionValues OptPartitionBy OptWith OnCommitOption OptTableSpace
+ {
+ CreateStmt *n = makeNode(CreateStmt);
+ $7->relpersistence = $2;
+ n->relation = $7;
+ n->partitionOf = $10;
+ n->partValues = $11;
+ n->partitionby = $12;
+ n->constraints = NIL;
+ n->options = $13;
+ n->oncommit = $14;
+ n->tablespacename = $15;
n->if_not_exists = true;
$$ = (Node *)n;
}
@@ -3402,6 +3487,132 @@ OptInherit: INHERITS '(' qualified_name_list ')' { $$ = $3; }
| /*EMPTY*/ { $$ = NIL; }
;
+/* Optional partition key (PARTITION ON) definition */
+OptPartitionBy: PartitionBy { $$ = $1; }
+ | /*EMPTY*/ { $$ = NULL; }
+ ;
+
+PartitionBy: PARTITION BY RANGE ON '(' part_params ')'
+ {
+ PartitionBy *n = makeNode(PartitionBy);
+
+ n->strategy = PARTITION_STRAT_RANGE;
+ n->partParams = $6;
+ n->location = @1;
+
+ $$ = n;
+ }
+ | PARTITION BY LIST ON '(' part_params ')'
+ {
+ PartitionBy *n = makeNode(PartitionBy);
+
+ n->strategy = PARTITION_STRAT_LIST;
+ n->partParams = $6;
+ n->location = @1;
+
+ $$ = n;
+ }
+ ;
+
+part_params: part_elem { $$ = list_make1($1); }
+ | part_params ',' part_elem { $$ = lappend($1, $3); }
+ ;
+
+part_elem: ColId opt_class
+ {
+ PartitionElem *n = makeNode(PartitionElem);
+
+ n->name = $1;
+ n->expr = NULL;
+ n->opclass = $2;
+ n->location = @1;
+ $$ = n;
+ }
+ | func_expr_windowless opt_class
+ {
+ PartitionElem *n = makeNode(PartitionElem);
+
+ n->name = NULL;
+ n->expr = $1;
+ n->opclass = $2;
+ n->location = @1;
+ $$ = n;
+ }
+ | '(' a_expr ')' opt_class
+ {
+ PartitionElem *n = makeNode(PartitionElem);
+
+ n->name = NULL;
+ n->expr = $2;
+ n->opclass = $4;
+ n->location = @1;
+ $$ = n;
+ }
+ ;
+
+/* Definition of a partition */
+PartitionValues:
+ FOR VALUES ListValues { $$ = $3; }
+ | FOR VALUES RangeValues { $$ = $3; }
+ ;
+
+ListValues:
+ opt_in '(' ValuesList ')'
+ {
+ PartitionValues *n = makeNode(PartitionValues);
+
+ n->listvalues = $3;
+ n->rangemins = NIL;
+ n->rangemaxs = NIL;
+ n->location = @1;
+ $$ = n;
+ }
+ ;
+
+RangeValues:
+ END_P '(' ValuesList ')'
+ {
+ PartitionValues *n = makeNode(PartitionValues);
+
+ n->listvalues = NIL;
+ n->rangemins = NIL;
+ n->rangemaxs = $3;
+ n->location = @1;
+ $$ = n;
+ }
+ |
+ START '(' ValuesList ')'
+ {
+ PartitionValues *n = makeNode(PartitionValues);
+
+ n->listvalues = NIL;
+ n->rangemins = $3;
+ n->rangemaxs = NIL;
+ n->location = @1;
+ $$ = n;
+ }
+ |
+ START '(' ValuesList ')' END_P '(' ValuesList ')'
+ {
+ PartitionValues *n = makeNode(PartitionValues);
+
+ n->listvalues = NIL;
+ n->rangemins = $3;
+ n->rangemaxs = $7;
+ n->location = @1;
+ $$ = n;
+ }
+ ;
+
+opt_in: IN_P {}
+ | /* EMPTY */ {}
+ ;
+
+ValuesList:
+ a_expr { $$ = list_make1($1); }
+ | ValuesList ',' a_expr { $$ = lappend($1, $3); }
+ ;
+
/* WITH (options) is preferred, WITH OIDS and WITHOUT OIDS are legacy forms */
OptWith:
WITH reloptions { $$ = $2; }
@@ -13637,6 +13848,7 @@ unreserved_keyword:
| ASSERTION
| ASSIGNMENT
| AT
+ | ATTACH
| ATTRIBUTE
| BACKWARD
| BEFORE
@@ -13682,6 +13894,7 @@ unreserved_keyword:
| DELETE_P
| DELIMITER
| DELIMITERS
+ | DETACH
| DICTIONARY
| DISABLE_P
| DISCARD
@@ -13743,6 +13956,7 @@ unreserved_keyword:
| LAST_P
| LEAKPROOF
| LEVEL
+ | LIST
| LISTEN
| LOAD
| LOCAL
diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c
index 5b0d568..b66aac8 100644
--- a/src/backend/parser/parse_agg.c
+++ b/src/backend/parser/parse_agg.c
@@ -450,6 +450,9 @@ check_agglevels_and_constraints(ParseState *pstate, Node *expr)
err = _("grouping operations are not allowed in DEFAULT expressions");
break;
+ case EXPR_KIND_PARTITION_VALUES:
+ err = _("aggregate functions are not allowed in partition value expressions");
+ break;
case EXPR_KIND_INDEX_EXPRESSION:
if (isAgg)
err = _("aggregate functions are not allowed in index expressions");
@@ -457,6 +460,9 @@ check_agglevels_and_constraints(ParseState *pstate, Node *expr)
err = _("grouping operations are not allowed in index expressions");
break;
+ case EXPR_KIND_PARTKEY_EXPRESSION:
+ err = _("aggregate functions are not allowed in partition key expressions");
+ break;
case EXPR_KIND_INDEX_PREDICATE:
if (isAgg)
err = _("aggregate functions are not allowed in index predicates");
@@ -827,9 +833,15 @@ transformWindowFuncCall(ParseState *pstate, WindowFunc *wfunc,
case EXPR_KIND_FUNCTION_DEFAULT:
err = _("window functions are not allowed in DEFAULT expressions");
break;
+ case EXPR_KIND_PARTITION_VALUES:
+ err = _("window functions are not allowed in partition value expressions");
+ break;
case EXPR_KIND_INDEX_EXPRESSION:
err = _("window functions are not allowed in index expressions");
break;
+ case EXPR_KIND_PARTKEY_EXPRESSION:
+ err = _("window functions are not allowed in partition key expressions");
+ break;
case EXPR_KIND_INDEX_PREDICATE:
err = _("window functions are not allowed in index predicates");
break;
diff --git a/src/backend/parser/parse_clause.c b/src/backend/parser/parse_clause.c
index 5980856..c856215 100644
--- a/src/backend/parser/parse_clause.c
+++ b/src/backend/parser/parse_clause.c
@@ -22,6 +22,7 @@
#include "catalog/catalog.h"
#include "catalog/heap.h"
#include "catalog/pg_constraint.h"
+#include "catalog/pg_partition_fn.h"
#include "catalog/pg_type.h"
#include "commands/defrem.h"
#include "nodes/makefuncs.h"
@@ -2880,6 +2881,17 @@ transformOnConflictArbiter(ParseState *pstate,
parser_errposition(pstate,
exprLocation((Node *) onConflictClause))));
+ /*
+ * ON CONFLICT on partitioned tables disallowed.
+ * XXX - not permanently.
+ */
+ if (IsPartitionedRel(pstate->p_target_relation))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("ON CONFLICT not supported with partitioned tables"),
+ parser_errposition(pstate,
+ exprLocation((Node *) onConflictClause))));
+
/* ON CONFLICT DO NOTHING does not require an inference clause */
if (infer)
{
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index fa77ef1..183ae6b 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -1700,9 +1700,15 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
case EXPR_KIND_FUNCTION_DEFAULT:
err = _("cannot use subquery in DEFAULT expression");
break;
+ case EXPR_KIND_PARTITION_VALUES:
+ err = _("cannot use subquery in partition value expressions");
+ break;
case EXPR_KIND_INDEX_EXPRESSION:
err = _("cannot use subquery in index expression");
break;
+ case EXPR_KIND_PARTKEY_EXPRESSION:
+ err = _("cannot use subquery in partition key expressions");
+ break;
case EXPR_KIND_INDEX_PREDICATE:
err = _("cannot use subquery in index predicate");
break;
@@ -3225,6 +3231,10 @@ ParseExprKindName(ParseExprKind exprKind)
return "EXECUTE";
case EXPR_KIND_TRIGGER_WHEN:
return "WHEN";
+ case EXPR_KIND_PARTKEY_EXPRESSION:
+ return "partition key expression";
+ case EXPR_KIND_PARTITION_VALUES:
+ return "PARTITION FOR VALUES";
/*
* There is intentionally no default: case here, so that the
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 16d40c7..596b550 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -83,6 +83,8 @@ typedef struct
List *alist; /* "after list" of things to do after creating
* the table */
IndexStmt *pkey; /* PRIMARY KEY index, if any */
+ bool ispartitioned; /* true if CREATE TABLE ... PARTITION BY */
+ List *partitionElts; /* CREATE TABLE ... PARTITION OF ... */
} CreateStmtContext;
/* State shared by transformCreateSchemaStmt and its subroutines */
@@ -123,6 +125,11 @@ static void transformConstraintAttrs(CreateStmtContext *cxt,
List *constraintList);
static void transformColumnType(CreateStmtContext *cxt, ColumnDef *column);
static void setSchemaName(char *context_schema, char **stmt_schema_name);
+static void transformPartitionOf(CreateStmtContext *cxt,
+ RangeVar *parent,
+ PartitionValues *values);
+static PartitionValues* transformPartitionValues(CreateStmtContext *cxt,
+ PartitionValues *values);
/*
@@ -222,6 +229,8 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
cxt.blist = NIL;
cxt.alist = NIL;
cxt.pkey = NULL;
+ cxt.ispartitioned = stmt->partitionby ? true : false;
+ cxt.partitionElts = NIL;
/*
* Notice that we allow OIDs here only for plain tables, even though
@@ -236,11 +245,29 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
cxt.hasoids = interpretOidsOption(stmt->options, !cxt.isforeign);
Assert(!stmt->ofTypename || !stmt->inhRelations); /* grammar enforces */
+ Assert(!stmt->ofTypename || !stmt->partitionOf); /* grammar enforces */
+ Assert(!stmt->inhRelations || !stmt->partitionOf); /* grammar enforces */
if (stmt->ofTypename)
transformOfType(&cxt, stmt->ofTypename);
/*
+ * Transform "PARTITION OF parent FOR VALUES ..." clause:
+ *
+ * 1. Gin up a (LIKE parent INCLUDING ALL) clause,
+ * 2. Gin up a AlterTableStmt that arranges to attaches this relation
+ * as a partition of parent.
+ */
+ if (stmt->partitionOf)
+ {
+ transformPartitionOf(&cxt, stmt->partitionOf, stmt->partValues);
+
+ /* Override based on the result of transformPartitionOf. */
+ stmt->tableElts = cxt.partitionElts;
+ stmt->relation->schemaname = cxt.relation->schemaname;
+ }
+
+ /*
* Run through each primary element in the table creation clause. Separate
* column defs from constraints, and do preliminary analysis.
*/
@@ -541,6 +568,12 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
errmsg("primary key constraints are not supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
+ if (cxt->ispartitioned)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("primary key constraints are not supported on partitioned tables"),
+ parser_errposition(cxt->pstate,
+ constraint->location)));
/* FALL THRU */
case CONSTR_UNIQUE:
@@ -550,6 +583,12 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
errmsg("unique constraints are not supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
+ if (cxt->ispartitioned)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("unique constraints are not supported on partitioned tables"),
+ parser_errposition(cxt->pstate,
+ constraint->location)));
if (constraint->keys == NIL)
constraint->keys = list_make1(makeString(column->colname));
cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
@@ -567,6 +606,16 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
errmsg("foreign key constraints are not supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
+ /*
+ * Following limitation stems from the fact that row-level AFTER
+ * triggers are yet disallowed. See CreateTrigger().
+ */
+ if (cxt->ispartitioned)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("foreign key constraints are not supported on partitioned tables"),
+ parser_errposition(cxt->pstate,
+ constraint->location)));
/*
* Fill in the current attribute's name and throw it into the
@@ -632,6 +681,12 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
errmsg("primary key constraints are not supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
+ if (cxt->ispartitioned)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("primary key constraints are not supported on partitioned tables"),
+ parser_errposition(cxt->pstate,
+ constraint->location)));
cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
break;
@@ -642,6 +697,12 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
errmsg("unique constraints are not supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
+ if (cxt->ispartitioned)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("unique constraints are not supported on partitioned tables"),
+ parser_errposition(cxt->pstate,
+ constraint->location)));
cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
break;
@@ -652,6 +713,12 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
errmsg("exclusion constraints are not supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
+ if (cxt->ispartitioned)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("exclusion constraints are not supported on partitioned tables"),
+ parser_errposition(cxt->pstate,
+ constraint->location)));
cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
break;
@@ -666,6 +733,16 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
errmsg("foreign key constraints are not supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
+ /*
+ * Following limitation stems from the fact that row-level AFTER
+ * triggers are yet disallowed. See CreateTrigger().
+ */
+ if (cxt->ispartitioned)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("foreign key constraints are not supported on partitioned tables"),
+ parser_errposition(cxt->pstate,
+ constraint->location)));
cxt->fkconstraints = lappend(cxt->fkconstraints, constraint);
break;
@@ -718,6 +795,7 @@ transformTableLikeClause(CreateStmtContext *cxt, TableLikeClause *table_like_cla
relation = relation_openrv(table_like_clause->relation, AccessShareLock);
if (relation->rd_rel->relkind != RELKIND_RELATION &&
+ relation->rd_rel->relkind != RELKIND_PARTITIONED_REL &&
relation->rd_rel->relkind != RELKIND_VIEW &&
relation->rd_rel->relkind != RELKIND_MATVIEW &&
relation->rd_rel->relkind != RELKIND_COMPOSITE_TYPE &&
@@ -2430,6 +2508,7 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
cxt.inh_indexes = NIL;
cxt.blist = NIL;
cxt.alist = NIL;
+ cxt.ispartitioned = is_partitioned(relid) ? true : false;
cxt.pkey = NULL;
/*
@@ -2519,6 +2598,52 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
break;
}
+ /*
+ * Push a RenameStmt to rename the the table to specified name
+ * once it's attached to become a partition. Rest of the
+ * responsibilities belong to AlterTblStmt.
+ *
+ * XXX - This is currently not supported. Won't be until we teach
+ * ATExecAttachPartition() to deal with existing tables passed in
+ * to use as a partition. Remember to set cmd->def->parent:
+ */
+ case AT_AttachPartition:
+ {
+ if (cmd->using_table)
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("ALTER TABLE ATTACH PARTITION (...) USING TABLE not implemented")));
+ }
+
+ newcmds = lappend(newcmds, cmd);
+ break;
+ }
+
+ /*
+ * Push a RenameStmt to rename the partition to specified name
+ * once it's detached to become a regular table.
+ */
+ case AT_DetachPartition:
+ {
+ RenameStmt *stmt;
+
+ if (cmd->using_table)
+ {
+ stmt = makeNode(RenameStmt);
+
+ stmt->renameType = OBJECT_TABLE;
+ stmt->relation = ((PartitionDef *) cmd->def)->name;
+ stmt->newname = cmd->using_table->relname;
+ stmt->missing_ok = false;
+
+ cxt.alist = lappend(cxt.alist, stmt);
+ }
+
+ newcmds = lappend(newcmds, cmd);
+ break;
+ }
+
default:
newcmds = lappend(newcmds, cmd);
break;
@@ -2883,3 +3008,105 @@ setSchemaName(char *context_schema, char **stmt_schema_name)
"different from the one being created (%s)",
*stmt_schema_name, context_schema)));
}
+
+/*
+ * transformPartitionOf - transform CREATE TABLE ... PARTITION OF <parent>
+ */
+static void
+transformPartitionOf(CreateStmtContext *cxt,
+ RangeVar *parent,
+ PartitionValues *values)
+{
+ TableLikeClause *like;
+ PartitionDef *partition;
+ AlterTableCmd *attachcmd;
+ AlterTableStmt *alter;
+
+ /* Use the same schema as the parent if not specified. */
+ if (cxt->relation->schemaname == NULL)
+ cxt->relation->schemaname = parent->schemaname;
+
+ like = makeNode(TableLikeClause);
+ like->relation = parent;
+ like->options = CREATE_TABLE_LIKE_ALL;
+
+ cxt->partitionElts = list_make1(like);
+
+ /*
+ * Gin up a AlterTableStmt that would take care of creating the
+ * pg_partition entry for this relation as a partition of parent.
+ */
+ partition = makeNode(PartitionDef);
+ partition->name = cxt->relation;
+ partition->parent = parent;
+ partition->values = transformPartitionValues(cxt, values);
+
+ attachcmd = makeNode(AlterTableCmd);
+ attachcmd->subtype = AT_AttachPartition;
+ attachcmd->def = (Node *) partition;
+
+ alter = makeNode(AlterTableStmt);
+ alter->relation = parent;
+ alter->cmds = list_make1(attachcmd);
+ alter->relkind = OBJECT_TABLE;
+
+ cxt->alist = lappend(cxt->alist, alter);
+}
+
+/*
+ * transformPartitionValues
+ *
+ * Transform partition value as returned by the grammar into something
+ * we can evaluate to store into pg_partition.
+ */
+static PartitionValues *
+transformPartitionValues(CreateStmtContext *cxt, PartitionValues *values)
+{
+ ListCell *cell;
+ PartitionValues *result = (PartitionValues *)
+ makeNode(PartitionValues);
+
+ if (values->listvalues)
+ {
+ foreach(cell, values->listvalues)
+ {
+ Node *value = (Node *) lfirst(cell);
+
+ result->listvalues = lappend(result->listvalues,
+ transformExpr(cxt->pstate, value,
+ EXPR_KIND_PARTITION_VALUES));
+ }
+ }
+ else
+ {
+ if(values->rangemins)
+ {
+ foreach(cell, values->rangemins)
+ {
+ Node *value;
+
+ value = (Node *) lfirst(cell);
+ result->rangemins = lappend(result->rangemins,
+ transformExpr(cxt->pstate, value,
+ EXPR_KIND_PARTITION_VALUES));
+ }
+ }
+
+ if(values->rangemaxs)
+ {
+ foreach(cell, values->rangemaxs)
+ {
+ Node *value;
+
+ value = (Node *) lfirst(cell);
+ result->rangemaxs = lappend(result->rangemaxs,
+ transformExpr(cxt->pstate, value,
+ EXPR_KIND_PARTITION_VALUES));
+ }
+ }
+ }
+
+ result->location = values->location;
+
+ return result;
+}
diff --git a/src/backend/rewrite/rewriteDefine.c b/src/backend/rewrite/rewriteDefine.c
index 39c83a6..b6c6d96 100644
--- a/src/backend/rewrite/rewriteDefine.c
+++ b/src/backend/rewrite/rewriteDefine.c
@@ -260,6 +260,7 @@ DefineQueryRewrite(char *rulename,
* blocks them for users. Don't mention them in the error message.
*/
if (event_relation->rd_rel->relkind != RELKIND_RELATION &&
+ event_relation->rd_rel->relkind != RELKIND_PARTITIONED_REL &&
event_relation->rd_rel->relkind != RELKIND_MATVIEW &&
event_relation->rd_rel->relkind != RELKIND_VIEW)
ereport(ERROR,
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index e81bbc6..63b555e 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -961,37 +961,44 @@ ProcessUtilitySlow(Node *parsetree,
{
Datum toast_options;
static char *validnsps[] = HEAP_RELOPT_NAMESPACES;
+ char relkind = ((CreateStmt *) stmt)->partitionby
+ ? RELKIND_PARTITIONED_REL
+ : RELKIND_RELATION;
/* Create the table itself */
address = DefineRelation((CreateStmt *) stmt,
- RELKIND_RELATION,
+ relkind,
InvalidOid, NULL);
EventTriggerCollectSimpleCommand(address,
secondaryObject,
stmt);
- /*
- * Let NewRelationCreateToastTable decide if this
- * one needs a secondary relation too.
- */
- CommandCounterIncrement();
-
- /*
- * parse and validate reloptions for the toast
- * table
- */
- toast_options = transformRelOptions((Datum) 0,
- ((CreateStmt *) stmt)->options,
- "toast",
- validnsps,
- true,
- false);
- (void) heap_reloptions(RELKIND_TOASTVALUE,
- toast_options,
- true);
+ /* XXX - is this the right place? */
+ if (relkind != RELKIND_PARTITIONED_REL)
+ {
+ /*
+ * Let NewRelationCreateToastTable decide if this
+ * one needs a secondary relation too.
+ */
+ CommandCounterIncrement();
- NewRelationCreateToastTable(address.objectId,
- toast_options);
+ /*
+ * parse and validate reloptions for the toast
+ * table
+ */
+ toast_options = transformRelOptions((Datum) 0,
+ ((CreateStmt *) stmt)->options,
+ "toast",
+ validnsps,
+ true,
+ false);
+ (void) heap_reloptions(RELKIND_TOASTVALUE,
+ toast_options,
+ true);
+
+ NewRelationCreateToastTable(address.objectId,
+ toast_options);
+ }
}
else if (IsA(stmt, CreateForeignTableStmt))
{
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 44e9509..f8712b5 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -30,6 +30,7 @@
#include <fcntl.h>
#include <unistd.h>
+#include "access/nbtree.h"
#include "access/htup_details.h"
#include "access/multixact.h"
#include "access/reloptions.h"
@@ -39,6 +40,7 @@
#include "access/xlog.h"
#include "catalog/catalog.h"
#include "catalog/index.h"
+#include "catalog/heap.h"
#include "catalog/indexing.h"
#include "catalog/namespace.h"
#include "catalog/pg_amproc.h"
@@ -46,9 +48,13 @@
#include "catalog/pg_authid.h"
#include "catalog/pg_auth_members.h"
#include "catalog/pg_constraint.h"
+#include "catalog/pg_collation.h"
#include "catalog/pg_database.h"
#include "catalog/pg_namespace.h"
#include "catalog/pg_opclass.h"
+#include "catalog/pg_partition.h"
+#include "catalog/pg_partition_fn.h"
+#include "catalog/pg_partitioned_rel.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_rewrite.h"
#include "catalog/pg_tablespace.h"
@@ -59,6 +65,9 @@
#include "commands/policy.h"
#include "commands/trigger.h"
#include "miscadmin.h"
+#include "nodes/nodeFuncs.h"
+#include "nodes/execnodes.h"
+#include "nodes/parsenodes.h"
#include "optimizer/clauses.h"
#include "optimizer/planmain.h"
#include "optimizer/prep.h"
@@ -69,10 +78,12 @@
#include "storage/smgr.h"
#include "utils/array.h"
#include "utils/builtins.h"
+#include "utils/datum.h"
#include "utils/fmgroids.h"
#include "utils/inval.h"
#include "utils/lsyscache.h"
#include "utils/memutils.h"
+#include "utils/partition.h"
#include "utils/relmapper.h"
#include "utils/resowner_private.h"
#include "utils/snapmgr.h"
@@ -275,6 +286,9 @@ static OpClassCacheEnt *LookupOpclassInfo(Oid operatorClassOid,
StrategyNumber numSupport);
static void RelationCacheInitFileRemoveInDir(const char *tblspcpath);
static void unlink_initfile(const char *initfilename);
+static void RelationBuildPartitionKey(Relation relation);
+static PartitionInfo *CopyPartitionInfo(PartitionInfo *src, int partnatts,
+ PartitionKeyTypeInfo *typinfo);
/*
@@ -1048,6 +1062,10 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
else
relation->rd_rsdesc = NULL;
+ /* if a partitioned table - initialize the key info */
+ if (relation->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+ RelationBuildPartitionKey(relation);
+
/*
* if it's an index, initialize index-related information
*/
@@ -2007,6 +2025,12 @@ RelationDestroyRelation(Relation relation, bool remember_tupdesc)
bms_free(relation->rd_keyattr);
bms_free(relation->rd_idattr);
FreeTriggerDesc(relation->trigdesc);
+ if (relation->rd_pkeytuple)
+ pfree(relation->rd_pkeytuple);
+ if (relation->rd_pkeycxt)
+ MemoryContextDelete(relation->rd_pkeycxt);
+ if (relation->rd_partlistcxt)
+ MemoryContextDelete(relation->rd_partlistcxt);
if (relation->rd_options)
pfree(relation->rd_options);
if (relation->rd_indextuple)
@@ -4445,6 +4469,471 @@ RelationGetExclusionInfo(Relation indexRelation,
MemoryContextSwitchTo(oldcxt);
}
+/*
+ * RelationGetPartitionExpressions -- get the partition expressions for a
+ * partitioned table
+ *
+ * We cache the result of transforming pg_partitioned_rel.partexprs into
+ * a node tree. If the key has no expressional columns, we return NIL.
+ * Otherwise, the returned tree is copied into the caller's memory context.
+ * (We don't want to return a pointer to the relcache copy,since it could
+ * disappear due to relcache invalidation.)
+ */
+List *
+RelationGetPartitionExpressions(Relation relation)
+{
+ Relation pg_partitioned_rel;
+ List *result;
+ Datum exprsDatum;
+ bool isnull;
+ char *exprsString;
+ MemoryContext oldcxt;
+
+ /* Quick exit if we already computed the result. */
+ if (relation->rd_partexprs)
+ return (List *) copyObject(relation->rd_partexprs);
+
+ /*
+ * Quick exit if there is nothing to do.
+ *
+ * The first part should not happen really. We wouldn't be calling
+ * this on a non-RELKIND_PARTITIONED_REL relation.
+ */
+ if (relation->rd_pkeytuple == NULL ||
+ heap_attisnull(relation->rd_pkeytuple, Anum_pg_partitioned_rel_partexprs))
+ return NIL;
+
+ /*
+ * We build the tree we intend to return in the caller's context. After
+ * successfully completing the work, we copy it into the relcache entry.
+ * This avoids problems if we get some sort of error partway through.
+ */
+ pg_partitioned_rel = heap_open(PartitionedRelRelationId, AccessShareLock);
+ exprsDatum = heap_getattr(relation->rd_pkeytuple,
+ Anum_pg_partitioned_rel_partexprs,
+ RelationGetDescr(pg_partitioned_rel),
+ &isnull);
+
+ Assert(!isnull);
+ exprsString = TextDatumGetCString(exprsDatum);
+ result = (List *) stringToNode(exprsString);
+ pfree(exprsString);
+ heap_close(pg_partitioned_rel, AccessShareLock);
+
+ /*
+ * Run the expressions through eval_const_expressions. This is not just an
+ * optimization, but is necessary, because the planner will be comparing
+ * them to similarly-processed qual clauses, and may fail to detect valid
+ * matches without this. We don't bother with canonicalize_qual, however.
+ */
+ result = (List *) eval_const_expressions(NULL, (Node *) result);
+
+ /* May as well fix opfuncids too */
+ fix_opfuncids((Node *) result);
+
+ /* Now save a copy of the completed tree in the relcache entry. */
+ oldcxt = MemoryContextSwitchTo(relation->rd_pkeycxt);
+ relation->rd_partexprs = (List *) copyObject(result);
+ MemoryContextSwitchTo(oldcxt);
+
+ return result;
+}
+
+/*
+ * CopyPartitionInfo - copies the PartitionInfo of a partitioned relation
+ * into caller's set context
+ */
+static PartitionInfo *
+CopyPartitionInfo(PartitionInfo *src, int partnatts,
+ PartitionKeyTypeInfo *typinfo)
+{
+ PartitionInfo *result;
+ int i, j;
+
+ if (src == NULL)
+ return NULL;
+
+ result = (PartitionInfo *) palloc0(sizeof(PartitionInfo));
+
+ result->numpartitions = src->numpartitions;
+ result->strategy = src->strategy;
+ result->oids = (Oid *) palloc0(src->numpartitions * sizeof(Oid));
+
+ switch (src->strategy)
+ {
+ case PARTITION_STRAT_LIST:
+ result->listnvalues = (int *) palloc0(src->numpartitions
+ * sizeof(int));
+ result->listvalues = (Datum **) palloc0(src->numpartitions
+ * sizeof(Datum *));
+ for (i = 0; i < src->numpartitions; i++)
+ {
+ result->oids[i] = src->oids[i];
+ result->listnvalues[i] = src->listnvalues[i];
+ result->listvalues[i] = (Datum *)
+ palloc0(result->listnvalues[i] * sizeof(Datum));
+
+ for (j = 0; j < result->listnvalues[i]; j++)
+ {
+ Datum *from = &src->listvalues[i][j];
+ Datum *into = &result->listvalues[i][j];
+
+ if (!typinfo->typbyval[0])
+ {
+ if (typinfo->typlen[0] == -1)
+ *into = PointerGetDatum(PG_DETOAST_DATUM_COPY(*from));
+ else
+ *into = datumCopy(*from, false, typinfo->typlen[0]);
+ }
+ else
+ *into = *from;
+ }
+ }
+ break;
+
+ case PARTITION_STRAT_RANGE:
+ result->rangeminnull = (bool *) palloc0(src->numpartitions
+ * sizeof(bool));
+ result->rangemaxnull = (bool *) palloc0(src->numpartitions
+ * sizeof(bool));
+ for (i = 0; i < partnatts; i++)
+ {
+ result->rangemins[i] = (Datum *) palloc0(src->numpartitions
+ * sizeof(Datum));
+ result->rangemaxs[i] = (Datum *) palloc0(src->numpartitions
+ * sizeof(Datum));
+ }
+
+ for (i = 0; i < src->numpartitions; i++)
+ {
+ result->oids[i] = src->oids[i];
+ result->rangeminnull[i] = src->rangeminnull[i];
+ result->rangemaxnull[i] = src->rangemaxnull[i];
+
+ for (j = 0; j < partnatts; j++)
+ {
+ if (!result->rangeminnull[i])
+ {
+ Datum *from = &src->rangemins[j][i];
+ Datum *into = &result->rangemins[j][i];
+
+ if (!typinfo->typbyval[j])
+ {
+ if (typinfo->typlen[j] == -1)
+ *into = PointerGetDatum(PG_DETOAST_DATUM_COPY(*from));
+ else
+ *into = datumCopy(*from, false, typinfo->typlen[j]);
+ }
+ else
+ *into = *from;
+ }
+
+ if (!result->rangemaxnull[i])
+ {
+ Datum *from = &src->rangemaxs[j][i];
+ Datum *into = &result->rangemaxs[j][i];
+
+ if (!typinfo->typbyval[j])
+ {
+ if (typinfo->typlen[j] == -1)
+ *into = PointerGetDatum(PG_DETOAST_DATUM_COPY(*from));
+ else
+ *into = datumCopy(*from, false, typinfo->typlen[j]);
+ }
+ else
+ *into = *from;
+ }
+ }
+ }
+ break;
+ }
+
+ return result;
+}
+
+/*
+ * RelationGetPartitionInfo -- get the PartitionInfo containing info of
+ * existing partitions of a partitioned table
+ *
+ * We cache the partitions of relation as read from pg_partition. The cached
+ * info includes the array(s) of bounds, OIDs, etc. If relation is not
+ * partitioned, simply return NULL. Otherwise, the info is copied/created
+ * into/in the caller's memory context. (We don't want to return a pointer to
+ * the relcache copy, since it could disappear due to relcache invalidation.)
+ */
+PartitionInfo *
+RelationGetPartitionInfo(Relation relation)
+{
+ int i,
+ j;
+ int numparts = 0;
+ int partnatts = relation->rd_pkey->partnatts;
+ char strategy = relation->rd_pkey->partstrategy;
+ MemoryContext oldcxt;
+ PartitionInfo *result = NULL;
+ PartitionBoundInfo **partitions = NULL;
+ PartitionKeyTypeInfo *typinfo;
+
+ /* Quick exit if there is nothing to do. */
+ if (relation->rd_pkeytuple == NULL)
+ return NULL;
+
+ /* Get the type info for partition keys. */
+ typinfo = get_key_type_info(relation);
+
+ /* Quick copy and exit if we already computed the result. */
+ if (relation->rd_partitions != NULL)
+ return CopyPartitionInfo(relation->rd_partitions,
+ relation->rd_pkey->partnatts,
+ typinfo);
+
+ /*
+ * Either the first time building the list or the cached one was
+ * recently invalidated, so build a new.
+ */
+ partitions = GetPartitionBounds(relation, &numparts);
+
+ result = (PartitionInfo *) palloc0(sizeof(PartitionInfo));
+ result->numpartitions = numparts;
+ result->strategy = strategy;
+
+ /* Found some partitions. Now build a list in the caller's context. */
+ if (result->numpartitions)
+ {
+ result->oids = (Oid *) palloc0(numparts * sizeof(Oid));
+
+ switch (result->strategy)
+ {
+ case PARTITION_STRAT_LIST:
+ /* Simply copy list partitions. */
+ result->listnvalues = (int *) palloc0(numparts * sizeof(int));
+ result->listvalues = (Datum **) palloc0(numparts * sizeof(Datum *));
+
+ for (i = 0; i < numparts; i++)
+ {
+ result->oids[i] = partitions[i]->oid;
+ result->listnvalues[i] = partitions[i]->listnvalues;
+ result->listvalues[i] = (Datum *)
+ palloc0(partitions[i]->listnvalues * sizeof(Datum));
+ for (j = 0; j < result->listnvalues[i]; j++)
+ {
+ Datum *from = &partitions[i]->listvalues[j];
+ Datum *into = &result->listvalues[i][j];
+
+ if (!typinfo->typbyval[0])
+ {
+ if (typinfo->typlen[0] == -1)
+ *into = PointerGetDatum(PG_DETOAST_DATUM_COPY(*from));
+ else
+ *into = datumCopy(*from, false, typinfo->typlen[0]);
+ }
+ else
+ *into = *from;
+ }
+ }
+ break;
+
+ case PARTITION_STRAT_RANGE:
+ /* Sort on rangemax. */
+ qsort_arg(partitions, numparts, sizeof(PartitionBoundInfo *),
+ range_partition_cmp_max,
+ relation->rd_partsupfuncs);
+ /* Copy */
+ result->rangeminnull = (bool *) palloc0(numparts * sizeof(bool));
+ result->rangemaxnull = (bool *) palloc0(numparts * sizeof(bool));
+ for (i = 0; i < partnatts; i++)
+ {
+ result->rangemins[i] = (Datum *) palloc0(numparts * sizeof(Datum));
+ result->rangemaxs[i] = (Datum *) palloc0(numparts * sizeof(Datum));
+ }
+
+ for (i = 0; i < numparts; i++)
+ {
+ result->oids[i] = partitions[i]->oid;
+ result->rangeminnull[i] = partitions[i]->rangeminnull;
+ result->rangemaxnull[i] = partitions[i]->rangemaxnull;
+
+ for (j = 0; j < partnatts; j++)
+ {
+ if (!result->rangeminnull[i])
+ {
+ Datum *from = &partitions[i]->rangemins[j];
+ Datum *into = &result->rangemins[j][i];
+
+ if (!typinfo->typbyval[j])
+ {
+ if (typinfo->typlen[j] == -1)
+ *into = PointerGetDatum(PG_DETOAST_DATUM_COPY(*from));
+ else
+ *into = datumCopy(*from, false, typinfo->typlen[j]);
+ }
+ else
+ *into = *from;
+ }
+
+ if (!result->rangemaxnull[i])
+ {
+ Datum *from = &partitions[i]->rangemaxs[j];
+ Datum *into = &result->rangemaxs[j][i];
+
+ if (!typinfo->typbyval[j])
+ {
+ if (typinfo->typlen[j] == -1)
+ *into = PointerGetDatum(PG_DETOAST_DATUM_COPY(*from));
+ else
+ *into = datumCopy(*from, false, typinfo->typlen[j]);
+ }
+ else
+ *into = *from;
+ }
+ }
+ }
+ break;
+ }
+ free_partitions(partitions, numparts);
+ }
+
+ /* Save a copy in the relcache entry of relation. */
+
+ /*
+ * Create a memory context for caching the just created list of existing
+ * partitions and copy the list. This cached context turns out to be a
+ * convenience when dropping the cached list in RelationDestroyRelation.
+ */
+ relation->rd_partlistcxt = AllocSetContextCreate(CacheMemoryContext,
+ RelationGetRelationName(relation),
+ ALLOCSET_DEFAULT_MINSIZE,
+ ALLOCSET_DEFAULT_INITSIZE,
+ ALLOCSET_DEFAULT_MAXSIZE);
+
+ oldcxt = MemoryContextSwitchTo(relation->rd_partlistcxt);
+ relation->rd_partitions = CopyPartitionInfo(result,
+ relation->rd_pkey->partnatts,
+ typinfo);
+ MemoryContextSwitchTo(oldcxt);
+
+ return result;
+}
+
+/*
+ * RelationBuildPartitionKey - initialize partition key related fields
+ *
+ * Sets the following fields of relation:
+ *
+ * rd_pkey The Form_pg_partitioned_rel entry
+ * rd_pkeytuple HeapTuple of the same
+ * rd_pkeycxt A long-lived context for the following items
+ * rd_partopfamily Operator family per key column
+ * rd_partopcintype Operator input type per key column
+ * rd_partsupfuncs Comparison functions per key column (support funcs)
+ * rd_partexprs Simply set to NIL. See RelationGetPartitionExpression
+ */
+static void
+RelationBuildPartitionKey(Relation relation)
+{
+ Relation pg_partitioned_rel;
+ HeapTuple tuple;
+ oidvector *partclass;
+ char partstrategy;
+ int partnatts;
+ int i;
+ Datum datum;
+ bool isnull;
+ MemoryContext pkeycxt;
+ MemoryContext oldcxt;
+
+ tuple = SearchSysCache1(PARTITIONEDRELID, RelationGetRelid(relation));
+
+ /* pg_partitioned_rel entry just got dropped (shouldn't be the case)*/
+ if (!HeapTupleIsValid(tuple))
+ {
+ /* Safety! */
+ relation->rd_pkeytuple = NULL;
+ return;
+ }
+
+ /* Keep a copy of pg_partitioned_rel entry in the relation descriptor. */
+ oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
+ relation->rd_pkeytuple = heap_copytuple(tuple);
+ relation->rd_pkey = (Form_pg_partitioned_rel) GETSTRUCT(relation->rd_pkeytuple);
+ MemoryContextSwitchTo(oldcxt);
+
+ /* pg_partitioned_rel tuple */
+ ReleaseSysCache(tuple);
+
+ partstrategy = relation->rd_pkey->partstrategy;
+ partnatts = relation->rd_pkey->partnatts;
+
+ /* Use partclass to figure out compare functions for each key column. */
+ pg_partitioned_rel = heap_open(PartitionedRelRelationId, AccessShareLock);
+
+ datum = fastgetattr(tuple,
+ Anum_pg_partitioned_rel_partclass,
+ RelationGetDescr(pg_partitioned_rel),
+ &isnull);
+
+ Assert(!isnull);
+ partclass = (oidvector *) DatumGetPointer(datum);
+
+ heap_close(pg_partitioned_rel, AccessShareLock);
+
+ /*
+ * Make the private context to cache the items we compute below. The reason
+ * we need a context, and not just a couple of pallocs, is so that we won't
+ * leak any subsidiary info attached to fmgr lookup records.
+ *
+ * Context parameters are set on the assumption that it'll probably not
+ * contain much data.
+ */
+ pkeycxt = AllocSetContextCreate(CacheMemoryContext,
+ RelationGetRelationName(relation),
+ ALLOCSET_SMALL_MINSIZE,
+ ALLOCSET_SMALL_INITSIZE,
+ ALLOCSET_SMALL_MAXSIZE);
+ relation->rd_pkeycxt = pkeycxt;
+
+ relation->rd_partopfamily = (Oid *) MemoryContextAllocZero(pkeycxt,
+ partnatts * sizeof(Oid));
+ relation->rd_partopcintype = (Oid *) MemoryContextAllocZero(pkeycxt,
+ partnatts * sizeof(Oid));
+ relation->rd_partsupfuncs = (FmgrInfo *) MemoryContextAllocZero(pkeycxt,
+ partnatts * sizeof(FmgrInfo));
+
+ /* Determine the opfamily and opcintype. */
+ for (i = 0; i < partnatts; i++)
+ {
+ HeapTuple tuple;
+ Form_pg_opclass form;
+ Oid funcid;
+
+ tuple = SearchSysCache(CLAOID,
+ ObjectIdGetDatum(partclass->values[i]),
+ 0, 0, 0);
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "cache lookup failed for opclass %u",
+ partclass->values[i]);
+
+ form = (Form_pg_opclass) GETSTRUCT(tuple);
+
+ relation->rd_partopfamily[i] = form->opcfamily;
+ relation->rd_partopcintype[i] = form->opcintype;
+
+ /*
+ * cache a btree support function for inter-partition comparisons
+ * as well as comparing a new tuple against partition bounds.
+ */
+ funcid = get_opfamily_proc(form->opcfamily,
+ form->opcintype, form->opcintype,
+ BTORDER_PROC);
+
+ fmgr_info(funcid, &relation->rd_partsupfuncs[i]);
+
+ ReleaseSysCache(tuple);
+ }
+
+ /* RelationGetPartitionExpressions() caches its result here. */
+ relation->rd_partexprs = NIL;
+}
/*
* Routines to support ereport() reports of relation-related errors
diff --git a/src/backend/utils/cache/syscache.c b/src/backend/utils/cache/syscache.c
index efce7b9..3aa2d43 100644
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -47,6 +47,8 @@
#include "catalog/pg_opclass.h"
#include "catalog/pg_operator.h"
#include "catalog/pg_opfamily.h"
+#include "catalog/pg_partition.h"
+#include "catalog/pg_partitioned_rel.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_range.h"
#include "catalog/pg_rewrite.h"
@@ -567,6 +569,28 @@ static const struct cachedesc cacheinfo[] = {
},
8
},
+ {PartitionRelationId, /* PARTITIONID */
+ PartitionIdIndexId,
+ 1,
+ {
+ Anum_pg_partition_partitionid,
+ 0,
+ 0,
+ 0
+ },
+ 64
+ },
+ {PartitionedRelRelationId, /* PARTITIONEDRELID */
+ PartitionedRelrelidIndexId,
+ 1,
+ {
+ Anum_pg_partitioned_rel_partrelid,
+ 0,
+ 0,
+ 0
+ },
+ 64
+ },
{ProcedureRelationId, /* PROCNAMEARGSNSP */
ProcedureNameArgsNspIndexId,
3,
diff --git a/src/backend/utils/misc/Makefile b/src/backend/utils/misc/Makefile
index 7889101..473d071 100644
--- a/src/backend/utils/misc/Makefile
+++ b/src/backend/utils/misc/Makefile
@@ -14,7 +14,7 @@ include $(top_builddir)/src/Makefile.global
override CPPFLAGS := -I. -I$(srcdir) $(CPPFLAGS)
-OBJS = guc.o help_config.o pg_rusage.o ps_status.o rls.o \
+OBJS = guc.o help_config.o partition.o pg_rusage.o ps_status.o rls.o \
sampling.o superuser.o timeout.o tzparser.o
# This location might depend on the installation directories. Therefore
diff --git a/src/backend/utils/misc/partition.c b/src/backend/utils/misc/partition.c
new file mode 100644
index 0000000..301d6af
--- /dev/null
+++ b/src/backend/utils/misc/partition.c
@@ -0,0 +1,465 @@
+/*-------------------------------------------------------------------------
+ *
+ * partition.c
+ * Partitioning related utility functions.
+ *
+ * Portions Copyright (c) 1996-2015, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ * src/backend/utils/misc/partition.c
+ *
+ *-------------------------------------------------------------------------
+*/
+#include "postgres.h"
+
+#include "catalog/heap.h"
+#include "catalog/pg_collation.h"
+#include "executor/executor.h"
+#include "miscadmin.h"
+#include "nodes/nodeFuncs.h"
+#include "utils/partition.h"
+#include "utils/lsyscache.h"
+#include "utils/relcache.h"
+
+static Oid find_partition_for_tuple_recurse(Relation rel, TupleTableSlot *slot,
+ EState *estate);
+static Oid find_list_partition_for_tuple(Relation rel, PartitionInfo *pinfo,
+ Datum value);
+static Oid find_range_partition_for_tuple(Relation rel, PartitionInfo *pinfo,
+ Datum *values);
+
+/*
+ * get_partition_key_type_info
+ *
+ * Returns a PartitionKeyTypeInfo object that includes type info
+ * for individual partition key attributes.
+ *
+ * Separated out to avoid repeating the code.
+ */
+PartitionKeyTypeInfo *get_key_type_info(Relation rel)
+{
+ int i;
+ int partnatts;
+ AttrNumber *partattrs;
+ AttrNumber attno;
+ List *partexprs;
+ ListCell *partexprs_item;
+ PartitionKeyTypeInfo *result;
+
+ result = (PartitionKeyTypeInfo *) palloc0(sizeof(PartitionKeyTypeInfo));
+
+ partnatts = rel->rd_pkey->partnatts;
+ partattrs = rel->rd_pkey->partkey.values;
+ partexprs = RelationGetPartitionExpressions(rel);
+
+ partexprs_item = list_head(partexprs);
+ for (i = 0; i < partnatts; i++)
+ {
+ attno = partattrs[i];
+
+ if (attno != InvalidAttrNumber)
+ {
+ result->typid[i] = rel->rd_att->attrs[attno - 1]->atttypid;
+ result->typmod[i] = rel->rd_att->attrs[attno - 1]->atttypmod;
+ }
+ else
+ {
+ result->typid[i] = exprType(lfirst(partexprs_item));
+ result->typmod[i] = exprTypmod(lfirst(partexprs_item));
+ partexprs_item = lnext(partexprs_item);
+ }
+ get_typlenbyvalalign(result->typid[i],
+ &result->typlen[i],
+ &result->typbyval[i],
+ &result->typalign[i]);
+ }
+
+ return result;
+}
+
+/*
+ * range_partition_cmp_max - compare two range partitions using
+ * their max bounds
+ *
+ * 'a' and 'b' point to the range partitions being compared.
+ *
+ * 'arg' is an array of pointers to FmgrInfo structs, one for each
+ * comparison function corresponding to partition key columns.
+ */
+int32
+range_partition_cmp_max(const void *a, const void *b, void *arg)
+{
+ int i;
+ int32 result;
+ FmgrInfo *cmpfn = (FmgrInfo *) arg;
+ int natts = (*(const PartitionBoundInfo **) a)->partnatts;
+ const PartitionBoundInfo *arg1 = *(const PartitionBoundInfo **) a;
+ const PartitionBoundInfo *arg2 = *(const PartitionBoundInfo **) b;
+
+ /* rangemaxnull implies rangemax is +INFINITY */
+
+ /* arg1 > any, given it is an existing partition */
+ if (arg1->rangemaxnull && arg1->oid != InvalidOid)
+ return +1;
+
+ /* arg2 > any, given it is an existing partition */
+ if (arg2->rangemaxnull && arg2->oid != InvalidOid)
+ return -1;
+
+ for (i = 0; i < natts; i++)
+ {
+ result = DatumGetInt32(FunctionCall2Coll(&cmpfn[i],
+ DEFAULT_COLLATION_OID,
+ arg1->rangemaxs[i], arg2->rangemaxs[i]));
+
+ /* consider multicolumn range partitions */
+ if (!result)
+ continue;
+ else
+ return result;
+ }
+
+ /*
+ * A new partition being added is assumed to order before an existing
+ * partition with the equal bound for the assumptions made in
+ * CheckPartitionAgainstExisting() about partition ordering.
+ *
+ * XXX - clarify the assumption?
+ */
+ if (arg1->oid == InvalidOid)
+ return -1;
+
+ if (arg2->oid == InvalidOid)
+ return +1;
+
+ return 0;
+}
+
+/*
+ * range_partition_cmp_min - compare two range partitions using
+ * their min bounds
+ *
+ * 'a' and 'b' point to the range partitions being compared.
+ *
+ * 'arg' is an array of pointers to FmgrInfo structs, one for each
+ * comparison function corresponding to partition key columns.
+ */
+int32
+range_partition_cmp_min(const void *a, const void *b, void *arg)
+{
+ int i;
+ int32 result;
+ FmgrInfo *cmpfn = (FmgrInfo *) arg;
+ int natts = (*(const PartitionBoundInfo **) a)->partnatts;
+ const PartitionBoundInfo *arg1 = *(const PartitionBoundInfo **) a;
+ const PartitionBoundInfo *arg2 = *(const PartitionBoundInfo **) b;
+
+ /* rangeminnull implies rangemin is -INFINITY */
+
+ /* arg1 < any, given it is an existing partition */
+ if (arg1->rangeminnull && arg1->oid != InvalidOid)
+ return -1;
+
+ /* arg2 < any, given it is an existing partition*/
+ if (arg2->rangeminnull && arg2->oid != InvalidOid)
+ return +1;
+
+ for (i = 0; i < natts; i++)
+ {
+ result = DatumGetInt32(FunctionCall2Coll(&cmpfn[i],
+ DEFAULT_COLLATION_OID,
+ arg1->rangemins[i], arg2->rangemins[i]));
+
+ /* Consider multicolumn range partitions.*/
+ if (!result)
+ continue;
+ else
+ return result;
+ }
+
+ /*
+ * A new partition being added is assumed to order after an existing
+ * partition with the equal bound for the assumptions made in
+ * CheckPartitionAgainstExisting() about partition ordering.
+ *
+ * XXX - clarify the assumption?
+ */
+ if (arg1->oid == InvalidOid)
+ return +1;
+
+ if (arg2->oid == InvalidOid)
+ return -1;
+
+ return 0;
+}
+
+/*
+ * range_partition_cmp_bounds - compare bounds (min or max) of two
+ * partitions that are both known not NULL
+ *
+ * 'arg1' and 'arg2' are datum arrays, each 'natts' datums long.
+ * They could be a min or max bound, each belonging to a differnt
+ * partition.
+ *
+ * 'cmpfn' is an array of pointers to FmgrInfo structs, one for each
+ * comparison function corresponding to partition key columns.
+ *
+ * Note: This functions is directly called using rangemins/rangemaxs
+ * that are known not NULL (applies to arg1 and/or arg2).
+ */
+int32
+range_partition_cmp_bounds(const Datum *arg1, const Datum *arg2,
+ int natts,
+ FmgrInfo *cmpfn)
+{
+ int i;
+ int32 result;
+
+ for (i = 0; i < natts; i++)
+ {
+ result = DatumGetInt32(FunctionCall2Coll(&cmpfn[i],
+ DEFAULT_COLLATION_OID,
+ arg1[i], arg2[i]));
+
+ /* consider multicolumn range partitions */
+ if (!result)
+ continue;
+ else
+ return result;
+ }
+
+ return 0;
+}
+
+/*
+ * list_partitions_overlap - does list partition a overlap b?
+ *
+ * 'arg' is an array of pointers to FmgrInfo structs, one for each
+ * comparison function corresponding to partition key columns.
+ */
+bool
+list_partition_overlaps(PartitionBoundInfo *a, PartitionBoundInfo *b,
+ FmgrInfo *cmpfn)
+{
+ int i,
+ j;
+ int32 result;
+
+ /* For each value in a's list, check if it's equal to any of b's */
+ for (i = 0; i < a->listnvalues; i++)
+ {
+ Datum aDatum = a->listvalues[i];
+
+ for (j = 0; j < b->listnvalues; j++)
+ {
+ /* Note: There is only ever one cmpfn for list partitioning. */
+ result = DatumGetInt32(FunctionCall2Coll(&cmpfn[0],
+ DEFAULT_COLLATION_OID,
+ aDatum, b->listvalues[j]));
+ if (!result)
+ return true;
+ }
+ }
+
+ return false;
+}
+
+/*
+ * get_partition_for_tuple - entry point for recursively determining
+ * the target partition for tuple.
+ */
+Oid
+find_partition_for_tuple(Relation rel,
+ PartitionKeyInfo *pkinfo,
+ PartitionInfo *pinfo,
+ TupleTableSlot *slot,
+ EState *estate)
+{
+ int i;
+ Oid targetOid,
+ relid = RelationGetRelid(rel);
+ Datum values[PARTITION_MAX_KEYS];
+ bool isnull[PARTITION_MAX_KEYS];
+ TupleTableSlot *ecxt_scantuple;
+
+ /* Extract partition key column values from tuple */
+ Assert(GetPerTupleExprContext(estate)->ecxt_scantuple == slot);
+ FormPartitionKeyDatum(pkinfo,
+ slot,
+ estate,
+ values,
+ isnull);
+
+ /* For time being, disallow nulls in partition key cols. */
+ for (i = 0; i < pkinfo->pi_NumKeyAttrs; i++)
+ if (isnull[i])
+ ereport(ERROR,
+ (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+ errmsg("null value for partition key column \"%s\" not allowed",
+ get_attname(relid, pkinfo->pi_KeyAttrNumbers[i]))));
+
+ /* Find out actual partition. */
+ switch (pinfo->strategy)
+ {
+ case PARTITION_STRAT_LIST:
+ targetOid = find_list_partition_for_tuple(rel, pinfo, values[0]);
+ break;
+
+ case PARTITION_STRAT_RANGE:
+ targetOid = find_range_partition_for_tuple(rel, pinfo, values);
+ break;
+ }
+
+ /*
+ * Recurse to find actual partition if this partition itself is
+ * partitioned.
+ */
+ if (targetOid && is_partitioned(targetOid))
+ {
+ Relation relation;
+ relation = heap_open(targetOid, RowExclusiveLock);
+ targetOid = find_partition_for_tuple_recurse(relation, slot, estate);
+ /* XXX - should be closed now? */
+ heap_close(relation, RowExclusiveLock);
+ }
+
+ return targetOid;
+}
+
+/*
+ * find_partition_for_tuple_recurse - performs recursion for when a partition
+ * itself is found to be partitioned.
+ */
+
+static Oid
+find_partition_for_tuple_recurse(Relation rel,
+ TupleTableSlot *slot,
+ EState *estate)
+{
+ PartitionKeyInfo *pkinfo;
+ PartitionInfo *pinfo;
+
+ check_stack_depth();
+
+ pkinfo = BuildPartitionKeyInfo(rel);
+ pinfo = RelationGetPartitionInfo(rel);
+
+ return find_partition_for_tuple(rel, pkinfo, pinfo, slot, estate);
+}
+
+/*
+ * find_list_partition_for_tuple - find the list partition for value
+ */
+static Oid
+find_list_partition_for_tuple(Relation rel, PartitionInfo *pinfo,
+ Datum value)
+{
+ int i,
+ j;
+ int32 result;
+ FmgrInfo cmpfn = rel->rd_partsupfuncs[0];
+
+ if (pinfo->numpartitions == 0)
+ return InvalidOid;
+
+ for (i = 0; i < pinfo->numpartitions; i++)
+ {
+ for (j = 0; j < pinfo->listnvalues[i]; j++)
+ {
+ result = DatumGetInt32(FunctionCall2Coll(&cmpfn,
+ DEFAULT_COLLATION_OID,
+ pinfo->listvalues[i][j], value));
+
+ if (!result)
+ return pinfo->oids[i];
+ }
+ }
+
+ return InvalidOid;
+}
+
+/*
+ * find_range_partition_for_tuple - find the range partition for values
+ */
+static Oid
+find_range_partition_for_tuple(Relation rel, PartitionInfo *pinfo,
+ Datum *values)
+{
+ int low, high, idx,
+ i,
+ partnatts = rel->rd_pkey->partnatts;
+ FmgrInfo *cmpfn = rel->rd_partsupfuncs;
+ int32 cmpval;
+ Datum *rangebound;
+
+ if (pinfo->numpartitions == 0)
+ return InvalidOid;
+
+ rangebound = (Datum *) palloc0(partnatts * sizeof(Datum));
+
+ /* Good ol' bsearch */
+ low = 0;
+ high = pinfo->numpartitions - 1;
+ while (low <= high)
+ {
+ idx = (low + high) / 2;
+
+ /* Note: Only the last partition is allowed to have null rangemax. */
+ if (pinfo->rangemaxnull[idx])
+ {
+ for (i = 0; i < partnatts; i++)
+ rangebound[i] = pinfo->rangemins[i][idx];
+
+ cmpval = range_partition_cmp_bounds(values, rangebound,
+ partnatts, cmpfn);
+ if (cmpval >= 0)
+ return pinfo->oids[idx];
+ else
+ return InvalidOid;
+ }
+ else
+ {
+ for (i = 0; i < partnatts; i++)
+ rangebound[i] = pinfo->rangemaxs[i][idx];
+
+ cmpval = range_partition_cmp_bounds(values, rangebound,
+ partnatts, cmpfn);
+ if (!cmpval)
+ {
+ for (i = 0; i < partnatts; i++)
+ rangebound[i] = pinfo->rangemins[i][idx+1];
+
+ cmpval = range_partition_cmp_bounds(values, rangebound,
+ partnatts, cmpfn);
+ if (!cmpval)
+ return pinfo->oids[idx+1];
+ else /* There is a gap between this and the next. */
+ return InvalidOid;
+ }
+ else if (cmpval < 0)
+ {
+ /* Note: only the first partition is allowed to have null rangemin. */
+ if (pinfo->rangeminnull[idx])
+ return pinfo->oids[idx];
+ else
+ {
+ for (i = 0; i < partnatts; i++)
+ rangebound[i] = pinfo->rangemins[i][idx];
+
+ cmpval = range_partition_cmp_bounds(values, rangebound,
+ partnatts, cmpfn);
+ if (cmpval >= 0)
+ return pinfo->oids[idx];
+ else
+ high = idx - 1;
+ }
+ }
+ else
+ low = idx + 1;
+ }
+ }
+
+ return InvalidOid;
+}
diff --git a/src/include/catalog/heap.h b/src/include/catalog/heap.h
index e6ac394..fb1af70 100644
--- a/src/include/catalog/heap.h
+++ b/src/include/catalog/heap.h
@@ -17,6 +17,7 @@
#include "catalog/indexing.h"
#include "catalog/objectaddress.h"
#include "parser/parse_node.h"
+#include "nodes/execnodes.h"
typedef struct RawColumnDefault
@@ -133,5 +134,20 @@ extern void CheckAttributeType(const char *attname,
Oid atttypid, Oid attcollation,
List *containing_rowtypes,
bool allow_system_table_mods);
+extern void StorePartitionKey(Relation rel, PartitionKeyInfo *pkinfo,
+ Oid *partClassOids, char strategy);
+extern void RemovePartitionKeyByRelId(Oid relid);
+extern PartitionKeyInfo *BuildPartitionKeyInfo(Relation rel);
+extern void RemovePartitionBoundByRelId(Oid relid);
+extern void StorePartitionBounds(Relation childrel,
+ Relation parentrel,
+ int listnvalues,
+ Datum *datum);
+extern void SetRelationIsPartition(Relation rel, bool ispartition);
+extern void FormPartitionKeyDatum(PartitionKeyInfo *pkinfo,
+ TupleTableSlot *slot,
+ EState *estate,
+ Datum *values,
+ bool *isnull);
#endif /* HEAP_H */
diff --git a/src/include/catalog/indexing.h b/src/include/catalog/indexing.h
index c38958d..1f73595 100644
--- a/src/include/catalog/indexing.h
+++ b/src/include/catalog/indexing.h
@@ -316,6 +316,14 @@ DECLARE_UNIQUE_INDEX(pg_replication_origin_roiident_index, 6001, on pg_replicati
DECLARE_UNIQUE_INDEX(pg_replication_origin_roname_index, 6002, on pg_replication_origin using btree(roname text_pattern_ops));
#define ReplicationOriginNameIndex 6002
+DECLARE_UNIQUE_INDEX(pg_partitioned_rel_partrelid_index, 3315, on pg_partitioned_rel using btree(partrelid oid_ops));
+#define PartitionedRelrelidIndexId 3315
+
+DECLARE_UNIQUE_INDEX(pg_partition_partitionid_index, 3316, on pg_partition using btree(partitionid oid_ops));
+#define PartitionIdIndexId 3316
+DECLARE_INDEX(pg_partition_parent_index, 3317, on pg_partition using btree(partparent oid_ops));
+#define PartitionParentIndexId 3317
+
/* last step of initialization script: build the indexes declared above */
BUILD_INDICES
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index e526cd9..a9a9e0d 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -65,7 +65,8 @@ CATALOG(pg_class,1259) BKI_BOOTSTRAP BKI_ROWTYPE_OID(83) BKI_SCHEMA_MACRO
bool relhasrules; /* has (or has had) any rules */
bool relhastriggers; /* has (or has had) any TRIGGERs */
bool relhassubclass; /* has (or has had) derived classes */
- bool relrowsecurity; /* row security is enabled or not */
+ bool relispartition; /* is a RELKIND_RELATION relation a partition */
+ bool relrowsecurity; /* row security is enabled or not */
bool relispopulated; /* matview currently holds query results */
char relreplident; /* see REPLICA_IDENTITY_xxx constants */
TransactionId relfrozenxid; /* all Xids < this are frozen in this rel */
@@ -95,7 +96,7 @@ typedef FormData_pg_class *Form_pg_class;
* ----------------
*/
-#define Natts_pg_class 30
+#define Natts_pg_class 31
#define Anum_pg_class_relname 1
#define Anum_pg_class_relnamespace 2
#define Anum_pg_class_reltype 3
@@ -119,13 +120,14 @@ typedef FormData_pg_class *Form_pg_class;
#define Anum_pg_class_relhasrules 21
#define Anum_pg_class_relhastriggers 22
#define Anum_pg_class_relhassubclass 23
-#define Anum_pg_class_relrowsecurity 24
-#define Anum_pg_class_relispopulated 25
-#define Anum_pg_class_relreplident 26
-#define Anum_pg_class_relfrozenxid 27
-#define Anum_pg_class_relminmxid 28
-#define Anum_pg_class_relacl 29
-#define Anum_pg_class_reloptions 30
+#define Anum_pg_class_relispartition 24
+#define Anum_pg_class_relrowsecurity 25
+#define Anum_pg_class_relispopulated 26
+#define Anum_pg_class_relreplident 27
+#define Anum_pg_class_relfrozenxid 28
+#define Anum_pg_class_relminmxid 29
+#define Anum_pg_class_relacl 30
+#define Anum_pg_class_reloptions 31
/* ----------------
* initial contents of pg_class
@@ -140,13 +142,13 @@ typedef FormData_pg_class *Form_pg_class;
* Note: "3" in the relfrozenxid column stands for FirstNormalTransactionId;
* similarly, "1" in relminmxid stands for FirstMultiXactId
*/
-DATA(insert OID = 1247 ( pg_type PGNSP 71 0 PGUID 0 0 0 0 0 0 0 f f p r 30 0 t f f f f f t n 3 1 _null_ _null_ ));
+DATA(insert OID = 1247 ( pg_type PGNSP 71 0 PGUID 0 0 0 0 0 0 0 f f p r 30 0 t f f f f f f t n 3 1 _null_ _null_ ));
DESCR("");
-DATA(insert OID = 1249 ( pg_attribute PGNSP 75 0 PGUID 0 0 0 0 0 0 0 f f p r 21 0 f f f f f f t n 3 1 _null_ _null_ ));
+DATA(insert OID = 1249 ( pg_attribute PGNSP 75 0 PGUID 0 0 0 0 0 0 0 f f p r 21 0 f f f f f f f t n 3 1 _null_ _null_ ));
DESCR("");
-DATA(insert OID = 1255 ( pg_proc PGNSP 81 0 PGUID 0 0 0 0 0 0 0 f f p r 28 0 t f f f f f t n 3 1 _null_ _null_ ));
+DATA(insert OID = 1255 ( pg_proc PGNSP 81 0 PGUID 0 0 0 0 0 0 0 f f p r 28 0 t f f f f f f t n 3 1 _null_ _null_ ));
DESCR("");
-DATA(insert OID = 1259 ( pg_class PGNSP 83 0 PGUID 0 0 0 0 0 0 0 f f p r 30 0 t f f f f f t n 3 1 _null_ _null_ ));
+DATA(insert OID = 1259 ( pg_class PGNSP 83 0 PGUID 0 0 0 0 0 0 0 f f p r 31 0 t f f f f f f t n 3 1 _null_ _null_ ));
DESCR("");
@@ -158,6 +160,7 @@ DESCR("");
#define RELKIND_COMPOSITE_TYPE 'c' /* composite type */
#define RELKIND_FOREIGN_TABLE 'f' /* foreign table */
#define RELKIND_MATVIEW 'm' /* materialized view */
+#define RELKIND_PARTITIONED_REL 'P' /* partitioned table */
#define RELPERSISTENCE_PERMANENT 'p' /* regular table */
#define RELPERSISTENCE_UNLOGGED 'u' /* unlogged permanent table */
diff --git a/src/include/catalog/pg_partition.h b/src/include/catalog/pg_partition.h
new file mode 100644
index 0000000..2c5b736
--- /dev/null
+++ b/src/include/catalog/pg_partition.h
@@ -0,0 +1,61 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_partition.h
+ * definition of the system "partition" relation (pg_partition)
+ * along with the relation's initial contents.
+ *
+ *
+ * Portions Copyright (c) 1996-2015, PostgreSQL Global Development Group
+ *
+ * $PostgreSQL: pgsql/src/include/catalog/pg_partition.h $
+ *
+ * NOTES
+ * the genbki.sh script reads this file and generates .bki
+ * information from the DATA() statements.
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_PARTITION_H
+#define PG_PARTITION_H
+
+#include "catalog/genbki.h"
+
+/* ----------------
+ * pg_partitioned_rel definition. cpp turns this into
+ * typedef struct FormData_pg_partitioned_rel
+ * ----------------
+ */
+#define PartitionRelationId 3309
+
+CATALOG(pg_partition,3309) BKI_WITHOUT_OIDS
+{
+ Oid partitionid; /* partition oid */
+ Oid partparent; /* parent oid */
+
+#ifdef CATALOG_VARLEN /* variable-length fields start here */
+ anyarray partlistvalues; /* list of allowed values of the only
+ * partition column */
+ anyarray partrangebounds; /* list of bounds of ranges of
+ * allowed values per partition key
+ * column */
+#endif
+} FormData_pg_partition;
+
+/* ----------------
+ * Form_pg_partition corresponds to a pointer to a tuple with
+ * the format of pg_partition relation.
+ * ----------------
+ */
+typedef FormData_pg_partition *Form_pg_partition;
+
+/* ----------------
+ * compiler constants for pg_partition
+ * ----------------
+ */
+#define Natts_pg_partition 4
+#define Anum_pg_partition_partitionid 1
+#define Anum_pg_partition_partparent 2
+#define Anum_pg_partition_partlistvalues 3
+#define Anum_pg_partition_partrangebounds 4
+
+#endif /* PG_PARTITION_H */
diff --git a/src/include/catalog/pg_partition_fn.h b/src/include/catalog/pg_partition_fn.h
new file mode 100644
index 0000000..b038aa2
--- /dev/null
+++ b/src/include/catalog/pg_partition_fn.h
@@ -0,0 +1,51 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_partition_fn.h
+ * prototypes for functions in catalog/pg_partition.c
+ *
+ *
+ * Portions Copyright (c) 1996-2015, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/catalog/pg_partition_fn.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_PARTITION_FN_H
+#define PG_PARTITION_FN_H
+
+#include "nodes/pg_list.h"
+#include "nodes/execnodes.h"
+#include "storage/lock.h"
+#include "utils/rel.h"
+
+/* Bound info of a single partition */
+typedef struct PartitionBoundInfo
+{
+ Oid oid; /* This partition's OID */
+ int partnatts; /* qsort_arg needs this to iterate over key columns
+ when comparing two PartitionBoundInfo objects */
+ int listnvalues; /* How many values does listvalues below hold */
+ bool rangeminnull; /* Whether this range partition does not have a
+ min bound */
+ bool rangemaxnull; /* Whether this range partition does not have a
+ max bound */
+ Datum *rangemins; /* A range partition's min bound - one datum per
+ key column */
+ Datum *rangemaxs; /* A range partition's max bound - one datum per
+ key column */
+ Datum *listvalues; /* A list partition's values - the distinct values
+ that the only key column can take */
+} PartitionBoundInfo;
+
+extern PartitionBoundInfo **GetPartitionBounds(Relation rel, int *numparts);
+extern void free_partitions(PartitionBoundInfo **p, int count);
+extern Oid get_partition_parent(Oid relid, bool recurse);
+extern bool IsPartition(Relation rel);
+extern bool is_partition(Oid relid);
+extern bool IsPartitionedRel(Relation rel);
+extern bool is_partitioned(Oid relid);
+extern List *find_partitions(Oid relid, LOCKMODE lockmode);
+extern List *find_all_partitions(Oid relid, LOCKMODE lockmode);
+
+#endif /* PG_PARTITION_FN_H */
diff --git a/src/include/catalog/pg_partitioned_rel.h b/src/include/catalog/pg_partitioned_rel.h
new file mode 100644
index 0000000..dc55bea
--- /dev/null
+++ b/src/include/catalog/pg_partitioned_rel.h
@@ -0,0 +1,66 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_partitioned_rel.h
+ * definition of the system "partitioned" relation (pg_partitioned_rel)
+ * along with the relation's initial contents.
+ *
+ *
+ * Portions Copyright (c) 1996-2015, PostgreSQL Global Development Group
+ *
+ * $PostgreSQL: pgsql/src/include/catalog/pg_partitioned_rel.h $
+ *
+ * NOTES
+ * the genbki.sh script reads this file and generates .bki
+ * information from the DATA() statements.
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_PARTITIONED_REL_H
+#define PG_PARTITIONED_REL_H
+
+#include "catalog/genbki.h"
+
+/* ----------------
+ * pg_partitioned_rel definition. cpp turns this into
+ * typedef struct FormData_pg_partitioned_rel
+ * ----------------
+ */
+#define PartitionedRelRelationId 3308
+
+CATALOG(pg_partitioned_rel,3308) BKI_WITHOUT_OIDS
+{
+ Oid partrelid; /* partitioned table oid */
+ char partstrategy; /* partitioning strategy */
+ int16 partnatts; /* number of partition columns */
+
+ /* variable-length fields start here, but we allow direct access to indkey */
+ int2vector partkey; /* column numbers of partition columns */
+
+#ifdef CATALOG_VARLEN
+ oidvector partclass; /* operator class to compare keys */
+ pg_node_tree partexprs; /* expression trees for partition key members
+ * that are not simple column references; one
+ * for each zero entry in partkey[] */
+#endif
+} FormData_pg_partitioned_rel;
+
+/* ----------------
+ * Form_pg_partitioned_rel corresponds to a pointer to a tuple with
+ * the format of pg_partitioned_rel relation.
+ * ----------------
+ */
+typedef FormData_pg_partitioned_rel *Form_pg_partitioned_rel;
+
+/* ----------------
+ * compiler constants for pg_partitioned_rel
+ * ----------------
+ */
+#define Natts_pg_partitioned_rel 6
+#define Anum_pg_partitioned_rel_partrelid 1
+#define Anum_pg_partitioned_rel_partstrategy 2
+#define Anum_pg_partitioned_rel_partnatts 3
+#define Anum_pg_partitioned_rel_partkey 4
+#define Anum_pg_partitioned_rel_partclass 5
+#define Anum_pg_partitioned_rel_partexprs 6
+
+#endif /* PG_PARTITIONED_REL_H */
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index adae296..b998945 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -42,6 +42,8 @@ extern bool CheckIndexCompatible(Oid oldId,
List *attributeList,
List *exclusionOpNames);
extern Oid GetDefaultOpClass(Oid type_id, Oid am_id);
+extern Oid GetIndexOpClass(List *opclass, Oid attrType, const char *accessMethodName,
+ Oid accessMethodId);
/* commands/functioncmds.c */
extern ObjectAddress CreateFunction(CreateFunctionStmt *stmt, const char *queryString);
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index 193a654..c1d4f48 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -375,6 +375,9 @@ extern void check_exclusion_constraint(Relation heap, Relation index,
ItemPointer tupleid,
Datum *values, bool *isnull,
EState *estate, bool newIndex);
+extern ResultRelInfo *ExecFindPartition(ResultRelInfo *resultRelInfo,
+ TupleTableSlot *slot,
+ EState *estate);
#endif /* EXECUTOR_H */
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 5796de8..a281e21 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -20,6 +20,7 @@
#include "lib/pairingheap.h"
#include "nodes/params.h"
#include "nodes/plannodes.h"
+#include "utils/rel.h"
#include "utils/reltrigger.h"
#include "utils/sortsupport.h"
#include "utils/tuplestore.h"
@@ -76,6 +77,28 @@ typedef struct IndexInfo
bool ii_BrokenHotChain;
} IndexInfo;
+/*
+ * PartitionKeyInfo
+ *
+ * This struct holds the information needed to extract partition
+ * column values from a heap tuple. Also (ab-)used by tablecmds.c to
+ * to pass around the key info when defining a partitioned relation.
+ *
+ * NumKeyAttrs number of columns in partition key
+ * KeyAttrNumbers underlying-rel attribute numbers used as keys
+ * (zeroes indicate expressions)
+ * Expressions expr trees for expression entries, or NIL if none
+ * ExpressionsState exec state for expressions, or NIL if none
+ */
+typedef struct PartitionKeyInfo
+{
+ NodeTag type;
+ int pi_NumKeyAttrs;
+ AttrNumber pi_KeyAttrNumbers[PARTITION_MAX_KEYS];
+ List *pi_Expressions; /* list of Expr */
+ List *pi_ExpressionsState; /* list of ExprState */
+} PartitionKeyInfo;
+
/* ----------------
* ExprContext_CB
*
@@ -341,6 +364,8 @@ typedef struct ResultRelInfo
ProjectionInfo *ri_projectReturning;
ProjectionInfo *ri_onConflictSetProj;
List *ri_onConflictSetWhere;
+ PartitionKeyInfo *ri_PartitionKeyInfo;
+ PartitionInfo *ri_Partitions;
} ResultRelInfo;
/* ----------------
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 748e434..ee3c12d 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -37,6 +37,7 @@ typedef enum NodeTag
T_ResultRelInfo,
T_EState,
T_TupleTableSlot,
+ T_PartitionKeyInfo,
/*
* TAGS FOR PLAN NODES (plannodes.h)
@@ -427,6 +428,10 @@ typedef enum NodeTag
T_OnConflictClause,
T_CommonTableExpr,
T_RoleSpec,
+ T_PartitionBy,
+ T_PartitionElem,
+ T_PartitionValues,
+ T_PartitionDef,
/*
* TAGS FOR REPLICATION GRAMMAR PARSE NODES (replnodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 151c93a..954ef4e 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -689,6 +689,76 @@ typedef struct XmlSerialize
int location; /* token location, or -1 if unknown */
} XmlSerialize;
+/* Partitioning related definitions */
+
+/*
+ * PartitionElem - a partition key column
+ *
+ * CREATE TABLE ... PARTITION BY ... ON (<here>)
+ *
+ * 'name' Name of the table column included in the key
+ * 'expr' Expression node tree of expressional key column
+ * 'opclass' Operator class name associated with the column
+ */
+typedef struct PartitionElem
+{
+ NodeTag type;
+ char *name; /* name of column to partition on, or NULL */
+ Node *expr; /* expression to partition on, or NULL */
+ List *opclass; /* name of desired opclass; NIL = default */
+ int location; /* token location, or -1 if unknown */
+} PartitionElem;
+
+/*
+ * PartitionBy - partition key definition including the strategy
+ *
+ * 'strategy' partition strategy to use (one of the below defined)
+ * 'partParams' List of PartitionElems, one for each key column
+ */
+#define PARTITION_STRAT_LIST 'l'
+#define PARTITION_STRAT_RANGE 'r'
+
+typedef struct PartitionBy
+{
+ NodeTag type;
+ char strategy;
+ List *partParams;
+ int location; /* token location, or -1 if unknown */
+} PartitionBy;
+
+/*
+ * PartitionValues - partition values
+ *
+ * CREATE TABLE ... PARTITION OF ... FOR VALUES (<here>) ...
+ *
+ * 'listvalues' List of values when defining a list partition
+ * 'rangemins' If present, min bound of a range partition, one value per column
+ * 'rangemaxs' If present, max bound of a range partition, one value per column
+ */
+typedef struct PartitionValues
+{
+ NodeTag type;
+ List *listvalues;
+ List *rangemins;
+ List *rangemaxs;
+ int location; /* token location, or -1 if unknown */
+} PartitionValues;
+
+/*
+ * PartitionDef - a single partition definition
+ *
+ * 'name' Name of this partition
+ * 'parent' Name of the parent of this partition
+ * 'values' A PartitionValues struct describing FOR VALUES part of
+ * the definition
+ */
+typedef struct PartitionDef
+{
+ NodeTag type;
+ RangeVar *name;
+ RangeVar *parent;
+ PartitionValues *values;
+} PartitionDef;
/****************************************************************************
* Nodes for a Query tree
@@ -1513,7 +1583,15 @@ typedef enum AlterTableType
AT_ReplicaIdentity, /* REPLICA IDENTITY */
AT_EnableRowSecurity, /* ENABLE ROW SECURITY */
AT_DisableRowSecurity, /* DISABLE ROW SECURITY */
- AT_GenericOptions /* OPTIONS (...) */
+ AT_GenericOptions, /* OPTIONS (...) */
+ AT_AttachPartition, /* CREATE TABLE name PARTITION OF parent
+ * FOR VALUES (...)
+ * or
+ * ALTER TABLE parent ATTACH PARTITION name
+ * FOR VALUES (...)
+ * USING [ TABLE ] table_name */
+ AT_DetachPartition /* ALTER TABLE parent DETACH PARTITION name
+ * [ USING table_name ] */
} AlterTableType;
typedef struct ReplicaIdentityStmt
@@ -1532,6 +1610,8 @@ typedef struct AlterTableCmd /* one subcommand of an ALTER TABLE */
Node *newowner; /* RoleSpec */
Node *def; /* definition of new column, index,
* constraint, or parent table */
+ RangeVar *using_table; /* table to use as source/target for
+ * attach/detach partition sub-commands */
DropBehavior behavior; /* RESTRICT or CASCADE for DROP cases */
bool missing_ok; /* skip error if missing? */
} AlterTableCmd;
@@ -1735,9 +1815,13 @@ typedef struct CreateStmt
{
NodeTag type;
RangeVar *relation; /* relation to create */
+ RangeVar *partitionOf; /* if 'relation' is a partition,
+ * this is the parent */
List *tableElts; /* column definitions (list of ColumnDef) */
List *inhRelations; /* relations to inherit from (list of
* inhRelation) */
+ PartitionValues *partValues; /* partition bounding values */
+ PartitionBy *partitionby; /* partition key definition */
TypeName *ofTypename; /* OF typename */
List *constraints; /* constraints (list of Constraint nodes) */
List *options; /* options from WITH clause */
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 2414069..c6c290d 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -49,6 +49,7 @@ PG_KEYWORD("assertion", ASSERTION, UNRESERVED_KEYWORD)
PG_KEYWORD("assignment", ASSIGNMENT, UNRESERVED_KEYWORD)
PG_KEYWORD("asymmetric", ASYMMETRIC, RESERVED_KEYWORD)
PG_KEYWORD("at", AT, UNRESERVED_KEYWORD)
+PG_KEYWORD("attach", ATTACH, UNRESERVED_KEYWORD)
PG_KEYWORD("attribute", ATTRIBUTE, UNRESERVED_KEYWORD)
PG_KEYWORD("authorization", AUTHORIZATION, TYPE_FUNC_NAME_KEYWORD)
PG_KEYWORD("backward", BACKWARD, UNRESERVED_KEYWORD)
@@ -126,6 +127,7 @@ PG_KEYWORD("delete", DELETE_P, UNRESERVED_KEYWORD)
PG_KEYWORD("delimiter", DELIMITER, UNRESERVED_KEYWORD)
PG_KEYWORD("delimiters", DELIMITERS, UNRESERVED_KEYWORD)
PG_KEYWORD("desc", DESC, RESERVED_KEYWORD)
+PG_KEYWORD("detach", DETACH, UNRESERVED_KEYWORD)
PG_KEYWORD("dictionary", DICTIONARY, UNRESERVED_KEYWORD)
PG_KEYWORD("disable", DISABLE_P, UNRESERVED_KEYWORD)
PG_KEYWORD("discard", DISCARD, UNRESERVED_KEYWORD)
@@ -226,6 +228,7 @@ PG_KEYWORD("left", LEFT, TYPE_FUNC_NAME_KEYWORD)
PG_KEYWORD("level", LEVEL, UNRESERVED_KEYWORD)
PG_KEYWORD("like", LIKE, TYPE_FUNC_NAME_KEYWORD)
PG_KEYWORD("limit", LIMIT, RESERVED_KEYWORD)
+PG_KEYWORD("list", LIST, UNRESERVED_KEYWORD)
PG_KEYWORD("listen", LISTEN, UNRESERVED_KEYWORD)
PG_KEYWORD("load", LOAD, UNRESERVED_KEYWORD)
PG_KEYWORD("local", LOCAL, UNRESERVED_KEYWORD)
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index 5249945..aec586d 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -64,7 +64,9 @@ typedef enum ParseExprKind
EXPR_KIND_ALTER_COL_TRANSFORM, /* transform expr in ALTER COLUMN TYPE */
EXPR_KIND_EXECUTE_PARAMETER, /* parameter value in EXECUTE */
EXPR_KIND_TRIGGER_WHEN, /* WHEN condition in CREATE TRIGGER */
- EXPR_KIND_POLICY /* USING or WITH CHECK expr in policy */
+ EXPR_KIND_POLICY, /* USING or WITH CHECK expr in policy */
+ EXPR_KIND_PARTKEY_EXPRESSION, /* partition key expression */
+ EXPR_KIND_PARTITION_VALUES /* FOR VALUES in CREATE TABLE ... PARTITION OF */
} ParseExprKind;
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index d08add3..f871711 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,4 +26,5 @@ extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
extern List *transformCreateSchemaStmt(CreateSchemaStmt *stmt);
+
#endif /* PARSE_UTILCMD_H */
diff --git a/src/include/pg_config_manual.h b/src/include/pg_config_manual.h
index e278fa0..d7badea 100644
--- a/src/include/pg_config_manual.h
+++ b/src/include/pg_config_manual.h
@@ -46,6 +46,12 @@
#define INDEX_MAX_KEYS 32
/*
+ * Maximum number of columns in a partition key. This is entirely arbitrary
+ * at this point
+ */
+#define PARTITION_MAX_KEYS 16
+
+/*
* Set the upper and lower bounds of sequence values.
*/
#define SEQ_MAXVALUE PG_INT64_MAX
diff --git a/src/include/utils/partition.h b/src/include/utils/partition.h
new file mode 100644
index 0000000..4126f0b
--- /dev/null
+++ b/src/include/utils/partition.h
@@ -0,0 +1,40 @@
+/*-------------------------------------------------------------------------
+ *
+ * partition.h
+ * Header file for utility functions related to partitioning
+ *
+ * Copyright (c) 2007-2015, PostgreSQL Global Development Group
+ *
+ * src/include/utils/partition.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PARTITION_H
+#define PARTITION_H
+
+#include "catalog/pg_partition_fn.h"
+
+typedef struct PartitionKeyTypeInfo
+{
+ Oid typid[PARTITION_MAX_KEYS];
+ int32 typmod[PARTITION_MAX_KEYS];
+ int16 typlen[PARTITION_MAX_KEYS];
+ bool typbyval[PARTITION_MAX_KEYS];
+ char typalign[PARTITION_MAX_KEYS];
+} PartitionKeyTypeInfo;
+
+extern PartitionKeyTypeInfo *get_key_type_info(Relation rel);
+extern int32 range_partition_cmp_max(const void *a, const void *b, void *arg);
+extern int32 range_partition_cmp_min(const void *a, const void *b, void *arg);
+extern int32 range_partition_cmp_bounds(const Datum *lhs, const Datum *rhs,
+ int natts,
+ FmgrInfo *cmpfn);
+extern bool list_partition_overlaps(PartitionBoundInfo *a, PartitionBoundInfo *b,
+ FmgrInfo *cmpfn);
+extern Oid find_partition_for_tuple(Relation rel,
+ PartitionKeyInfo *pkinfo,
+ PartitionInfo *pinfo,
+ TupleTableSlot *slot,
+ EState *estate);
+
+#endif /* PARTITION_H */
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 8a55a09..16118ad 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -18,6 +18,7 @@
#include "catalog/pg_am.h"
#include "catalog/pg_class.h"
#include "catalog/pg_index.h"
+#include "catalog/pg_partitioned_rel.h"
#include "fmgr.h"
#include "nodes/bitmapset.h"
#include "rewrite/prs2lock.h"
@@ -63,6 +64,20 @@ typedef struct RelationAmInfo
FmgrInfo amcanreturn;
} RelationAmInfo;
+/* Details of partitions of a partitioned relation */
+typedef struct PartitionInfo
+{
+ int numpartitions;
+ char strategy;
+ Oid *oids;
+ int *listnvalues;
+ Datum **listvalues;
+ bool *rangeminnull;
+ bool *rangemaxnull;
+ Datum *rangemins[PARTITION_MAX_KEYS];
+ Datum *rangemaxs[PARTITION_MAX_KEYS];
+} PartitionInfo;
+
/*
* Here are the contents of a relation cache entry.
*/
@@ -117,6 +132,20 @@ typedef struct RelationData
Bitmapset *rd_keyattr; /* cols that can be ref'd by foreign keys */
Bitmapset *rd_idattr; /* included in replica identity index */
+ /* Partition key info */
+ Form_pg_partitioned_rel rd_pkey;
+ struct HeapTupleData *rd_pkeytuple;
+
+ MemoryContext rd_pkeycxt; /* memory context for following items */
+ Oid *rd_partopfamily; /* OIDs of op families for each partition col */
+ Oid *rd_partopcintype; /* OIDs of opclass declared input data types */
+ FmgrInfo *rd_partsupfuncs; /* lookup info for support procedures */
+ List *rd_partexprs; /* partition key expressions, if any */
+
+ /* Cached info for partitions of this rel */
+ MemoryContext rd_partlistcxt; /* memory context for the following */
+ PartitionInfo *rd_partitions; /* Partitions of this relation */
+
/*
* rd_options is set whenever rd_rel is loaded into the relcache entry.
* Note that you can NOT look into rd_rel for this data. NULL means "use
diff --git a/src/include/utils/relcache.h b/src/include/utils/relcache.h
index 6953281..3581db0 100644
--- a/src/include/utils/relcache.h
+++ b/src/include/utils/relcache.h
@@ -43,6 +43,12 @@ extern Oid RelationGetReplicaIndex(Relation relation);
extern List *RelationGetIndexExpressions(Relation relation);
extern List *RelationGetIndexPredicate(Relation relation);
+/*
+ * Routines to compute/retrieve cached info of partitioned relations
+ */
+extern List *RelationGetPartitionExpressions(Relation relation);
+extern struct PartitionInfo *RelationGetPartitionInfo(Relation relation);
+
typedef enum IndexAttrBitmapKind
{
INDEX_ATTR_BITMAP_ALL,
diff --git a/src/include/utils/syscache.h b/src/include/utils/syscache.h
index 18404e2..14bb619 100644
--- a/src/include/utils/syscache.h
+++ b/src/include/utils/syscache.h
@@ -72,6 +72,8 @@ enum SysCacheIdentifier
OPEROID,
OPFAMILYAMNAMENSP,
OPFAMILYOID,
+ PARTITIONID,
+ PARTITIONEDRELID,
PROCNAMEARGSNSP,
PROCOID,
RANGETYPE,
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index eb0bc88..29abb9e 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -117,6 +117,8 @@ pg_namespace|t
pg_opclass|t
pg_operator|t
pg_opfamily|t
+pg_partition|t
+pg_partitioned_rel|t
pg_pltemplate|t
pg_policy|t
pg_proc|t
^ permalink raw reply [nested|flat] 161+ messages in thread
* Re: Declarative partitioning
@ 2015-08-18 11:02 Thom Brown <thom@linux.com>
parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
9 siblings, 1 reply; 161+ messages in thread
From: Thom Brown @ 2015-08-18 11:02 UTC (permalink / raw)
To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; +Cc: pgsql-hackers
On 18 August 2015 at 11:30, Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
wrote:
>
> Hi,
>
> I would like propose $SUBJECT for this development cycle. Attached is a
> WIP patch that implements most if not all of what's described below. Some
> yet unaddressed parts are mentioned below, too. I'll add this to the
> CF-SEP.
>
> Syntax
> ======
>
> 1. Creating a partitioned table
>
> CREATE TABLE table_name
> PARTITION BY {RANGE|LIST}
> ON (column_list);
>
> Where column_list consists of simple column names or expressions:
>
> PARTITION BY LIST ON (name)
> PARTITION BY RANGE ON (year, month)
>
> PARTITION BY LIST ON ((lower(left(name, 2)))
> PARTITION BY RANGE ON ((extract(year from d)), (extract(month from d)))
>
> Note: LIST partition key supports only one column.
>
> For each column, you could write operator class name:
>
> PARTITION BY LIST/RANGE ON (colname [USING] opclass_name),
>
> If not specified, the default btree operator class based on type of each
> key column is used. If none of the available btree operator classes are
> compatible with the partitioning strategy (list/range), error is thrown.
> Built-in btree operator classes cover a good number of types for list and
> range partitioning in practical scenarios.
>
> A table created using this form is of proposed new relkind
> RELKIND_PARTITIONED_REL. An entry in pg_partitioned_rel (see below) is
> created to store partition key info.
>
> Note: A table cannot be partitioned after-the-fact using ALTER TABLE.
>
> Normal dependencies are created between the partitioned table and operator
> classes, object in partition expressions like functions.
>
> 2. Creating a partition of a partitioned table
>
> CREATE TABLE table_name
> PARTITION OF partitioned_table_name
> FOR VALUES values_spec;
>
> Where values_spec is:
>
> listvalues: [IN] (val1, ...)
>
> rangevalues: START (col1min, ... ) END (col1max, ... )
> | START (col1min, ... )
> | END (col1max, ... )
>
> A table created using this form has proposed pg_class.relispartition set
> to true. An entry in pg_partition (see below) is created to store the
> partition bound info.
>
> The values_spec should match the partitioning strategy of the partitioned
> table. In case of a range partition, the values in START and/or END should
> match columns in the partition key.
>
> Defining a list partition is fairly straightforward - just spell out the
> list of comma-separated values. Error is thrown if the list of values
> overlaps with one of the existing partitions' list.
>
> CREATE TABLE persons_by_state (name text, state text)
> PARTITION BY LIST ON (state);
>
> CREATE TABLE persons_IL
> PARTITION OF persons_by_state
> FOR VALUES IN ('IL');
>
> CREATE TABLE persons_fail
> PARTITION OF persons_by_state
> FOR VALUES IN ('IL');
> ERROR: cannot create partition that overlaps with an existing one
>
> For a range partition, there are more than one way:
>
> Specify both START and END bounds: resulting range should not overlap with
> the range(s) covered by existing partitions. Error is thrown otherwise.
> Although rare in practice, gaps between ranges are OK.
>
> CREATE TABLE measurement(logdate date NOT NULL)
> PARTITION BY RANGE ON (logdate);
>
> CREATE TABLE measurement_y2006m02
> PARTITION OF measurement
> FOR VALUES START ('2006-02-01') END ('2006-03-01'); --success
>
> CREATE TABLE measurement_fail
> PARTITION OF measurement
> FOR VALUES START ('2006-02-15') END ('2006-03-01');
> ERROR: cannot create partition that overlaps with an existing one
>
> Specify only the START bound: add the partition on the left of some range
> covered by existing partitions provided no overlap occurs (also
> considering gaps between ranges, if any). If no such range exists, the new
> partition will cover the range [START, +INFINITY) and become the rightmost
> partition. Error is thrown if the specified START causes overlap.
>
> CREATE TABLE measurement_y2006m01
> PARTITION OF measurement
> FOR VALUES START ('2006-01-01'); --success
>
> CREATE TABLE measurement_fail
> PARTITION OF measurement
> FOR VALUES START ('2006-02-01'); --overlaps with measurement_y2006m02
> ERROR: cannot create partition that overlaps with an existing one
>
> Specify only the END bound: add the partition on the right of some range
> covered by existing partitions provided no overlap occurs (also
> considering gaps between ranges, if any). If no such range exists, the new
> partition would cover the range (-INFINITY, END) and become the leftmost
> partition. Error is thrown if the specified END causes overlap.
>
> CREATE TABLE measurement_y2006m03
> PARTITION OF measurement
> FOR VALUES END ('2006-04-01'); --success
>
> CREATE TABLE measurement_fail
> PARTITION OF measurement
> FOR VALUES END ('2006-03-01'); --overlaps with measurement_y2006m02
> ERROR: cannot create partition that overlaps with an existing one
>
> For each partition, START and END bound values are stored in the
> catalog. Note that the lower bound is inclusive, whereas the upper bound
> is exclusive.
>
> Note: At most one range partition can have null min bound in which case it
> covers the range (-INFINITY, END). Also, at most one range partition can
> have null max bound in which case it covers the range [START, +INFINITY).
>
> A normal dependency is created between the parent and the partition.
>
> 3. Multi-level partitioning
>
> CREATE TABLE table_name
> PARTITION OF partitioned_table_name
> FOR VALUES values_spec
> PARTITION BY {RANGE|LIST} ON (columns_list)
>
> This variant implements a form of so called composite or sub-partitioning
> with arbitrarily deep partitioning structure. A table created using this
> form has both the relkind RELKIND_PARTITIONED_REL and
> pg_class.relispartition set to true.
>
> 4. (yet unimplemented) Attach partition (from existing table)
>
> ALTER TABLE partitioned_table
> ATTACH PARTITION partition_name
> FOR VALUES values_spec
> USING [TABLE] table_name;
>
> ALTER TABLE table_name
> SET VALID PARTITION OF <parent>;
>
> The first of the above pair of commands would attach table_name as a (yet)
> 'invalid' partition of partitioned_table (after confirming that it matches
> the schema and does not overlap with other partitions per FOR VALUES
> spec). It would also record the FOR VALUES part in the partition catalog
> and set pg_class.relispartition to true for table_name.
>
> After the first command is done, the second command would take exclusive
> lock on table_name, scan the table to check if it contains any values
> outside the boundaries defined by FOR VALUES clause defined previously,
> throw error if so, mark as valid partition of parent if not.
>
> Does that make sense?
>
> 5. Detach partition
>
> ALTER TABLE partitioned_table
> DETACH PARTITION partition_name [USING table_name]
>
> This removes partition_name as partition of partitioned_table. The table
> continues to exist with the same name or 'table_name', if specified.
> pg_class.relispartition is set to false for the table, so it behaves like
> a normal table.
>
>
> System catalogs
> ===============
>
> 1. pg_partitioned_rel
>
> CATALOG(pg_partitioned_rel,oid) BKI_WITHOUT_OIDS
> {
> Oid partrelid; /* partitioned table pg_class.oid */
> char partstrategy; /* partitioning strategy 'l'/'r' */
> int16 partnatts; /* number of partition columns */
> int2vector partkey; /* column numbers of partition columns;
> * 0 where specified column is an
> * expresion */
> oidvector partclass; /* operator class to compare keys */
> pg_node_tree partexprs; /* expression trees for partition key
> * members that are not simple column
> * references; one for each zero entry
> * in partkey[] */
> };
>
>
> 2. pg_partition (omits partisvalid alluded to above)
>
> CATALOG(pg_partition,oid) BKI_WITHOUT_OIDS
> {
> Oid partitionid; /* partition oid */
> Oid partparent; /* parent oid */
> anyarray partlistvalues; /* list of allowed values of the only
> * partition column */
> anyarray partrangebounds; /* list of bounds of ranges of
> * allowed values per partition key
> * column */
> };
>
>
> Further notes
> =============
>
> There are a number of restrictions on performing after-the-fact changes
> using ALTER TABLE to partitions (ie, relispartition=true):
>
> * Cannot add/drop column
> * Cannot set/drop OIDs
> * Cannot set/drop NOT NULL
> * Cannot set/drop default
> * Cannot alter column type
> * Cannot add/drop alter constraint (table level)
> * Cannot change persistence
> * Cannot change inheritance
> * Cannot link to a composite type
>
> Such changes should be made to the topmost parent in the partitioning
> hierarchy (hereafter referred to as just parent). These are recursively
> applied to all the tables in the hierarchy. Although the last two items
> cannot be performed on parent either.
>
> Dropping a partition using DROP TABLE is not allowed. It needs to detached
> using ALTER TABLE on parent before it can be dropped as a normal table.
>
> Triggers on partitions are not allowed. They should be defined on the
> parent. That said, I could not figure out a way to implement row-level
> AFTER triggers on partitioned tables (more about that in a moment); so
> they are currently not allowed:
>
> CREATE TRIGGER audit_trig
> AFTER INSERT ON persons
> FOR EACH ROW EXECUTE PROCEDURE audit_func();
> ERROR: Row-level AFTER triggers are not supported on partitioned tables
>
> Column/table constraints on partitions are not allowed. They should be
> defined on the parent. Foreign key constraints are not allowed due to
> above limitation (no row-level after triggers).
>
> A partitioning parent (RELKIND_PARTITIONED_REL) does not have storage.
>
> Creating index on parent is not allowed. They should be defined on (leaf)
> partitions. Because of this limitation, primary keys are not allowed on a
> partitioned table. Perhaps, we should be able to just create a dummy
> entry somewhere to represent an index on parent (which every partition
> then copies.) Then by restricting primary key to contain all partition key
> columns, we can implement unique constraint over the whole partitioned
> table. That will in turn allow us to use partitioned tables as PK rels in
> a foreign key constraint provided row-level AFTER trigger issue is
> resolved.
>
> VACUUM/ANALYZE on individual partitions should work like normal tables.
> I've not implemented something like inheritance tree sampling for
> partitioning tree in this patch. Autovacuum has been taught to ignore
> parent tables and vacuum/analyze partitions normally.
>
> Dropping a partitioned table should (?) unconditionally drop all its
> partitions probably but, currently the patch uses dependencies, so
> requires to specify CASCADE to do the same.
>
> What should TRUNCATE on partitioned table do?
>
> Ownership, privileges/permissions, RLS should be managed through the
> parent table although not comprehensively addressed in the patch.
>
> There is no need to define tuple routing triggers. CopyFrom() and
> ExecInsert() determine target partition just before performing
> heap_insert() and ExecInsertIndexTuples(). IOW, any BR triggers and
> constraints (on parent) are executed for tuple before being routed to a
> partition. If no partition can be found, it's an error.
>
> Because row-level AFTER triggers need to save ItemPointers in trigger
> event data and defining triggers on partitions (which is where tuples
> really go) is not allowed, I could not find a straightforward way to
> implement them. So, perhaps we should allow (only) row-level AFTER
> triggers on partitions or think of modifying trigger.c to know about this
> twist explicitly.
>
> Internal representations
> ========================
>
> For a RELKIND_PARTITIONED_REL relations, RelationData gets a few new
> fields to store the partitioning metadata. That includes partition key
> tuple (pg_partitioned_rel) including some derived info (opfamily,
> opcintype, compare proc FmgrInfos, partition expression trees).
>
> Additionally, it also includes a PartitionInfo object which includes
> partition OIDs array, partition bound arrays (if range partitioned,
> rangemax is sorted in ascending order and OIDs are likewise ordered). It
> is built from information in pg_partition catalog.
>
> While RelationBuildDesc() initializes the basic key info, fields like
> expression trees, PartitionInfo are built on demand and cached. For
> example, InitResultRelInfo() builds the latter to populate the newly added
> ri_PartitionKeyInfo and ri_Partitions fields, respectively.
>
> PartitionInfo object is rebuilt on every cache invalidation of the rel
> which includes when adding/attaching/detaching a new partition.
>
> Planner and executor considerations
> =====================================
>
> The patch does not yet implement any planner changes for partitioned
> tables, although I'm working on the same and post updates as soon as
> possible. That means, it is not possible to run SELECT/UPDATE/DELETE
> queries on partitioned tables without getting:
>
> postgres=# SELECT * FROM persons;
> ERROR: could not open file "base/13244/106975": No such file or directory
>
> Given that there would be more direct ways of performing partition pruning
> decisions with the proposed, it would be nice to utilize them.
> Specifically, I would like to avoid having to rely on constraint exclusion
> for partition pruning whereby subquery_planner() builds append_rel_list
> and the later steps exclude useless partitions.
>
> By extending RelOptInfo to include partitioning info for partitioned rels,
> it might be possible to perform partition pruning directly without
> previously having to expand them. Although, as things stand now, it's not
> clear how that might work - when would partition RTEs be added to the
> rtable? The rtable is assumed not to change after
> setup_simple_rel_arrays() has done its job which is much earlier than when
> it would be desirable for the partitioned table expansion (along with
> partition pruning) to happen. Moreover, if that means we might not be able
> to build RelOptInfo's for partitions, how to choose best paths for them
> (index paths or not, etc.)?
>
> I'm also hoping we don't require something like inheritance_planner() for
> when partitioned tables are target rels. I assume considerations for why
> the special processing is necessary for inheritance trees in that scenario
> don't apply to partitioning trees. So, if grouping_planner() returns a
> Append plan (among other options) for the partitioning tree, tacking a
> ModifyTable node on top should do the trick?
>
> Suggestions greatly welcome in this area.
>
> Other items
> ===========
>
> Will include the following once we start reaching consensus on main parts
> of the proposed design/implementation:
>
> * New regression tests
> * Documentation updates
> * pg_dump, psql, etc.
>
> For reference, some immediately previous discussions:
>
> * On partitioning *
>
> http://www.postgresql.org/message-id/20140829155607.GF7705@eldon.alvh.no-ip.org
>
> * Partitioning WIP patch *
> http://www.postgresql.org/message-id/54EC32B6.9070605@lab.ntt.co.jp
>
> Comments welcome!
>
> Thanks,
> Amit
>
Wow, didn't expect to see that email this morning.
A very quick test:
CREATE TABLE purchases (purchase_id serial, purchase_time timestamp, item
text) partition by range on ((extract(year from
purchase_time)),(extract(month from purchase_time)));
ERROR: referenced relation "purchases" is not a table or foreign table
Thom
^ permalink raw reply [nested|flat] 161+ messages in thread
* Re: Declarative partitioning
@ 2015-08-18 11:23 Amit Langote <amitlangote09@gmail.com>
parent: Thom Brown <thom@linux.com>
0 siblings, 1 reply; 161+ messages in thread
From: Amit Langote @ 2015-08-18 11:23 UTC (permalink / raw)
To: Thom Brown <thom@linux.com>; +Cc: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; pgsql-hackers
Hi Thom,
On Tue, Aug 18, 2015 at 8:02 PM, Thom Brown <thom@linux.com> wrote:
>
>
> Wow, didn't expect to see that email this morning.
>
> A very quick test:
>
> CREATE TABLE purchases (purchase_id serial, purchase_time timestamp, item
> text) partition by range on ((extract(year from
> purchase_time)),(extract(month from purchase_time)));
> ERROR: referenced relation "purchases" is not a table or foreign table
>
Thanks for the quick test.
Damn, I somehow missed adding the new relkind to a check in
process_owned_by(). Will fix this and look for any such oversights.
Thanks,
Amit
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 161+ messages in thread
* Re: Declarative partitioning
@ 2015-08-18 13:43 David Fetter <david@fetter.org>
parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
9 siblings, 1 reply; 161+ messages in thread
From: David Fetter @ 2015-08-18 13:43 UTC (permalink / raw)
To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; +Cc: pgsql-hackers
On Tue, Aug 18, 2015 at 07:30:20PM +0900, Amit Langote wrote:
> Hi,
>
> I would like propose $SUBJECT for this development cycle. Attached is a
> WIP patch that implements most if not all of what's described below. Some
> yet unaddressed parts are mentioned below, too. I'll add this to the CF-SEP.
Thanks for pushing this forward! We've needed this done for at least
a decade.
> 4. (yet unimplemented) Attach partition (from existing table)
>
> ALTER TABLE partitioned_table
> ATTACH PARTITION partition_name
> FOR VALUES values_spec
> USING [TABLE] table_name;
>
> ALTER TABLE table_name
> SET VALID PARTITION OF <parent>;
>
> The first of the above pair of commands would attach table_name as a (yet)
> 'invalid' partition of partitioned_table (after confirming that it matches
> the schema and does not overlap with other partitions per FOR VALUES
> spec). It would also record the FOR VALUES part in the partition catalog
> and set pg_class.relispartition to true for table_name.
>
> After the first command is done, the second command would take exclusive
> lock on table_name, scan the table to check if it contains any values
> outside the boundaries defined by FOR VALUES clause defined previously,
> throw error if so, mark as valid partition of parent if not.
One small change to make this part more efficient:
1. Take the access exclusive lock on table_name.
2. Check for a matching constraint on it.
3. If it's there, mark it as a valid partition.
4. If not, check for values outside the boundaries as above.
Should the be a *valid* constraint? Perhaps that should be
parameterized, as I'm not yet seeing a compelling argument either
direction. I'm picturing something like:
ALTER TABLE table_name SET VALID PARTITION OF <parent> [TRUST]
where TRUST would mean that an existing constraint need not be VALID.
> Does that make sense?
Yep.
> 5. Detach partition
>
> ALTER TABLE partitioned_table
> DETACH PARTITION partition_name [USING table_name]
>
> This removes partition_name as partition of partitioned_table. The table
> continues to exist with the same name or 'table_name', if specified.
> pg_class.relispartition is set to false for the table, so it behaves like
> a normal table.
Could this take anything short of an access exclusive lock on the
parent?
Cheers,
David.
--
David Fetter <david@fetter.org> http://fetter.org/
Phone: +1 415 235 3778 AIM: dfetter666 Yahoo!: dfetter
Skype: davidfetter XMPP: david.fetter@gmail.com
Remember to vote!
Consider donating to Postgres: http://www.postgresql.org/about/donate
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 161+ messages in thread
* Re: Declarative partitioning
@ 2015-08-18 17:31 Josh Berkus <josh@agliodbs.com>
parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
9 siblings, 2 replies; 161+ messages in thread
From: Josh Berkus @ 2015-08-18 17:31 UTC (permalink / raw)
To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; pgsql-hackers
Amit,
> I would like propose $SUBJECT for this development cycle. Attached is a
> WIP patch that implements most if not all of what's described below. Some
> yet unaddressed parts are mentioned below, too. I'll add this to the CF-SEP.
First of all, wow! Really happy to see this.
>
> Syntax
> ======
>
> 1. Creating a partitioned table
>
> CREATE TABLE table_name
> PARTITION BY {RANGE|LIST}
> ON (column_list);
>
> Where column_list consists of simple column names or expressions:
>
> PARTITION BY LIST ON (name)
> PARTITION BY RANGE ON (year, month)
>
> PARTITION BY LIST ON ((lower(left(name, 2)))
> PARTITION BY RANGE ON ((extract(year from d)), (extract(month from d)))
So far so good. Have you given any thought as to how a user will
determine which partition corresponds to which values (for purposes of
dropping/maintaining a partition)?
Also, won't doing things like extract() for range partitions make it
much harder for you to develop the planner parts of this solution?
What about defining an interval instead, such as:
PARTITION BY RANGE USING ( interval ) ON ( column );
i.e.
PARTITION BY RANGE USING ( INTERVAL '1 month' ) ON ( submitted_date );
PARTITION BY RANGE USING ( 100000 ) ON ( user_id );
This would make it easy for you to construct range type values defining
the range of each partition, which would then make the planner work much
easier than calling a function would, no?
Or am I misunderstanding how you're using ranges here? It kind of seems
like you're still leaving specific range defintions up to the user,
which is (from my perspective) unsatisfactory (see below).
I'm assuming that all range partitions will be [ closed, open ) ranges.
> 2. Creating a partition of a partitioned table
>
> CREATE TABLE table_name
> PARTITION OF partitioned_table_name
> FOR VALUES values_spec;
>
> Where values_spec is:
>
> listvalues: [IN] (val1, ...)
>
> rangevalues: START (col1min, ... ) END (col1max, ... )
> | START (col1min, ... )
> | END (col1max, ... )
So, one thing I missed in here is anything about automated partitioning
of tables; that is, creating new partitions based on incoming data or a
simple statement which doesn't require knowledge of the partitioning
scheme. It's possible (and entirely accceptable) that you're
considering automated partition creation outside of the scope of this
patch. However, for range partitions, it would be *really* useful to
have this syntax:
CREATE NEXT PARTITION ON parent_table;
Which would just create the "next" partition based on whatever the range
partitoning scheme is, instead of requiring the user to calculate start
and end values which might or might not match the parent partitioning
scheme, and might leave gaps. Also this would be useful for range
partitions:
CREATE PARTITION ON parent_table USING ( start_value );
... where start_value is the start range of the new partition. Again,
easier for users to get correct.
Both of these require the idea of regular intervals for range
partitions, that is, on a table partitioned by month on a timestamptz
column, each partition will have the range [ month:1, nextmonth:1 ).
This is the most common use-case for range partitions (like, 95% of all
partitioning cases I've seen), so a new partitioning scheme ought to
address it.
While there are certainly users who desire the ability to define
arbitrary ranges for each range partition, these are by far the minority
and could be accomodated by a different path with more complex syntax.
Further, I'd wager that most users who want to define arbitrary ranges
for range partitions aren't going to be satisfied with the other
restrictions on declarative partitioning (e.g. same constraints, columns
for all partitions) and are going to use inheritance partitioning anyway.
> 5. Detach partition
>
> ALTER TABLE partitioned_table
> DETACH PARTITION partition_name [USING table_name]
>
> This removes partition_name as partition of partitioned_table. The table
> continues to exist with the same name or 'table_name', if specified.
> pg_class.relispartition is set to false for the table, so it behaves like
> a normal table.
What about DROPping partitions? Do they need to be detached first?
> Creating index on parent is not allowed. They should be defined on (leaf)
> partitions. Because of this limitation, primary keys are not allowed on a
> partitioned table. Perhaps, we should be able to just create a dummy
> entry somewhere to represent an index on parent (which every partition
> then copies.)
This would be preferable, yes. Making users remember to manually create
indexes on each partition is undesirable.
> What should TRUNCATE on partitioned table do?
On the master table? Truncate all individual partitions. Do not drop
the partitions.
On a partitition? Truncate just that partition.
--
Josh Berkus
PostgreSQL Experts Inc.
http://pgexperts.com
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 161+ messages in thread
* Re: Declarative partitioning
@ 2015-08-18 17:57 Marc Mamin <M.Mamin@intershop.de>
parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
9 siblings, 1 reply; 161+ messages in thread
From: Marc Mamin @ 2015-08-18 17:57 UTC (permalink / raw)
To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; pgsql-hackers
great :-)
>2. Creating a partition of a partitioned table
>
>CREATE TABLE table_name
>PARTITION OF partitioned_table_name
>FOR VALUES values_spec;
>
>Where values_spec is:
>
>listvalues: [IN] (val1, ...)
>
Would it make sense to allow one complementary partition to the listvalues?
listvalues: [[NOT] IN] (val1, ...)
I've thought a few times about moving data with some most common values to dedicated partitions
and keeping the rest in a separate one...
best regards,
Marc Mamin
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 161+ messages in thread
* Re: Declarative partitioning
@ 2015-08-18 17:59 Corey Huinker <corey.huinker@gmail.com>
parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
9 siblings, 1 reply; 161+ messages in thread
From: Corey Huinker @ 2015-08-18 17:59 UTC (permalink / raw)
To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; +Cc: pgsql-hackers
On Tue, Aug 18, 2015 at 6:30 AM, Amit Langote <Langote_Amit_f8@lab.ntt.co.jp
> wrote:
>
> Hi,
>
> I would like propose $SUBJECT for this development cycle. Attached is a
> WIP patch that implements most if not all of what's described below. Some
> yet unaddressed parts are mentioned below, too. I'll add this to the
> CF-SEP.
>
> Syntax
> ======
>
> 1. Creating a partitioned table
>
> CREATE TABLE table_name
> PARTITION BY {RANGE|LIST}
> ON (column_list);
>
> Where column_list consists of simple column names or expressions:
>
> PARTITION BY LIST ON (name)
> PARTITION BY RANGE ON (year, month)
>
> PARTITION BY LIST ON ((lower(left(name, 2)))
> PARTITION BY RANGE ON ((extract(year from d)), (extract(month from d)))
>
> Note: LIST partition key supports only one column.
>
> For each column, you could write operator class name:
>
> PARTITION BY LIST/RANGE ON (colname [USING] opclass_name),
>
> If not specified, the default btree operator class based on type of each
> key column is used. If none of the available btree operator classes are
> compatible with the partitioning strategy (list/range), error is thrown.
> Built-in btree operator classes cover a good number of types for list and
> range partitioning in practical scenarios.
>
> A table created using this form is of proposed new relkind
> RELKIND_PARTITIONED_REL. An entry in pg_partitioned_rel (see below) is
> created to store partition key info.
>
> Note: A table cannot be partitioned after-the-fact using ALTER TABLE.
>
> Normal dependencies are created between the partitioned table and operator
> classes, object in partition expressions like functions.
>
> 2. Creating a partition of a partitioned table
>
> CREATE TABLE table_name
> PARTITION OF partitioned_table_name
> FOR VALUES values_spec;
>
> Where values_spec is:
>
> listvalues: [IN] (val1, ...)
>
> rangevalues: START (col1min, ... ) END (col1max, ... )
> | START (col1min, ... )
> | END (col1max, ... )
>
> A table created using this form has proposed pg_class.relispartition set
> to true. An entry in pg_partition (see below) is created to store the
> partition bound info.
>
> The values_spec should match the partitioning strategy of the partitioned
> table. In case of a range partition, the values in START and/or END should
> match columns in the partition key.
>
> Defining a list partition is fairly straightforward - just spell out the
> list of comma-separated values. Error is thrown if the list of values
> overlaps with one of the existing partitions' list.
>
> CREATE TABLE persons_by_state (name text, state text)
> PARTITION BY LIST ON (state);
>
> CREATE TABLE persons_IL
> PARTITION OF persons_by_state
> FOR VALUES IN ('IL');
>
> CREATE TABLE persons_fail
> PARTITION OF persons_by_state
> FOR VALUES IN ('IL');
> ERROR: cannot create partition that overlaps with an existing one
>
> For a range partition, there are more than one way:
>
> Specify both START and END bounds: resulting range should not overlap with
> the range(s) covered by existing partitions. Error is thrown otherwise.
> Although rare in practice, gaps between ranges are OK.
>
> CREATE TABLE measurement(logdate date NOT NULL)
> PARTITION BY RANGE ON (logdate);
>
> CREATE TABLE measurement_y2006m02
> PARTITION OF measurement
> FOR VALUES START ('2006-02-01') END ('2006-03-01'); --success
>
> CREATE TABLE measurement_fail
> PARTITION OF measurement
> FOR VALUES START ('2006-02-15') END ('2006-03-01');
> ERROR: cannot create partition that overlaps with an existing one
>
> Specify only the START bound: add the partition on the left of some range
> covered by existing partitions provided no overlap occurs (also
> considering gaps between ranges, if any). If no such range exists, the new
> partition will cover the range [START, +INFINITY) and become the rightmost
> partition. Error is thrown if the specified START causes overlap.
>
> CREATE TABLE measurement_y2006m01
> PARTITION OF measurement
> FOR VALUES START ('2006-01-01'); --success
>
> CREATE TABLE measurement_fail
> PARTITION OF measurement
> FOR VALUES START ('2006-02-01'); --overlaps with measurement_y2006m02
> ERROR: cannot create partition that overlaps with an existing one
>
> Specify only the END bound: add the partition on the right of some range
> covered by existing partitions provided no overlap occurs (also
> considering gaps between ranges, if any). If no such range exists, the new
> partition would cover the range (-INFINITY, END) and become the leftmost
> partition. Error is thrown if the specified END causes overlap.
>
> CREATE TABLE measurement_y2006m03
> PARTITION OF measurement
> FOR VALUES END ('2006-04-01'); --success
>
> CREATE TABLE measurement_fail
> PARTITION OF measurement
> FOR VALUES END ('2006-03-01'); --overlaps with measurement_y2006m02
> ERROR: cannot create partition that overlaps with an existing one
>
> For each partition, START and END bound values are stored in the
> catalog. Note that the lower bound is inclusive, whereas the upper bound
> is exclusive.
>
> Note: At most one range partition can have null min bound in which case it
> covers the range (-INFINITY, END). Also, at most one range partition can
> have null max bound in which case it covers the range [START, +INFINITY).
>
> A normal dependency is created between the parent and the partition.
>
> 3. Multi-level partitioning
>
> CREATE TABLE table_name
> PARTITION OF partitioned_table_name
> FOR VALUES values_spec
> PARTITION BY {RANGE|LIST} ON (columns_list)
>
> This variant implements a form of so called composite or sub-partitioning
> with arbitrarily deep partitioning structure. A table created using this
> form has both the relkind RELKIND_PARTITIONED_REL and
> pg_class.relispartition set to true.
>
> 4. (yet unimplemented) Attach partition (from existing table)
>
> ALTER TABLE partitioned_table
> ATTACH PARTITION partition_name
> FOR VALUES values_spec
> USING [TABLE] table_name;
>
> ALTER TABLE table_name
> SET VALID PARTITION OF <parent>;
>
> The first of the above pair of commands would attach table_name as a (yet)
> 'invalid' partition of partitioned_table (after confirming that it matches
> the schema and does not overlap with other partitions per FOR VALUES
> spec). It would also record the FOR VALUES part in the partition catalog
> and set pg_class.relispartition to true for table_name.
>
> After the first command is done, the second command would take exclusive
> lock on table_name, scan the table to check if it contains any values
> outside the boundaries defined by FOR VALUES clause defined previously,
> throw error if so, mark as valid partition of parent if not.
>
> Does that make sense?
>
> 5. Detach partition
>
> ALTER TABLE partitioned_table
> DETACH PARTITION partition_name [USING table_name]
>
> This removes partition_name as partition of partitioned_table. The table
> continues to exist with the same name or 'table_name', if specified.
> pg_class.relispartition is set to false for the table, so it behaves like
> a normal table.
>
>
> System catalogs
> ===============
>
> 1. pg_partitioned_rel
>
> CATALOG(pg_partitioned_rel,oid) BKI_WITHOUT_OIDS
> {
> Oid partrelid; /* partitioned table pg_class.oid */
> char partstrategy; /* partitioning strategy 'l'/'r' */
> int16 partnatts; /* number of partition columns */
> int2vector partkey; /* column numbers of partition columns;
> * 0 where specified column is an
> * expresion */
> oidvector partclass; /* operator class to compare keys */
> pg_node_tree partexprs; /* expression trees for partition key
> * members that are not simple column
> * references; one for each zero entry
> * in partkey[] */
> };
>
>
> 2. pg_partition (omits partisvalid alluded to above)
>
> CATALOG(pg_partition,oid) BKI_WITHOUT_OIDS
> {
> Oid partitionid; /* partition oid */
> Oid partparent; /* parent oid */
> anyarray partlistvalues; /* list of allowed values of the only
> * partition column */
> anyarray partrangebounds; /* list of bounds of ranges of
> * allowed values per partition key
> * column */
> };
>
>
> Further notes
> =============
>
> There are a number of restrictions on performing after-the-fact changes
> using ALTER TABLE to partitions (ie, relispartition=true):
>
> * Cannot add/drop column
> * Cannot set/drop OIDs
> * Cannot set/drop NOT NULL
> * Cannot set/drop default
> * Cannot alter column type
> * Cannot add/drop alter constraint (table level)
> * Cannot change persistence
> * Cannot change inheritance
> * Cannot link to a composite type
>
> Such changes should be made to the topmost parent in the partitioning
> hierarchy (hereafter referred to as just parent). These are recursively
> applied to all the tables in the hierarchy. Although the last two items
> cannot be performed on parent either.
>
> Dropping a partition using DROP TABLE is not allowed. It needs to detached
> using ALTER TABLE on parent before it can be dropped as a normal table.
>
> Triggers on partitions are not allowed. They should be defined on the
> parent. That said, I could not figure out a way to implement row-level
> AFTER triggers on partitioned tables (more about that in a moment); so
> they are currently not allowed:
>
> CREATE TRIGGER audit_trig
> AFTER INSERT ON persons
> FOR EACH ROW EXECUTE PROCEDURE audit_func();
> ERROR: Row-level AFTER triggers are not supported on partitioned tables
>
> Column/table constraints on partitions are not allowed. They should be
> defined on the parent. Foreign key constraints are not allowed due to
> above limitation (no row-level after triggers).
>
> A partitioning parent (RELKIND_PARTITIONED_REL) does not have storage.
>
> Creating index on parent is not allowed. They should be defined on (leaf)
> partitions. Because of this limitation, primary keys are not allowed on a
> partitioned table. Perhaps, we should be able to just create a dummy
> entry somewhere to represent an index on parent (which every partition
> then copies.) Then by restricting primary key to contain all partition key
> columns, we can implement unique constraint over the whole partitioned
> table. That will in turn allow us to use partitioned tables as PK rels in
> a foreign key constraint provided row-level AFTER trigger issue is
> resolved.
>
> VACUUM/ANALYZE on individual partitions should work like normal tables.
> I've not implemented something like inheritance tree sampling for
> partitioning tree in this patch. Autovacuum has been taught to ignore
> parent tables and vacuum/analyze partitions normally.
>
> Dropping a partitioned table should (?) unconditionally drop all its
> partitions probably but, currently the patch uses dependencies, so
> requires to specify CASCADE to do the same.
>
> What should TRUNCATE on partitioned table do?
>
> Ownership, privileges/permissions, RLS should be managed through the
> parent table although not comprehensively addressed in the patch.
>
> There is no need to define tuple routing triggers. CopyFrom() and
> ExecInsert() determine target partition just before performing
> heap_insert() and ExecInsertIndexTuples(). IOW, any BR triggers and
> constraints (on parent) are executed for tuple before being routed to a
> partition. If no partition can be found, it's an error.
>
> Because row-level AFTER triggers need to save ItemPointers in trigger
> event data and defining triggers on partitions (which is where tuples
> really go) is not allowed, I could not find a straightforward way to
> implement them. So, perhaps we should allow (only) row-level AFTER
> triggers on partitions or think of modifying trigger.c to know about this
> twist explicitly.
>
> Internal representations
> ========================
>
> For a RELKIND_PARTITIONED_REL relations, RelationData gets a few new
> fields to store the partitioning metadata. That includes partition key
> tuple (pg_partitioned_rel) including some derived info (opfamily,
> opcintype, compare proc FmgrInfos, partition expression trees).
>
> Additionally, it also includes a PartitionInfo object which includes
> partition OIDs array, partition bound arrays (if range partitioned,
> rangemax is sorted in ascending order and OIDs are likewise ordered). It
> is built from information in pg_partition catalog.
>
> While RelationBuildDesc() initializes the basic key info, fields like
> expression trees, PartitionInfo are built on demand and cached. For
> example, InitResultRelInfo() builds the latter to populate the newly added
> ri_PartitionKeyInfo and ri_Partitions fields, respectively.
>
> PartitionInfo object is rebuilt on every cache invalidation of the rel
> which includes when adding/attaching/detaching a new partition.
>
> Planner and executor considerations
> =====================================
>
> The patch does not yet implement any planner changes for partitioned
> tables, although I'm working on the same and post updates as soon as
> possible. That means, it is not possible to run SELECT/UPDATE/DELETE
> queries on partitioned tables without getting:
>
> postgres=# SELECT * FROM persons;
> ERROR: could not open file "base/13244/106975": No such file or directory
>
> Given that there would be more direct ways of performing partition pruning
> decisions with the proposed, it would be nice to utilize them.
> Specifically, I would like to avoid having to rely on constraint exclusion
> for partition pruning whereby subquery_planner() builds append_rel_list
> and the later steps exclude useless partitions.
>
> By extending RelOptInfo to include partitioning info for partitioned rels,
> it might be possible to perform partition pruning directly without
> previously having to expand them. Although, as things stand now, it's not
> clear how that might work - when would partition RTEs be added to the
> rtable? The rtable is assumed not to change after
> setup_simple_rel_arrays() has done its job which is much earlier than when
> it would be desirable for the partitioned table expansion (along with
> partition pruning) to happen. Moreover, if that means we might not be able
> to build RelOptInfo's for partitions, how to choose best paths for them
> (index paths or not, etc.)?
>
> I'm also hoping we don't require something like inheritance_planner() for
> when partitioned tables are target rels. I assume considerations for why
> the special processing is necessary for inheritance trees in that scenario
> don't apply to partitioning trees. So, if grouping_planner() returns a
> Append plan (among other options) for the partitioning tree, tacking a
> ModifyTable node on top should do the trick?
>
> Suggestions greatly welcome in this area.
>
> Other items
> ===========
>
> Will include the following once we start reaching consensus on main parts
> of the proposed design/implementation:
>
> * New regression tests
> * Documentation updates
> * pg_dump, psql, etc.
>
> For reference, some immediately previous discussions:
>
> * On partitioning *
>
> http://www.postgresql.org/message-id/20140829155607.GF7705@eldon.alvh.no-ip.org
>
> * Partitioning WIP patch *
> http://www.postgresql.org/message-id/54EC32B6.9070605@lab.ntt.co.jp
>
> Comments welcome!
>
> Thanks,
> Amit
>
>
> --
> Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
> To make changes to your subscription:
> http://www.postgresql.org/mailpref/pgsql-hackers
>
>
Quick thoughts borne of years of slugging it out with partitions on Oracle:
- Finally!!!!!!!!!!!
- Your range partitioning will need to express exclusive/inclusive bounds,
or go to the Oracle model where every partition is a cascading "values less
than" test context dependent on the partitions defined before it. I would
suggest that leveraging existing range types (or allowing the user to
specify a range type, like for a specific collation of a text range) would
allow for the most flexible and postgres-ish range definition. You seem to
do this with the "[USING] opclass_name" bit, but don't follow through on
the START(...) and END(...). Something like FOR VALUES <@
'[''2014-01-01'',''2015-01-01)'::daterange would cover most needs
succinctly, though I admit the syntax for complex ranges could be
cumbersome, though something like FOR VALUES <@
'[(''a'',1),(''b'',1))'::letter_then_number_range is still readable.
- No partitioning scheme survives first contact with reality. So you will
need a facility for splitting and joining existing partitions. For
splitting partitions, it's sufficient to require that the new partition
share either a upper/lower bound (with the same inclusivity/exclusivity) of
an existing partition, thus uniquely identifying the partition to be split,
and require that the other bound be within the range of the partition to be
split. Similarly, it's fair to require that the partitions to be joined be
adjacent in range. In both cases, range operators make these tests simple.
- Your features 4 and 5 are implemented in Oracle with SWAP PARTITION,
which is really neat for doing ETLs and index rebuilds offline in a copy
table, and then swapping the data segment of that table with the partition
specified. Which could be considered cheating because none of the partition
metadata changed, just the pointers to the segments. We already do this
with adding removing INHERIT. I'm not saying they can't be separate
functionality, but keeping an atomic SWAP operation would be grand.
^ permalink raw reply [nested|flat] 161+ messages in thread
* Re: Declarative partitioning
@ 2015-08-19 07:30 Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
parent: David Fetter <david@fetter.org>
0 siblings, 1 reply; 161+ messages in thread
From: Amit Langote @ 2015-08-19 07:30 UTC (permalink / raw)
To: David Fetter <david@fetter.org>; +Cc: pgsql-hackers
On 2015-08-18 PM 10:43, David Fetter wrote:
>>
>> After the first command is done, the second command would take exclusive
>> lock on table_name, scan the table to check if it contains any values
>> outside the boundaries defined by FOR VALUES clause defined previously,
>> throw error if so, mark as valid partition of parent if not.
>
> One small change to make this part more efficient:
>
> 1. Take the access exclusive lock on table_name.
> 2. Check for a matching constraint on it.
> 3. If it's there, mark it as a valid partition.
> 4. If not, check for values outside the boundaries as above.
>
That's an interesting idea. Thanks!
By a matching constraint, I guess you mean a 'valid' constraint from which
the declared partition constraint can be proven to follow. For (a simple)
example, from a CHECK (a >= 100 AND a < 150) on table_name, the partition
constraint implied by FOR VALUES START (100) END (200) can be assumed to hold.
> Should the be a *valid* constraint? Perhaps that should be
> parameterized, as I'm not yet seeing a compelling argument either
> direction. I'm picturing something like:
>
> ALTER TABLE table_name SET VALID PARTITION OF <parent> [TRUST]
>
> where TRUST would mean that an existing constraint need not be VALID.
>
Hmm, I'd think this step must be able to assert the partition constraint
beyond any doubt. If the DBA added the constraint and marked it invalid,
she should first VALIDATE the constraint to make it valid by performing
whatever steps necessary before. IOW, a full heap scan at least once is
inevitable (the reason why we might want to make this a two step process
at all). Am I missing something?
>
>> 5. Detach partition
>>
>> ALTER TABLE partitioned_table
>> DETACH PARTITION partition_name [USING table_name]
>>
>> This removes partition_name as partition of partitioned_table. The table
>> continues to exist with the same name or 'table_name', if specified.
>> pg_class.relispartition is set to false for the table, so it behaves like
>> a normal table.
>
> Could this take anything short of an access exclusive lock on the
> parent?
>
Yes, both the step 1 of ATTACH command and DETACH command take access
exclusive lock on the parent. They are rather quick metadata changes, so
should not stall others significantly, I think.
Thanks,
Amit
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 161+ messages in thread
* Re: Declarative partitioning
@ 2015-08-19 08:03 Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
parent: Marc Mamin <M.Mamin@intershop.de>
0 siblings, 1 reply; 161+ messages in thread
From: Amit Langote @ 2015-08-19 08:03 UTC (permalink / raw)
To: Marc Mamin <M.Mamin@intershop.de>; pgsql-hackers
On 2015-08-19 AM 02:57, Marc Mamin wrote:
>> 2. Creating a partition of a partitioned table
>>
>> CREATE TABLE table_name
>> PARTITION OF partitioned_table_name
>> FOR VALUES values_spec;
>>
>> Where values_spec is:
>>
>> listvalues: [IN] (val1, ...)
>>
>
> Would it make sense to allow one complementary partition to the listvalues?
>
> listvalues: [[NOT] IN] (val1, ...)
>
> I've thought a few times about moving data with some most common values to dedicated partitions
> and keeping the rest in a separate one...
>
Thanks, that's definitely something to consider.
I have been thinking of a sort of default list partition for the "rest" of
values. Would you rather declare that with something like the below than
having to enumerate all the values in a NOT IN list? Or the NOT IN way is
more intuitive/friendly?
CREATE TABLE _rest PARTITION OF table_name FOR VALUES [ IN ] DEFAULT
Of course, at most one such partition would be allowed.
Thanks,
Amit
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 161+ messages in thread
* Re: Declarative partitioning
@ 2015-08-19 11:59 Simon Riggs <simon@2ndQuadrant.com>
parent: Josh Berkus <josh@agliodbs.com>
1 sibling, 1 reply; 161+ messages in thread
From: Simon Riggs @ 2015-08-19 11:59 UTC (permalink / raw)
To: Josh Berkus <josh@agliodbs.com>; +Cc: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; pgsql-hackers
On 18 August 2015 at 18:31, Josh Berkus <josh@agliodbs.com> wrote:
> > 2. Creating a partition of a partitioned table
> >
> > CREATE TABLE table_name
> > PARTITION OF partitioned_table_name
> > FOR VALUES values_spec;
> >
> > Where values_spec is:
> >
> > listvalues: [IN] (val1, ...)
> >
> > rangevalues: START (col1min, ... ) END (col1max, ... )
> > | START (col1min, ... )
> > | END (col1max, ... )
>
> So, one thing I missed in here is anything about automated partitioning
> of tables; that is, creating new partitions based on incoming data or a
> simple statement which doesn't require knowledge of the partitioning
> scheme. It's possible (and entirely accceptable) that you're
> considering automated partition creation outside of the scope of this
> patch.
I would like to make automatic partitioning outside the scope of this first
patch.
However, for range partitions, it would be *really* useful to
> have this syntax:
>
> CREATE NEXT PARTITION ON parent_table;
>
> Which would just create the "next" partition based on whatever the range
> partitoning scheme is, instead of requiring the user to calculate start
> and end values which might or might not match the parent partitioning
> scheme, and might leave gaps. Also this would be useful for range
> partitions:
>
> CREATE PARTITION ON parent_table USING ( start_value );
>
> ... where start_value is the start range of the new partition. Again,
> easier for users to get correct.
>
> Both of these require the idea of regular intervals for range
> partitions, that is, on a table partitioned by month on a timestamptz
> column, each partition will have the range [ month:1, nextmonth:1 ).
> This is the most common use-case for range partitions (like, 95% of all
> partitioning cases I've seen), so a new partitioning scheme ought to
> address it.
>
> While there are certainly users who desire the ability to define
> arbitrary ranges for each range partition, these are by far the minority
> and could be accomodated by a different path with more complex syntax.
> Further, I'd wager that most users who want to define arbitrary ranges
> for range partitions aren't going to be satisfied with the other
> restrictions on declarative partitioning (e.g. same constraints, columns
> for all partitions) and are going to use inheritance partitioning anyway.
I like the idea of a regular partitioning step because it is how you design
such tables - "lets use monthly partitions".
This gives sanely terse syntax, rather than specifying pages and pages of
exact values in DDL....
PARTITION BY RANGE ON (columns) INCREMENT BY (INTERVAL '1 month' ) START
WITH value;
borrowing the same concepts from sequence syntax.
> Creating index on parent is not allowed. They should be defined on (leaf)
> > partitions. Because of this limitation, primary keys are not allowed on a
> > partitioned table. Perhaps, we should be able to just create a dummy
> > entry somewhere to represent an index on parent (which every partition
> > then copies.)
>
> This would be preferable, yes. Making users remember to manually create
> indexes on each partition is undesirable.
I think it is useful to allow additional indexes on partitions, if desired,
but we should always automatically build the indexes that are defined on
the master when we create a new partition.
Presumably unique indexes will be allowed on partitions. So if the
partition key is unique, we can say the whole partitioned table is unique
and call that a Primary Key.
I would want individual partitions to be placed on separate tablespaces,
but not by default.
--
Simon Riggs http://www.2ndQuadrant.com/
<http://www.2ndquadrant.com/;
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
^ permalink raw reply [nested|flat] 161+ messages in thread
* Re: Declarative partitioning
@ 2015-08-19 12:23 Simon Riggs <simon@2ndQuadrant.com>
parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
9 siblings, 1 reply; 161+ messages in thread
From: Simon Riggs @ 2015-08-19 12:23 UTC (permalink / raw)
To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; +Cc: pgsql-hackers
On 18 August 2015 at 11:30, Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
wrote:
>
> There is no need to define tuple routing triggers. CopyFrom() and
> ExecInsert() determine target partition just before performing
> heap_insert() and ExecInsertIndexTuples(). IOW, any BR triggers and
> constraints (on parent) are executed for tuple before being routed to a
> partition. If no partition can be found, it's an error.
>
> Because row-level AFTER triggers need to save ItemPointers in trigger
> event data and defining triggers on partitions (which is where tuples
> really go) is not allowed, I could not find a straightforward way to
> implement them. So, perhaps we should allow (only) row-level AFTER
> triggers on partitions or think of modifying trigger.c to know about this
> twist explicitly.
>
I think tables will eventually need FK support; its not sustainable as a
long term restriction, though perhaps its something we can do in a later
patch.
You haven't specified what would happen if an UPDATE would change a row's
partition. I'm happy to add this to the list of restrictions by saying that
the partition key cannot be updated.
We'll need regression tests that cover each restriction and docs that
match. This is not something we should leave until last. People read the
docs to understand the feature, helping them to reach consensus. So it is
for you to provide the docs before, not wait until later. I will begin a
code review once you tell me docs and tests are present. We all want the
feature, so its all about the details now.
--
Simon Riggs http://www.2ndQuadrant.com/
<http://www.2ndquadrant.com/;
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
^ permalink raw reply [nested|flat] 161+ messages in thread
* Re: Declarative partitioning
@ 2015-08-19 12:52 David Fetter <david@fetter.org>
parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
0 siblings, 1 reply; 161+ messages in thread
From: David Fetter @ 2015-08-19 12:52 UTC (permalink / raw)
To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; +Cc: pgsql-hackers
On Wed, Aug 19, 2015 at 04:30:39PM +0900, Amit Langote wrote:
> On 2015-08-18 PM 10:43, David Fetter wrote:
> >>
> >> After the first command is done, the second command would take exclusive
> >> lock on table_name, scan the table to check if it contains any values
> >> outside the boundaries defined by FOR VALUES clause defined previously,
> >> throw error if so, mark as valid partition of parent if not.
> >
> > One small change to make this part more efficient:
> >
> > 1. Take the access exclusive lock on table_name.
> > 2. Check for a matching constraint on it.
> > 3. If it's there, mark it as a valid partition.
> > 4. If not, check for values outside the boundaries as above.
> >
>
> That's an interesting idea. Thanks!
I hope I'm advancing this feature rather than bogging it down...
> By a matching constraint, I guess you mean a 'valid' constraint from
> which the declared partition constraint can be proven to follow. For
> (a simple) example, from a CHECK (a >= 100 AND a < 150) on
> table_name, the partition constraint implied by FOR VALUES START
> (100) END (200) can be assumed to hold.
Well, I was assuming an exact match, but a stricter match seems like a
nice-to-have...possibly later.
> > Should the be a *valid* constraint? Perhaps that should be
> > parameterized, as I'm not yet seeing a compelling argument either
> > direction. I'm picturing something like:
> >
> > ALTER TABLE table_name SET VALID PARTITION OF <parent> [TRUST]
> >
> > where TRUST would mean that an existing constraint need not be VALID.
>
> Hmm, I'd think this step must be able to assert the partition
> constraint beyond any doubt. If the DBA added the constraint and
> marked it invalid, she should first VALIDATE the constraint to make
> it valid by performing whatever steps necessary before. IOW, a full
> heap scan at least once is inevitable (the reason why we might want
> to make this a two step process at all). Am I missing something?
There are use cases where we need to warn people that their assertions
need to be true, and if those assertions are not true, this will
explode, leaving them to pick the resulting shrapnel out of their
faces. There are other parts of the system where this is true, as
when people write UDFs in C.
As I understand it, NOT VALID means, "I assert that the tuples already
here fit the constraint. Any changes will be checked against the
constraint."
I've seen cases where a gigantic amount of data is coming out of some
distributed system which holds the constraint as an invariant. This
let a DBA decide to add a NOT VALID constraint in order not to take
the hit of a second full scan of the data, which might have made the
import, and possibly the entire project, untenable.
See above.
> >> 5. Detach partition
> >>
> >> ALTER TABLE partitioned_table
> >> DETACH PARTITION partition_name [USING table_name]
> >>
> >> This removes partition_name as partition of partitioned_table.
> >> The table continues to exist with the same name or 'table_name',
> >> if specified. pg_class.relispartition is set to false for the
> >> table, so it behaves like a normal table.
> >
> > Could this take anything short of an access exclusive lock on the
> > parent?
>
> Yes, both the step 1 of ATTACH command and DETACH command take
> access exclusive lock on the parent. They are rather quick metadata
> changes, so should not stall others significantly, I think.
So no. Weakening required locks has been something of an ongoing
project, project-wide, and need not be part of the first cut of this
long-needed feature.
Thanks so much for working on this!
Cheers,
David.
--
David Fetter <david@fetter.org> http://fetter.org/
Phone: +1 415 235 3778 AIM: dfetter666 Yahoo!: dfetter
Skype: davidfetter XMPP: david.fetter@gmail.com
Remember to vote!
Consider donating to Postgres: http://www.postgresql.org/about/donate
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 161+ messages in thread
* Re: Declarative partitioning
@ 2015-08-19 19:55 Marc Mamin <M.Mamin@intershop.de>
parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
0 siblings, 0 replies; 161+ messages in thread
From: Marc Mamin @ 2015-08-19 19:55 UTC (permalink / raw)
To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; pgsql-hackers
>On 2015-08-19 AM 02:57, Marc Mamin wrote:
>>> 2. Creating a partition of a partitioned table
>>>
>>> CREATE TABLE table_name
>>> PARTITION OF partitioned_table_name
>>> FOR VALUES values_spec;
>>>
>>> Where values_spec is:
>>>
>>> listvalues: [IN] (val1, ...)
>>>
>>
>> Would it make sense to allow one complementary partition to the listvalues?
>>
>> listvalues: [[NOT] IN] (val1, ...)
>>
>> I've thought a few times about moving data with some most common values to dedicated partitions
>> and keeping the rest in a separate one...
>>
>
>Thanks, that's definitely something to consider.
>
>I have been thinking of a sort of default list partition for the "rest" of
>values. Would you rather declare that with something like the below than
>having to enumerate all the values in a NOT IN list? Or the NOT IN way is
>more intuitive/friendly?
>
>CREATE TABLE _rest PARTITION OF table_name FOR VALUES [ IN ] DEFAULT
>
>Of course, at most one such partition would be allowed.
On the one hand I guess it will be easies to check for partition overlapping if their definitions all contain the exact allowed values.
But this could be generalized to range partitions too:
CREATE TABLE _rest FALLBACK PARTITION OF table_name
The need for it for range partitions seems very narrow at the first glimpse, but I remember bore administrative work in order to ensure that there always was a partition available for incoming data (from a very old time when I was still working with Oracle).
To have it comfortable and nevertheless allow to define new partitions, this would require to always check/move data from the default partition to new partitions at create time.
and 2 other thoughts:
- In your proposal, the parent table is not materialized at all. Could it be used for the fallback partition?
- what about always having a fallback partition? This would reduce the risk of unexpected failures and somewhat help Postgres stand out from the crowd :)
regards,
Marc Mamin
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 161+ messages in thread
* Re: Declarative partitioning
@ 2015-08-19 20:10 Josh Berkus <josh@agliodbs.com>
parent: Simon Riggs <simon@2ndQuadrant.com>
0 siblings, 2 replies; 161+ messages in thread
From: Josh Berkus @ 2015-08-19 20:10 UTC (permalink / raw)
To: Simon Riggs <simon@2ndQuadrant.com>; +Cc: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; pgsql-hackers
On 08/19/2015 04:59 AM, Simon Riggs wrote:
> I like the idea of a regular partitioning step because it is how you
> design such tables - "lets use monthly partitions".
>
> This gives sanely terse syntax, rather than specifying pages and pages
> of exact values in DDL....
>
> PARTITION BY RANGE ON (columns) INCREMENT BY (INTERVAL '1 month' )
> START WITH value;
Oh, I like that syntax!
How would it work if there were multiple columns? Maybe we don't want
to allow that for this form?
--
Josh Berkus
PostgreSQL Experts Inc.
http://pgexperts.com
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 161+ messages in thread
* Re: Declarative partitioning
@ 2015-08-19 20:18 Thom Brown <thom@linux.com>
parent: Josh Berkus <josh@agliodbs.com>
1 sibling, 1 reply; 161+ messages in thread
From: Thom Brown @ 2015-08-19 20:18 UTC (permalink / raw)
To: Josh Berkus <josh@agliodbs.com>; +Cc: Simon Riggs <simon@2ndquadrant.com>; Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; pgsql-hackers
On 19 August 2015 at 21:10, Josh Berkus <josh@agliodbs.com> wrote:
> On 08/19/2015 04:59 AM, Simon Riggs wrote:
> > I like the idea of a regular partitioning step because it is how you
> > design such tables - "lets use monthly partitions".
> >
> > This gives sanely terse syntax, rather than specifying pages and pages
> > of exact values in DDL....
> >
> > PARTITION BY RANGE ON (columns) INCREMENT BY (INTERVAL '1 month' )
> > START WITH value;
>
> Oh, I like that syntax!
>
> How would it work if there were multiple columns? Maybe we don't want
> to allow that for this form?
>
If we went with that, and had:
CREATE TABLE orders (order_id serial, order_date date, item text)
PARTITION BY RANGE ON (order_date) INCREMENT BY (INTERVAL '1 month')
START WITH '2015-01-01';
Where would the following go?
INSERT INTO orders (order_date, item) VALUES ('2014-11-12', 'Old item');
Would there automatically be an "others" partition? Or would it produce an
error and act like a constraint?
Thom
^ permalink raw reply [nested|flat] 161+ messages in thread
* Re: Declarative partitioning
@ 2015-08-19 22:16 Josh Berkus <josh@agliodbs.com>
parent: Thom Brown <thom@linux.com>
0 siblings, 0 replies; 161+ messages in thread
From: Josh Berkus @ 2015-08-19 22:16 UTC (permalink / raw)
To: Thom Brown <thom@linux.com>; +Cc: Simon Riggs <simon@2ndquadrant.com>; Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; pgsql-hackers
On 08/19/2015 01:18 PM, Thom Brown wrote:
> On 19 August 2015 at 21:10, Josh Berkus <josh@agliodbs.com
> <mailto:josh@agliodbs.com>> wrote:
>
> On 08/19/2015 04:59 AM, Simon Riggs wrote:
> > I like the idea of a regular partitioning step because it is how you
> > design such tables - "lets use monthly partitions".
> >
> > This gives sanely terse syntax, rather than specifying pages and pages
> > of exact values in DDL....
> >
> > PARTITION BY RANGE ON (columns) INCREMENT BY (INTERVAL '1 month' )
> > START WITH value;
>
> Oh, I like that syntax!
>
> How would it work if there were multiple columns? Maybe we don't want
> to allow that for this form?
>
>
> If we went with that, and had:
>
> CREATE TABLE orders (order_id serial, order_date date, item text)
> PARTITION BY RANGE ON (order_date) INCREMENT BY (INTERVAL '1 month')
> START WITH '2015-01-01';
>
> Where would the following go?
>
> INSERT INTO orders (order_date, item) VALUES ('2014-11-12', 'Old item');
>
> Would there automatically be an "others" partition? Or would it produce
> an error and act like a constraint?
The "others" partition was brought up upthread, as an addition to the
original proposal. I really think that an "others" partition needs to
be up to the DBA; I've seen apps where they'd want to capture it, and
apps where they'd want such an insert to error.
I, for one, would be OK with a new partitioning which didn't address the
"others" partition issue until 9.7; I see it as a wholly separable
improvement.
Plus, you can always *manually* add high/low catchall partitions.
--
Josh Berkus
PostgreSQL Experts Inc.
http://pgexperts.com
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 161+ messages in thread
* Re: Declarative partitioning
@ 2015-08-20 02:16 Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
parent: Simon Riggs <simon@2ndQuadrant.com>
0 siblings, 3 replies; 161+ messages in thread
From: Amit Langote @ 2015-08-20 02:16 UTC (permalink / raw)
To: Simon Riggs <simon@2ndQuadrant.com>; +Cc: pgsql-hackers
On 2015-08-19 PM 09:23, Simon Riggs wrote:
> On 18 August 2015 at 11:30, Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
> wrote:
>
>>
>> There is no need to define tuple routing triggers. CopyFrom() and
>> ExecInsert() determine target partition just before performing
>> heap_insert() and ExecInsertIndexTuples(). IOW, any BR triggers and
>> constraints (on parent) are executed for tuple before being routed to a
>> partition. If no partition can be found, it's an error.
>>
>> Because row-level AFTER triggers need to save ItemPointers in trigger
>> event data and defining triggers on partitions (which is where tuples
>> really go) is not allowed, I could not find a straightforward way to
>> implement them. So, perhaps we should allow (only) row-level AFTER
>> triggers on partitions or think of modifying trigger.c to know about this
>> twist explicitly.
>>
>
> I think tables will eventually need FK support; its not sustainable as a
> long term restriction, though perhaps its something we can do in a later
> patch.
>
Sure. Solving the row-level AFTER trigger problem should hopefully open up
the possibility of partitioned-table-as-FK-rel implementation.
> You haven't specified what would happen if an UPDATE would change a row's
> partition. I'm happy to add this to the list of restrictions by saying that
> the partition key cannot be updated.
>
UPDATEs that change a row's partition would cause error. I haven't
implemented that yet but will that way in the next patch.
By the last sentence, do you mean only UPDATEs to the partition key that
cause rows to jump partitions or simply any UPDATEs to the partition key?
> We'll need regression tests that cover each restriction and docs that
> match. This is not something we should leave until last. People read the
> docs to understand the feature, helping them to reach consensus. So it is
> for you to provide the docs before, not wait until later. I will begin a
> code review once you tell me docs and tests are present. We all want the
> feature, so its all about the details now.
>
Sorry, should have added tests and docs already. I will add them in the
next version of the patch. Thanks for willing to review.
Thanks,
Amit
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 161+ messages in thread
* Re: Declarative partitioning
@ 2015-08-20 07:05 Michael Paquier <michael.paquier@gmail.com>
parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2 siblings, 0 replies; 161+ messages in thread
From: Michael Paquier @ 2015-08-20 07:05 UTC (permalink / raw)
To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; +Cc: Simon Riggs <simon@2ndquadrant.com>; pgsql-hackers
On Thu, Aug 20, 2015 at 11:16 AM, Amit Langote <
Langote_Amit_f8@lab.ntt.co.jp> wrote:
> On 2015-08-19 PM 09:23, Simon Riggs wrote:
> > We'll need regression tests that cover each restriction and docs that
> > match. This is not something we should leave until last. People read the
> > docs to understand the feature, helping them to reach consensus. So it is
> > for you to provide the docs before, not wait until later. I will begin a
> > code review once you tell me docs and tests are present. We all want the
> > feature, so its all about the details now.
> >
>
> Sorry, should have added tests and docs already. I will add them in the
> next version of the patch.
>
Yes, those would be really good to have before any review so as it is
possible to grasp an understanding of what this patch does. I would like to
look at it as well more in depths.
> Thanks for willing to review.
>
Really thanks for working on that! I am sure you are going to get a lot of
feedback.
Regards,
--
Michael
^ permalink raw reply [nested|flat] 161+ messages in thread
* Re: Declarative partitioning
@ 2015-08-20 07:43 Simon Riggs <simon@2ndQuadrant.com>
parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2 siblings, 0 replies; 161+ messages in thread
From: Simon Riggs @ 2015-08-20 07:43 UTC (permalink / raw)
To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; +Cc: Simon Riggs <simon@2ndquadrant.com>; pgsql-hackers
On 20 August 2015 at 03:16, Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
wrote:
> Sorry, should have added tests and docs already. I will add them in the
> next version of the patch. Thanks for willing to review.
>
Thanks for picking up this challenge. It's easier if you have someone
interested all the way.
--
Simon Riggs http://www.2ndQuadrant.com/
<http://www.2ndquadrant.com/;
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
^ permalink raw reply [nested|flat] 161+ messages in thread
* Re: Declarative partitioning
@ 2015-08-20 09:10 Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
parent: Josh Berkus <josh@agliodbs.com>
1 sibling, 1 reply; 161+ messages in thread
From: Amit Langote @ 2015-08-20 09:10 UTC (permalink / raw)
To: Josh Berkus <josh@agliodbs.com>; Simon Riggs <simon@2ndQuadrant.com>; +Cc: pgsql-hackers
On 2015-08-20 AM 05:10, Josh Berkus wrote:
> On 08/19/2015 04:59 AM, Simon Riggs wrote:
>> I like the idea of a regular partitioning step because it is how you
>> design such tables - "lets use monthly partitions".
>>
>> This gives sanely terse syntax, rather than specifying pages and pages
>> of exact values in DDL....
>>
>> PARTITION BY RANGE ON (columns) INCREMENT BY (INTERVAL '1 month' )
>> START WITH value;
>
> Oh, I like that syntax!
>
> How would it work if there were multiple columns? Maybe we don't want
> to allow that for this form?
>
Yea, we could simply restrict it to the single column case, which does not
sound like a major restriction.
Thanks,
Amit
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 161+ messages in thread
* Re: Declarative partitioning
@ 2015-08-20 09:27 Pavan Deolasee <pavan.deolasee@gmail.com>
parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
9 siblings, 1 reply; 161+ messages in thread
From: Pavan Deolasee @ 2015-08-20 09:27 UTC (permalink / raw)
To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; +Cc: pgsql-hackers
On Tue, Aug 18, 2015 at 4:00 PM, Amit Langote <Langote_Amit_f8@lab.ntt.co.jp
> wrote:
>
> Hi,
>
> I would like propose $SUBJECT for this development cycle. Attached is a
> WIP patch that implements most if not all of what's described below. Some
> yet unaddressed parts are mentioned below, too. I'll add this to the
> CF-SEP.
>
> Syntax
> ======
>
> 1. Creating a partitioned table
>
> CREATE TABLE table_name
> PARTITION BY {RANGE|LIST}
> ON (column_list);
>
> Where column_list consists of simple column names or expressions:
>
> PARTITION BY LIST ON (name)
> PARTITION BY RANGE ON (year, month)
>
> PARTITION BY LIST ON ((lower(left(name, 2)))
> PARTITION BY RANGE ON ((extract(year from d)), (extract(month from d)))
>
>
How about HASH partitioning? Are there plans to support foreign tables as
partitions?
Thanks,
Pavan
--
Pavan Deolasee http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services
^ permalink raw reply [nested|flat] 161+ messages in thread
* Re: Declarative partitioning
@ 2015-08-20 09:34 Simon Riggs <simon@2ndQuadrant.com>
parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
0 siblings, 1 reply; 161+ messages in thread
From: Simon Riggs @ 2015-08-20 09:34 UTC (permalink / raw)
To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; +Cc: Josh Berkus <josh@agliodbs.com>; Simon Riggs <simon@2ndquadrant.com>; pgsql-hackers
On 20 August 2015 at 10:10, Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
wrote:
> On 2015-08-20 AM 05:10, Josh Berkus wrote:
> > On 08/19/2015 04:59 AM, Simon Riggs wrote:
> >> I like the idea of a regular partitioning step because it is how you
> >> design such tables - "lets use monthly partitions".
> >>
> >> This gives sanely terse syntax, rather than specifying pages and pages
> >> of exact values in DDL....
> >>
> >> PARTITION BY RANGE ON (columns) INCREMENT BY (INTERVAL '1 month' )
> >> START WITH value;
> >
> > Oh, I like that syntax!
> >
> > How would it work if there were multiple columns? Maybe we don't want
> > to allow that for this form?
> >
>
> Yea, we could simply restrict it to the single column case, which does not
> sound like a major restriction.
>
PARTITION BY ...
SUBPARTITION BY ...
We should plan for that in the way we develop the internals, but full
support can wait until later patches.
My view has long been that the internals are they aspect here, not the
syntax. We need to be able to have a very fast partition-selection
mechanism that can be used in the planner or executor for each tuple.
Working backwards, we need a relcache representation that allows that, and
a catalog representation that allows that and syntax to match.
--
Simon Riggs http://www.2ndQuadrant.com/
<http://www.2ndquadrant.com/;
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
^ permalink raw reply [nested|flat] 161+ messages in thread
* Re: Declarative partitioning
@ 2015-08-20 09:45 Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
parent: Pavan Deolasee <pavan.deolasee@gmail.com>
0 siblings, 1 reply; 161+ messages in thread
From: Amit Langote @ 2015-08-20 09:45 UTC (permalink / raw)
To: Pavan Deolasee <pavan.deolasee@gmail.com>; +Cc: pgsql-hackers
On 2015-08-20 PM 06:27, Pavan Deolasee wrote:
> On Tue, Aug 18, 2015 at 4:00 PM, Amit Langote <Langote_Amit_f8@lab.ntt.co.jp
>> wrote:
>>
>> PARTITION BY LIST ON (name)
>> PARTITION BY RANGE ON (year, month)
>>
>> PARTITION BY LIST ON ((lower(left(name, 2)))
>> PARTITION BY RANGE ON ((extract(year from d)), (extract(month from d)))
>>
>>
>
> How about HASH partitioning? Are there plans to support foreign tables as
> partitions?
>
I've not given HASH partitioning a lot of thought yet. About the latter,
it seems it should not be too difficult to incorporate into the proposed
partitioning internals.
Thanks,
Amit
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 161+ messages in thread
* Re: Declarative partitioning
@ 2015-08-20 09:47 Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
parent: Simon Riggs <simon@2ndQuadrant.com>
0 siblings, 0 replies; 161+ messages in thread
From: Amit Langote @ 2015-08-20 09:47 UTC (permalink / raw)
To: Simon Riggs <simon@2ndQuadrant.com>; +Cc: Josh Berkus <josh@agliodbs.com>; pgsql-hackers
On 2015-08-20 PM 06:34, Simon Riggs wrote:
> On 20 August 2015 at 10:10, Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
> wrote:
>
>> On 2015-08-20 AM 05:10, Josh Berkus wrote:
>>>> PARTITION BY RANGE ON (columns) INCREMENT BY (INTERVAL '1 month' )
>>>> START WITH value;
>>>
>>> Oh, I like that syntax!
>>>
>>> How would it work if there were multiple columns? Maybe we don't want
>>> to allow that for this form?
>>>
>>
>> Yea, we could simply restrict it to the single column case, which does not
>> sound like a major restriction.
>>
>
> PARTITION BY ...
> SUBPARTITION BY ...
>
> We should plan for that in the way we develop the internals, but full
> support can wait until later patches.
>
At the moment, a form of SUBPARTITION BY is to allow PARTITION BY in a
partition definition. But I can see that may not be what people would expect.
> My view has long been that the internals are they aspect here, not the
> syntax. We need to be able to have a very fast partition-selection
> mechanism that can be used in the planner or executor for each tuple.
> Working backwards, we need a relcache representation that allows that, and
> a catalog representation that allows that and syntax to match.
>
Agreed.
Thanks,
Amit
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 161+ messages in thread
* Re: Declarative partitioning
@ 2015-08-20 09:57 Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
parent: Corey Huinker <corey.huinker@gmail.com>
0 siblings, 1 reply; 161+ messages in thread
From: Amit Langote @ 2015-08-20 09:57 UTC (permalink / raw)
To: Corey Huinker <corey.huinker@gmail.com>; +Cc: pgsql-hackers
On 2015-08-19 AM 02:59, Corey Huinker wrote:
>
> Quick thoughts borne of years of slugging it out with partitions on Oracle:
>
> - Finally!!!!!!!!!!!
>
> - Your range partitioning will need to express exclusive/inclusive bounds,
> or go to the Oracle model where every partition is a cascading "values less
> than" test context dependent on the partitions defined before it. I would
> suggest that leveraging existing range types (or allowing the user to
> specify a range type, like for a specific collation of a text range) would
> allow for the most flexible and postgres-ish range definition. You seem to
> do this with the "[USING] opclass_name" bit, but don't follow through on
> the START(...) and END(...). Something like FOR VALUES <@
> '[''2014-01-01'',''2015-01-01)'::daterange would cover most needs
> succinctly, though I admit the syntax for complex ranges could be
> cumbersome, though something like FOR VALUES <@
> '[(''a'',1),(''b'',1))'::letter_then_number_range is still readable.
>
It seems the way of specifying per-partition definition/constraint,
especially for range partitioning, would have a number of interesting
alternatives.
By the way, the [USING opclass_name] bit is just a way of telling that a
particular key column has user-defined notion of "ordering" in case of
range partitioning and "equality" for list partitioning. The opclass would
eventually determine which WHERE clauses (looking at operators, operand
types) are candidates to help prune partitions. If we use the range_op
range_literal::range_type notation to describe partition constraint for
each partition, it might not offer much beyond the readability. We are not
really going to detect range operators being applied in WHERE conditions
to trigger partition pruning, for example. Although I may be missing
something...
> - No partitioning scheme survives first contact with reality. So you will
> need a facility for splitting and joining existing partitions. For
> splitting partitions, it's sufficient to require that the new partition
> share either a upper/lower bound (with the same inclusivity/exclusivity) of
> an existing partition, thus uniquely identifying the partition to be split,
> and require that the other bound be within the range of the partition to be
> split. Similarly, it's fair to require that the partitions to be joined be
> adjacent in range. In both cases, range operators make these tests simple.
>
SPLIT/MERGE can be done in later patches/release, I think.
> - Your features 4 and 5 are implemented in Oracle with SWAP PARTITION,
> which is really neat for doing ETLs and index rebuilds offline in a copy
> table, and then swapping the data segment of that table with the partition
> specified. Which could be considered cheating because none of the partition
> metadata changed, just the pointers to the segments. We already do this
> with adding removing INHERIT. I'm not saying they can't be separate
> functionality, but keeping an atomic SWAP operation would be grand.
>
I think we can manage to find ways to make the proposed ATTACH/DETACH as
useful and convenient. Thanks for reminding of SWAP PARTITION.
Regards,
Amit
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 161+ messages in thread
* Re: Declarative partitioning
@ 2015-08-20 09:58 Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
parent: David Fetter <david@fetter.org>
0 siblings, 1 reply; 161+ messages in thread
From: Amit Langote @ 2015-08-20 09:58 UTC (permalink / raw)
To: David Fetter <david@fetter.org>; +Cc: pgsql-hackers
On 2015-08-19 PM 09:52, David Fetter wrote:
> On Wed, Aug 19, 2015 at 04:30:39PM +0900, Amit Langote wrote:
>>>
>>> One small change to make this part more efficient:
>>>
>>> 1. Take the access exclusive lock on table_name.
>>> 2. Check for a matching constraint on it.
>>> 3. If it's there, mark it as a valid partition.
>>> 4. If not, check for values outside the boundaries as above.
>>>
>>
>> That's an interesting idea. Thanks!
>
> I hope I'm advancing this feature rather than bogging it down...
>
Definitely advancing.
>> By a matching constraint, I guess you mean a 'valid' constraint from
>> which the declared partition constraint can be proven to follow. For
>> (a simple) example, from a CHECK (a >= 100 AND a < 150) on
>> table_name, the partition constraint implied by FOR VALUES START
>> (100) END (200) can be assumed to hold.
>
> Well, I was assuming an exact match, but a stricter match seems like a
> nice-to-have...possibly later.
>
>>> Should the be a *valid* constraint? Perhaps that should be
>>> parameterized, as I'm not yet seeing a compelling argument either
>>> direction. I'm picturing something like:
>>>
>>> ALTER TABLE table_name SET VALID PARTITION OF <parent> [TRUST]
>>>
>>> where TRUST would mean that an existing constraint need not be VALID.
>>
>> Hmm, I'd think this step must be able to assert the partition
>> constraint beyond any doubt. If the DBA added the constraint and
>> marked it invalid, she should first VALIDATE the constraint to make
>> it valid by performing whatever steps necessary before. IOW, a full
>> heap scan at least once is inevitable (the reason why we might want
>> to make this a two step process at all). Am I missing something?
>
> There are use cases where we need to warn people that their assertions
> need to be true, and if those assertions are not true, this will
> explode, leaving them to pick the resulting shrapnel out of their
> faces. There are other parts of the system where this is true, as
> when people write UDFs in C.
>
> As I understand it, NOT VALID means, "I assert that the tuples already
> here fit the constraint. Any changes will be checked against the
> constraint."
>
> I've seen cases where a gigantic amount of data is coming out of some
> distributed system which holds the constraint as an invariant. This
> let a DBA decide to add a NOT VALID constraint in order not to take
> the hit of a second full scan of the data, which might have made the
> import, and possibly the entire project, untenable.
>
> See above.
>
Ah, I understand the point of parameterization (TRUST). Seems like it
would be good to have with appropriate documentation of the same. Perhaps,
it might as well a parameter to the step 1 itself.
>>>> 5. Detach partition
>>>>
>>>> ALTER TABLE partitioned_table
>>>> DETACH PARTITION partition_name [USING table_name]
>>>>
>>>> This removes partition_name as partition of partitioned_table.
>>>> The table continues to exist with the same name or 'table_name',
>>>> if specified. pg_class.relispartition is set to false for the
>>>> table, so it behaves like a normal table.
>>>
>>> Could this take anything short of an access exclusive lock on the
>>> parent?
>>
>> Yes, both the step 1 of ATTACH command and DETACH command take
>> access exclusive lock on the parent. They are rather quick metadata
>> changes, so should not stall others significantly, I think.
>
> So no. Weakening required locks has been something of an ongoing
> project, project-wide, and need not be part of the first cut of this
> long-needed feature.
>
Do you mean ATTACH and DETACH, if they require access exclusive lock on
the parent, should not be in the first cut? Or am I misreading?
If so, there is no way to drop partitions. With the patch, it would be
achieved with detach and drop (if required).
> Thanks so much for working on this!
>
Thanks for the feedback and suggestions!
Regards,
Amit
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 161+ messages in thread
* Re: Declarative partitioning
@ 2015-08-20 12:36 David Steele <david@pgmasters.net>
parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
0 siblings, 1 reply; 161+ messages in thread
From: David Steele @ 2015-08-20 12:36 UTC (permalink / raw)
To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; Pavan Deolasee <pavan.deolasee@gmail.com>; +Cc: pgsql-hackers
On 8/20/15 5:45 AM, Amit Langote wrote:
> On 2015-08-20 PM 06:27, Pavan Deolasee wrote:
>> On Tue, Aug 18, 2015 at 4:00 PM, Amit Langote <Langote_Amit_f8@lab.ntt.co.jp
>>> wrote:
>>>
>>> PARTITION BY LIST ON (name)
>>> PARTITION BY RANGE ON (year, month)
>>>
>>> PARTITION BY LIST ON ((lower(left(name, 2)))
>>> PARTITION BY RANGE ON ((extract(year from d)), (extract(month from d)))
>>>
>>>
>>
>> How about HASH partitioning? Are there plans to support foreign tables as
>> partitions?
>>
>
> I've not given HASH partitioning a lot of thought yet. About the latter,
> it seems it should not be too difficult to incorporate into the proposed
> partitioning internals.
Hash partitioning seems like it could wait. If fact, I've nearly always
implemented hash partitions as list partitions. This gives me control
over the hash function and allows me to use properties of the key to my
advantage. For instance - if your key is a sha1 hash there's no need to
rehash, just grab the required number of bits off the end of the key.
My experiences with Oracle's hash function were generally not good -
there's a reason many hash algorithms exist. If/when we do hash
partitioning in Postgres I'd like to see the hash function be
user-definable.
Meanwhile, I think list and range are a good start. I'd prefer to see
sub-partitioning before hash partitioning.
--
-David
david@pgmasters.net
Attachments:
[application/pgp-signature] signature.asc (841B, ../../55D5C9E5.6040900@pgmasters.net/2-signature.asc)
download
^ permalink raw reply [nested|flat] 161+ messages in thread
* Re: Declarative partitioning
@ 2015-08-20 13:19 David Fetter <david@fetter.org>
parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
0 siblings, 2 replies; 161+ messages in thread
From: David Fetter @ 2015-08-20 13:19 UTC (permalink / raw)
To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; +Cc: pgsql-hackers
On Thu, Aug 20, 2015 at 06:58:24PM +0900, Amit Langote wrote:
> On 2015-08-19 PM 09:52, David Fetter wrote:
> > On Wed, Aug 19, 2015 at 04:30:39PM +0900, Amit Langote wrote:
> >
> > There are use cases where we need to warn people that their assertions
> > need to be true, and if those assertions are not true, this will
> > explode, leaving them to pick the resulting shrapnel out of their
> > faces. There are other parts of the system where this is true, as
> > when people write UDFs in C.
> >
> > As I understand it, NOT VALID means, "I assert that the tuples already
> > here fit the constraint. Any changes will be checked against the
> > constraint."
> >
> > I've seen cases where a gigantic amount of data is coming out of some
> > distributed system which holds the constraint as an invariant. This
> > let a DBA decide to add a NOT VALID constraint in order not to take
> > the hit of a second full scan of the data, which might have made the
> > import, and possibly the entire project, untenable.
>
> Ah, I understand the point of parameterization (TRUST). Seems like it
> would be good to have with appropriate documentation of the same. Perhaps,
> it might as well a parameter to the step 1 itself.
So TRUST would obviate step 2? Better still!
> >>>> 5. Detach partition
> >>>>
> >>>> ALTER TABLE partitioned_table
> >>>> DETACH PARTITION partition_name [USING table_name]
> >>>>
> >>>> This removes partition_name as partition of partitioned_table.
> >>>> The table continues to exist with the same name or 'table_name',
> >>>> if specified. pg_class.relispartition is set to false for the
> >>>> table, so it behaves like a normal table.
> >>>
> >>> Could this take anything short of an access exclusive lock on the
> >>> parent?
> >>
> >> Yes, both the step 1 of ATTACH command and DETACH command take
> >> access exclusive lock on the parent. They are rather quick metadata
> >> changes, so should not stall others significantly, I think.
> >
> > So no. Weakening required locks has been something of an ongoing
> > project, project-wide, and need not be part of the first cut of this
> > long-needed feature.
> >
>
> Do you mean ATTACH and DETACH, if they require access exclusive lock on
> the parent, should not be in the first cut? Or am I misreading?
Sorry I was unclear.
ATTACH and DETACH should be in the first cut even if they require an
access exclusive lock.
Cheers,
David.
--
David Fetter <david@fetter.org> http://fetter.org/
Phone: +1 415 235 3778 AIM: dfetter666 Yahoo!: dfetter
Skype: davidfetter XMPP: david.fetter@gmail.com
Remember to vote!
Consider donating to Postgres: http://www.postgresql.org/about/donate
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 161+ messages in thread
* Re: Declarative partitioning
@ 2015-08-20 15:51 Corey Huinker <corey.huinker@gmail.com>
parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
0 siblings, 0 replies; 161+ messages in thread
From: Corey Huinker @ 2015-08-20 15:51 UTC (permalink / raw)
To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; +Cc: pgsql-hackers
>
>
> It seems the way of specifying per-partition definition/constraint,
> especially for range partitioning, would have a number of interesting
> alternatives.
>
> By the way, the [USING opclass_name] bit is just a way of telling that a
> particular key column has user-defined notion of "ordering" in case of
> range partitioning and "equality" for list partitioning. The opclass would
> eventually determine which WHERE clauses (looking at operators, operand
> types) are candidates to help prune partitions. If we use the range_op
> range_literal::range_type notation to describe partition constraint for
> each partition, it might not offer much beyond the readability. We are not
> really going to detect range operators being applied in WHERE conditions
> to trigger partition pruning, for example. Although I may be missing
> something...
>
I don't think it's important that ranges be used internally for partition
pruning, though I don't see a reason why they couldn't. I was making the
case for range types being used at partition creation/declaration time,
because you were proposing new syntax to describe a range of values in text
when we already have that in range types. We should eat our own dog food.
But mostly, I wanted to make sure that range partitions could have
inclusive and exclusive bounds.
>
> > - No partitioning scheme survives first contact with reality. So you will
> > need a facility for splitting and joining existing partitions. For
> > splitting partitions, it's sufficient to require that the new partition
> > share either a upper/lower bound (with the same inclusivity/exclusivity)
> of
> > an existing partition, thus uniquely identifying the partition to be
> split,
> > and require that the other bound be within the range of the partition to
> be
> > split. Similarly, it's fair to require that the partitions to be joined
> be
> > adjacent in range. In both cases, range operators make these tests
> simple.
> >
>
> SPLIT/MERGE can be done in later patches/release, I think.
>
Later patches for sure. When a partition "fills up", it's a critical
performance issue, so the fewer steps needed to amend it the better.
^ permalink raw reply [nested|flat] 161+ messages in thread
* Re: Declarative partitioning
@ 2015-08-20 16:00 Corey Huinker <corey.huinker@gmail.com>
parent: David Steele <david@pgmasters.net>
0 siblings, 0 replies; 161+ messages in thread
From: Corey Huinker @ 2015-08-20 16:00 UTC (permalink / raw)
To: David Steele <david@pgmasters.net>; +Cc: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; Pavan Deolasee <pavan.deolasee@gmail.com>; pgsql-hackers
>
>
> My experiences with Oracle's hash function were generally not good -
> there's a reason many hash algorithms exist. If/when we do hash
> partitioning in Postgres I'd like to see the hash function be
> user-definable.
>
+1
In my experience, hash partitioning had one use: When you had run out of
ways to logically partition the data, AND you had two tables in a foreign
key relationship, AND that relationship was the most common use-case for
selecting the two tables. In which case, your one big join became 8 or 16
little ones, and all was well in the universe...
...until those little joins started to spill to disk, and now you need to
repartition by 2x hashes, and that basically means a reload of your primary
fact table, and a talk with a boss about why you painted yourself into a
corner when you first did that.
>
> Meanwhile, I think list and range are a good start. I'd prefer to see
> sub-partitioning before hash partitioning.
>
+1
^ permalink raw reply [nested|flat] 161+ messages in thread
* Re: Declarative partitioning
@ 2015-08-20 17:36 Josh Berkus <josh@agliodbs.com>
parent: David Fetter <david@fetter.org>
1 sibling, 0 replies; 161+ messages in thread
From: Josh Berkus @ 2015-08-20 17:36 UTC (permalink / raw)
To: David Fetter <david@fetter.org>; Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; +Cc: pgsql-hackers
On 08/20/2015 06:19 AM, David Fetter wrote:
> On Thu, Aug 20, 2015 at 06:58:24PM +0900, Amit Langote wrote:
>> Do you mean ATTACH and DETACH, if they require access exclusive lock on
>> the parent, should not be in the first cut? Or am I misreading?
>
> Sorry I was unclear.
>
> ATTACH and DETACH should be in the first cut even if they require an
> access exclusive lock.
>
> Cheers,
> David.
I don't see a way for them to *ever* not require an access exclusive lock.
We could eventually implement:
DETACH PARTITION CONCURRENTLY
... but that's the only way I can see around it.
--
Josh Berkus
PostgreSQL Experts Inc.
http://pgexperts.com
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 161+ messages in thread
* Re: Declarative partitioning
@ 2015-08-20 21:27 David Fetter <david@fetter.org>
parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2 siblings, 1 reply; 161+ messages in thread
From: David Fetter @ 2015-08-20 21:27 UTC (permalink / raw)
To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; +Cc: Simon Riggs <simon@2ndQuadrant.com>; pgsql-hackers
On Thu, Aug 20, 2015 at 11:16:37AM +0900, Amit Langote wrote:
> On 2015-08-19 PM 09:23, Simon Riggs wrote:
> > On 18 August 2015 at 11:30, Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
> > wrote:
> > You haven't specified what would happen if an UPDATE would change a row's
> > partition. I'm happy to add this to the list of restrictions by saying that
> > the partition key cannot be updated.
>
> UPDATEs that change a row's partition would cause error. I haven't
> implemented that yet but will that way in the next patch.
>
> By the last sentence, do you mean only UPDATEs to the partition key that
> cause rows to jump partitions or simply any UPDATEs to the partition key?
I don't know what Simon had in mind, but it seems to me that we have
the following in descending order of convenience to users, and I
presume, descending order of difficulty of implementation:
1. Updates propagate transparently, moving rows between partitions if needed.
2. Updates fail only if the change to a partition key would cause the
row to have to move to another partition.
3. All updates to the partition key fail.
Whichever of these we choose, we should document it with great
clarity.
> > We'll need regression tests that cover each restriction and docs
> > that match. This is not something we should leave until last.
> > People read the docs to understand the feature, helping them to
> > reach consensus. So it is for you to provide the docs before, not
> > wait until later. I will begin a code review once you tell me docs
> > and tests are present. We all want the feature, so its all about
> > the details now.
>
> Sorry, should have added tests and docs already. I will add them in
> the next version of the patch. Thanks for willing to review.
Docs and tests are crucial, and thanks again for taking this on.
Cheers,
David.
--
David Fetter <david@fetter.org> http://fetter.org/
Phone: +1 415 235 3778 AIM: dfetter666 Yahoo!: dfetter
Skype: davidfetter XMPP: david.fetter@gmail.com
Remember to vote!
Consider donating to Postgres: http://www.postgresql.org/about/donate
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 161+ messages in thread
* Re: Declarative partitioning
@ 2015-08-21 05:47 Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
parent: David Fetter <david@fetter.org>
1 sibling, 0 replies; 161+ messages in thread
From: Amit Langote @ 2015-08-21 05:47 UTC (permalink / raw)
To: David Fetter <david@fetter.org>; +Cc: pgsql-hackers
On 2015-08-20 PM 10:19, David Fetter wrote:
> On Thu, Aug 20, 2015 at 06:58:24PM +0900, Amit Langote wrote:
>>
>> Ah, I understand the point of parameterization (TRUST). Seems like it
>> would be good to have with appropriate documentation of the same. Perhaps,
>> it might as well a parameter to the step 1 itself.
>
> So TRUST would obviate step 2? Better still!
>
I really wish we could do this in one step, but...
As you know, primary intention behind keeping step 2 separate is to do the
heavy validation work with access exclusive lock on the other table
instead of on the master table.
Assuming we add TRUST TO step 1 (with implications clarified in docs),
when/if the user does not use TRUST, the default behavior being to perform
the validation, would it be OK as it would have to take the expensive lock
on the master table?
Motivation for keeping the step 2 (and TRUST parameter with it) is really
to avoid this last concern.
All that said, we'd still need some way to tell rows that came from a
TRUSTed table but do not really belong to the partition because we skipped
checking that at all. Obviously one can always write a query on the
partition that can find them, but some form of DDL would be what people
might expect.
>>
>> Do you mean ATTACH and DETACH, if they require access exclusive lock on
>> the parent, should not be in the first cut? Or am I misreading?
>
> Sorry I was unclear.
>
> ATTACH and DETACH should be in the first cut even if they require an
> access exclusive lock.
>
Ah, got it.
Thanks,
Amit
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 161+ messages in thread
* Re: Declarative partitioning
@ 2015-08-21 05:52 Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
parent: David Fetter <david@fetter.org>
0 siblings, 1 reply; 161+ messages in thread
From: Amit Langote @ 2015-08-21 05:52 UTC (permalink / raw)
To: David Fetter <david@fetter.org>; +Cc: Simon Riggs <simon@2ndQuadrant.com>; pgsql-hackers
On 2015-08-21 AM 06:27, David Fetter wrote:
>> By the last sentence, do you mean only UPDATEs to the partition key that
>> cause rows to jump partitions or simply any UPDATEs to the partition key?
>
> I don't know what Simon had in mind, but it seems to me that we have
> the following in descending order of convenience to users, and I
> presume, descending order of difficulty of implementation:
>
> 1. Updates propagate transparently, moving rows between partitions if needed.
>
> 2. Updates fail only if the change to a partition key would cause the
> row to have to move to another partition.
>
> 3. All updates to the partition key fail.
>
I was thinking I'd implement 2.
There was some discussion last year[1] about how 1 could be realized.
Thanks,
Amit
[1]
http://www.postgresql.org/message-id/20140829172216.GF10109@awork2.anarazel.de
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 161+ messages in thread
* Re: Declarative partitioning
@ 2015-08-21 06:20 Pavan Deolasee <pavan.deolasee@gmail.com>
parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
0 siblings, 0 replies; 161+ messages in thread
From: Pavan Deolasee @ 2015-08-21 06:20 UTC (permalink / raw)
To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; +Cc: David Fetter <david@fetter.org>; Simon Riggs <simon@2ndquadrant.com>; pgsql-hackers
On Fri, Aug 21, 2015 at 11:22 AM, Amit Langote <
Langote_Amit_f8@lab.ntt.co.jp> wrote:
> On 2015-08-21 AM 06:27, David Fetter wrote:
>
> >> By the last sentence, do you mean only UPDATEs to the partition key that
> >> cause rows to jump partitions or simply any UPDATEs to the partition
> key?
> >
> > I don't know what Simon had in mind, but it seems to me that we have
> > the following in descending order of convenience to users, and I
> > presume, descending order of difficulty of implementation:
> >
> > 1. Updates propagate transparently, moving rows between partitions if
> needed.
> >
> > 2. Updates fail only if the change to a partition key would cause the
> > row to have to move to another partition.
> >
> > 3. All updates to the partition key fail.
> >
>
> I was thinking I'd implement 2.
>
>
+1. IMHO thats a good starting point.
Thanks,
Pavan
--
Pavan Deolasee http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services
^ permalink raw reply [nested|flat] 161+ messages in thread
* Re: Declarative partitioning
@ 2015-08-22 03:34 Jim Nasby <Jim.Nasby@BlueTreble.com>
parent: Josh Berkus <josh@agliodbs.com>
1 sibling, 1 reply; 161+ messages in thread
From: Jim Nasby @ 2015-08-22 03:34 UTC (permalink / raw)
To: Josh Berkus <josh@agliodbs.com>; Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; pgsql-hackers
On 8/18/15 12:31 PM, Josh Berkus wrote:
> Also this would be useful for range
> partitions:
>
> CREATE PARTITION ON parent_table USING ( start_value );
>
> ... where start_value is the start range of the new partition. Again,
> easier for users to get correct.
Instead of that, I think it would be more foolproof to do
CREATE PARTITION ON parent_table FOR ( value1, ... );
instead of trusting the user to get the exact start value correct.
Though... I guess there could be value in allowing an exact start value
but throwing an error if it doesn't sit exactly on a boundary. Might
make it less likely to accidentally create the wrong partition.
--
Jim Nasby, Data Architect, Blue Treble Consulting, Austin TX
Data in Trouble? Get it in Treble! http://BlueTreble.com
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 161+ messages in thread
* Re: Declarative partitioning
@ 2015-08-23 23:53 Josh Berkus <josh@agliodbs.com>
parent: Jim Nasby <Jim.Nasby@BlueTreble.com>
0 siblings, 1 reply; 161+ messages in thread
From: Josh Berkus @ 2015-08-23 23:53 UTC (permalink / raw)
To: Jim Nasby <Jim.Nasby@BlueTreble.com>; Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; pgsql-hackers
On 08/21/2015 08:34 PM, Jim Nasby wrote:
> On 8/18/15 12:31 PM, Josh Berkus wrote:
>> Also this would be useful for range
>> partitions:
>>
>> CREATE PARTITION ON parent_table USING ( start_value );
>>
>> ... where start_value is the start range of the new partition. Again,
>> easier for users to get correct.
>
> Instead of that, I think it would be more foolproof to do
>
> CREATE PARTITION ON parent_table FOR ( value1, ... );
>
> instead of trusting the user to get the exact start value correct.
>
> Though... I guess there could be value in allowing an exact start value
> but throwing an error if it doesn't sit exactly on a boundary. Might
> make it less likely to accidentally create the wrong partition.
Well, I'm figuring that most people would use "CREATE NEXT PARTITION"
instead.
--
Josh Berkus
PostgreSQL Experts Inc.
http://pgexperts.com
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 161+ messages in thread
* Re: Declarative partitioning
@ 2015-08-24 07:46 Simon Riggs <simon@2ndQuadrant.com>
parent: Josh Berkus <josh@agliodbs.com>
0 siblings, 1 reply; 161+ messages in thread
From: Simon Riggs @ 2015-08-24 07:46 UTC (permalink / raw)
To: Josh Berkus <josh@agliodbs.com>; +Cc: Jim Nasby <Jim.Nasby@bluetreble.com>; Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; pgsql-hackers
On 24 August 2015 at 00:53, Josh Berkus <josh@agliodbs.com> wrote:
> On 08/21/2015 08:34 PM, Jim Nasby wrote:
> > On 8/18/15 12:31 PM, Josh Berkus wrote:
> >> Also this would be useful for range
> >> partitions:
> >>
> >> CREATE PARTITION ON parent_table USING ( start_value );
> >>
> >> ... where start_value is the start range of the new partition. Again,
> >> easier for users to get correct.
> >
> > Instead of that, I think it would be more foolproof to do
> >
> > CREATE PARTITION ON parent_table FOR ( value1, ... );
> >
> > instead of trusting the user to get the exact start value correct.
> >
> > Though... I guess there could be value in allowing an exact start value
> > but throwing an error if it doesn't sit exactly on a boundary. Might
> > make it less likely to accidentally create the wrong partition.
>
> Well, I'm figuring that most people would use "CREATE NEXT PARTITION"
> instead.
>
ALTER TABLE foo ADD PARTITION NEXT;
when a sequence of partitions has been defined
--
Simon Riggs http://www.2ndQuadrant.com/
<http://www.2ndquadrant.com/;
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
^ permalink raw reply [nested|flat] 161+ messages in thread
* Re: Declarative partitioning
@ 2015-08-24 12:53 Andrew Dunstan <andrew@dunslane.net>
parent: Simon Riggs <simon@2ndQuadrant.com>
0 siblings, 0 replies; 161+ messages in thread
From: Andrew Dunstan @ 2015-08-24 12:53 UTC (permalink / raw)
To: Simon Riggs <simon@2ndQuadrant.com>; Josh Berkus <josh@agliodbs.com>; +Cc: Jim Nasby <Jim.Nasby@bluetreble.com>; Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; pgsql-hackers
On 08/24/2015 03:46 AM, Simon Riggs wrote:
>
>
> ALTER TABLE foo ADD PARTITION NEXT;
>
> when a sequence of partitions has been defined
>
>
Or perhaps
ALTER TABLE foo ADD PARTITION NEXT nn;
So you can set up, say, a week's worth of daily partitions at once.
I could also imagine a variant that allows you to specify partitions up
to the one including some value.
cheers
andrew
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 161+ messages in thread
* Re: Declarative partitioning
@ 2015-08-26 14:00 Simon Riggs <simon@2ndQuadrant.com>
parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
9 siblings, 1 reply; 161+ messages in thread
From: Simon Riggs @ 2015-08-26 14:00 UTC (permalink / raw)
To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; +Cc: pgsql-hackers
On 18 August 2015 at 11:30, Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
wrote:
> I would like propose $SUBJECT for this development cycle. Attached is a
> WIP patch that implements most if not all of what's described below. Some
> yet unaddressed parts are mentioned below, too. I'll add this to the
> CF-SEP.
>
Thanks for working on this. It's a great start.
> 3. Multi-level partitioning
>
> CREATE TABLE table_name
> PARTITION OF partitioned_table_name
> FOR VALUES values_spec
> PARTITION BY {RANGE|LIST} ON (columns_list)
>
> This variant implements a form of so called composite or sub-partitioning
> with arbitrarily deep partitioning structure. A table created using this
> form has both the relkind RELKIND_PARTITIONED_REL and
> pg_class.relispartition set to true.
>
Multi-level partitioning is probably going to complicate things beyond
sanity.
One RELKIND_PARTITIONED_REL with lots of partitions sounds best to me. We
can still have N dimensions of partitioning (or partitioning and
subpartitioning, if you prefer that term)
The patch does not yet implement any planner changes for partitioned
> tables, although I'm working on the same and post updates as soon as
> possible. That means, it is not possible to run SELECT/UPDATE/DELETE
> queries on partitioned tables without getting:
>
> postgres=# SELECT * FROM persons;
> ERROR: could not open file "base/13244/106975": No such file or directory
>
> Given that there would be more direct ways of performing partition pruning
> decisions with the proposed, it would be nice to utilize them.
> Specifically, I would like to avoid having to rely on constraint exclusion
> for partition pruning whereby subquery_planner() builds append_rel_list
> and the later steps exclude useless partitions.
>
This is really the heart of this patch/design. You can work for months on
all the rest of this, but you will live or die by how the optimization
works because that is the thing we really need to work well. Previous
attempts ignored this aspect and didn't get committed. It's hard, perhaps
even scary, but its critical. It's the 80/20 rule in reverse - 20% of the
code is 80% of the difficulty.
I suggest you write a partition query test script .sql and work towards
making this work. Not exhaustive and weird tests, but 5-10 key queries that
need to be optimized precisely and quickly. I'm sure that's been done
before.
Will include the following once we start reaching consensus on main parts
> of the proposed design/implementation:
>
> * New regression tests
> * Documentation updates
> * pg_dump, psql, etc.
>
> For reference, some immediately previous discussions:
>
> * On partitioning *
>
> http://www.postgresql.org/message-id/20140829155607.GF7705@eldon.alvh.no-ip.org
>
> * Partitioning WIP patch *
> http://www.postgresql.org/message-id/54EC32B6.9070605@lab.ntt.co.jp
If you want to achieve consensus, please write either docs or README files
that explain how this works.
It took me a few seconds to notice deviations from Alvaro's original post.
I shouldn't have to read a full thread to see what the conclusions were,
you need to record them coherently.
Some great examples of such things are
src/backend/optimizer/README
src/backend/access/nbtree/README
Please imagine how far such code would have got without them, then look at
the code comments on the top of each of the functions in that area for
examples of the clarity of design this needs.
Comments welcome!
>
Yes, comments in code are indeed welcome, as well as the README/docs.
I couldn't see why you invented a new form of Alter Table recursion.
We will need to support multi-row batched COPY.
I'm pleased to see this patch and will stay with it to completion, perhaps
others also. We have 3 more CFs in this release, Nov, Jan, Mar - so this
has a great chance of making it into 9.6. The current patch implements a
bunch of stuff, but its hard to say what, how or why it does it and without
the planner stuff its all moot. My recommendation is we say "Returned with
Feedback" on this now, looking forward to next patch.
If you submit another patch before Nov, I will review it without waiting
for Nov 1.
There will be much discussion on syntax, but that is not the key point. DDL
Support routines are usually pretty straightforward too, so that can be
left for now.
--
Simon Riggs http://www.2ndQuadrant.com/
<http://www.2ndquadrant.com/;
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
^ permalink raw reply [nested|flat] 161+ messages in thread
* Re: Declarative partitioning
@ 2015-08-26 14:01 Simon Riggs <simon@2ndquadrant.com>
parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
9 siblings, 1 reply; 161+ messages in thread
From: Simon Riggs @ 2015-08-26 14:01 UTC (permalink / raw)
To: pgsql-hackers
The following review has been posted through the commitfest application:
make installcheck-world: not tested
Implements feature: not tested
Spec compliant: not tested
Documentation: not tested
Needs planner work and tests of that. ALTER TABLE etc can wait.
The new status of this patch is: Waiting on Author
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 161+ messages in thread
* Re: Declarative partitioning
@ 2015-09-04 05:51 Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
parent: Simon Riggs <simon@2ndQuadrant.com>
0 siblings, 1 reply; 161+ messages in thread
From: Amit Langote @ 2015-09-04 05:51 UTC (permalink / raw)
To: Simon Riggs <simon@2ndQuadrant.com>; +Cc: pgsql-hackers
Sorry about the long delay in replying, to this message or the others
posted in the last few days. I should have notified in advance of my
vacation with rather limited Internet access.
On 2015-08-26 PM 11:00, Simon Riggs wrote:
> On 18 August 2015 at 11:30, Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
>
>> 3. Multi-level partitioning
>>
>> CREATE TABLE table_name
>> PARTITION OF partitioned_table_name
>> FOR VALUES values_spec
>> PARTITION BY {RANGE|LIST} ON (columns_list)
>>
>> This variant implements a form of so called composite or sub-partitioning
>> with arbitrarily deep partitioning structure. A table created using this
>> form has both the relkind RELKIND_PARTITIONED_REL and
>> pg_class.relispartition set to true.
>>
>
> Multi-level partitioning is probably going to complicate things beyond
> sanity.
>
> One RELKIND_PARTITIONED_REL with lots of partitions sounds best to me. We
> can still have N dimensions of partitioning (or partitioning and
> subpartitioning, if you prefer that term)
>
Implementation in the patch lets RELKIND_PARTITIONED_REL under another and
so on to which appears to give a capability to have arbitrarily deep
partitioning structure. That might feel unnecessary. Initial motivation to
go for such design was to not have to complicate the partition key catalog
with details of how to accommodate some notion of sub-partition template.
ISTM, most usecases (as covered by other databases) would not require to
use more than 2 levels. Maybe, we should consider catering to only that
set of usecases using some sub-partition template based design (and then
some specialized syntax).
Or, we could just not handle sub-partitioning initially, by which I mean
composite partitioning where you can have schemes such as range-list,
list-range, range-hash, etc. We do still have multi-column
range partitioning.
In any case, we have to have a design that will cater to the multi-level
partitioning.
>
>> The patch does not yet implement any planner changes for partitioned
>> tables, although I'm working on the same and post updates as soon as
>> possible. That means, it is not possible to run SELECT/UPDATE/DELETE
>> queries on partitioned tables without getting:
>>
>> postgres=# SELECT * FROM persons;
>> ERROR: could not open file "base/13244/106975": No such file or directory
>>
>> Given that there would be more direct ways of performing partition pruning
>> decisions with the proposed, it would be nice to utilize them.
>> Specifically, I would like to avoid having to rely on constraint exclusion
>> for partition pruning whereby subquery_planner() builds append_rel_list
>> and the later steps exclude useless partitions.
>>
>
> This is really the heart of this patch/design. You can work for months on
> all the rest of this, but you will live or die by how the optimization
> works because that is the thing we really need to work well. Previous
> attempts ignored this aspect and didn't get committed. It's hard, perhaps
> even scary, but its critical. It's the 80/20 rule in reverse - 20% of the
> code is 80% of the difficulty.
>
> I suggest you write a partition query test script .sql and work towards
> making this work. Not exhaustive and weird tests, but 5-10 key queries that
> need to be optimized precisely and quickly. I'm sure that's been done
> before.
>
Yes, I am working on this and hope to have something to show soon.
>
> I couldn't see why you invented a new form of Alter Table recursion.
>
It was intended to keep the ALTER TABLE considerations for inherited
tables (and typed tables) separate from those for partitioned tables. But...
This begs a larger question that I did not try to answer in this
design/patch - for partitions, do we need to have any catalog entries
other than the pg_class tuple? If we manage to not require them, we would
not need any AT recursion business at all except for cases that require
pg_class tuple updates. The pg_class tuple would be the only
authoritative catalog entry for partitions. Everything else belongs with
the master table. That includes catalog entries for attributes,
constraints, triggers, etc. Alvaro had mentioned something like this in
his proposal. Although, I hope that such radical design is not very
difficult to realize/code.
Also, we might have to go back to the slightly controversial question of
whether partitions share the same namespace as normal tables (partitioned
or not). In the patch, partitions are created using CREATE TABLE, altered
with ALTER TABLE (albeit with several restrictions). How about rather
partitions are created/altered using:
ALTER TABLE master CREATE PARTITION <name> ...,
ALTER TABLE master MODIFY PARTITION <name> ... (as mentioned above)
AT commands covered by the latter should only ever require updating the
pg_class tuple for the named partition.
ALTER TABLE <name> directly on a partition can be made to say something
like the following under this scheme:
ERROR: <name> is not a table
Although, I'm not thinking of hiding partitions from everyone. For
example, maintenance commands like VACUUM/ANALYZE (including autovacuum,
of course) would be able to see them.
> We will need to support multi-row batched COPY.
Currently, tuple-routing may switch the ResultRelInfo for every
consecutive tuple. So, at once I abandoned any hope of adapting the
routing mechanism to heap_multi_insert() API. But, perhaps there is a
smarter way. Let me think about that.
>
> I'm pleased to see this patch and will stay with it to completion, perhaps
> others also. We have 3 more CFs in this release, Nov, Jan, Mar - so this
> has a great chance of making it into 9.6. The current patch implements a
> bunch of stuff, but its hard to say what, how or why it does it and without
> the planner stuff its all moot. My recommendation is we say "Returned with
> Feedback" on this now, looking forward to next patch.
>
> If you submit another patch before Nov, I will review it without waiting
> for Nov 1.
>
> There will be much discussion on syntax, but that is not the key point. DDL
> Support routines are usually pretty straightforward too, so that can be
> left for now.
>
Sure, let me address number of points you have raised. In the upcoming
versions, I will try to provide extensive documentation (both internal and
user). As for the syntax and DDL changes you think can be dealt with
later, I tend to think let's keep the bare essentials necessary.
Thanks,
Amit
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 161+ messages in thread
* Re: Declarative partitioning
@ 2015-09-04 09:41 Simon Riggs <simon@2ndQuadrant.com>
parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
0 siblings, 0 replies; 161+ messages in thread
From: Simon Riggs @ 2015-09-04 09:41 UTC (permalink / raw)
To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; +Cc: Simon Riggs <simon@2ndquadrant.com>; pgsql-hackers
On 4 September 2015 at 06:51, Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
wrote:
>
> Sorry about the long delay in replying, to this message or the others
> posted in the last few days. I should have notified in advance of my
> vacation with rather limited Internet access.
No problem, I'm on leave too.
> >> The patch does not yet implement any planner changes for partitioned
> >> tables, although I'm working on the same and post updates as soon as
> >> possible.
>
...
> >
> > This is really the heart of this patch/design. You can work for months on
> > all the rest of this, but you will live or die by how the optimization
> > works because that is the thing we really need to work well. Previous
> > attempts ignored this aspect and didn't get committed. It's hard, perhaps
> > even scary, but its critical. It's the 80/20 rule in reverse - 20% of the
> > code is 80% of the difficulty.
> >
> > I suggest you write a partition query test script .sql and work towards
> > making this work. Not exhaustive and weird tests, but 5-10 key queries
> that
> > need to be optimized precisely and quickly. I'm sure that's been done
> > before.
> >
>
> Yes, I am working on this and hope to have something to show soon.
No rush, no pressure; lets get this right.
> >
> > I couldn't see why you invented a new form of Alter Table recursion.
> >
>
> It was intended to keep the ALTER TABLE considerations for inherited
> tables (and typed tables) separate from those for partitioned tables.
> But...
>
> This begs a larger question that I did not try to answer in this
> design/patch - for partitions, do we need to have any catalog entries
> other than the pg_class tuple?
Everything should start from the requirements of the optimization approach.
Once we have that clear, we can confirm other requirements.
--
Simon Riggs http://www.2ndQuadrant.com/
<http://www.2ndquadrant.com/;
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
^ permalink raw reply [nested|flat] 161+ messages in thread
* Re: Declarative partitioning
@ 2015-10-20 17:26 Thom Brown <thom@linux.com>
parent: Amit Langote <amitlangote09@gmail.com>
0 siblings, 1 reply; 161+ messages in thread
From: Thom Brown @ 2015-10-20 17:26 UTC (permalink / raw)
To: Amit Langote <amitlangote09@gmail.com>; +Cc: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; pgsql-hackers
On 18 August 2015 at 12:23, Amit Langote <amitlangote09@gmail.com> wrote:
> Hi Thom,
>
> On Tue, Aug 18, 2015 at 8:02 PM, Thom Brown <thom@linux.com> wrote:
>>
>>
>> Wow, didn't expect to see that email this morning.
>>
>> A very quick test:
>>
>> CREATE TABLE purchases (purchase_id serial, purchase_time timestamp, item
>> text) partition by range on ((extract(year from
>> purchase_time)),(extract(month from purchase_time)));
>> ERROR: referenced relation "purchases" is not a table or foreign table
>>
>
> Thanks for the quick test.
>
> Damn, I somehow missed adding the new relkind to a check in
> process_owned_by(). Will fix this and look for any such oversights.
This doesn't seem to have materialised. Are you still working on this?
Thom
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 161+ messages in thread
* Re: Declarative partitioning
@ 2015-10-20 17:34 Amit Langote <amitlangote09@gmail.com>
parent: Thom Brown <thom@linux.com>
0 siblings, 1 reply; 161+ messages in thread
From: Amit Langote @ 2015-10-20 17:34 UTC (permalink / raw)
To: Thom Brown <thom@linux.com>; +Cc: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; pgsql-hackers
On Wednesday, 21 October 2015, Thom Brown <thom@linux.com> wrote:
> On 18 August 2015 at 12:23, Amit Langote <amitlangote09@gmail.com
> <javascript:;>> wrote:
> > Hi Thom,
> >
> > On Tue, Aug 18, 2015 at 8:02 PM, Thom Brown <thom@linux.com
> <javascript:;>> wrote:
> >>
> >>
> >> Wow, didn't expect to see that email this morning.
> >>
> >> A very quick test:
> >>
> >> CREATE TABLE purchases (purchase_id serial, purchase_time timestamp,
> item
> >> text) partition by range on ((extract(year from
> >> purchase_time)),(extract(month from purchase_time)));
> >> ERROR: referenced relation "purchases" is not a table or foreign table
> >>
> >
> > Thanks for the quick test.
> >
> > Damn, I somehow missed adding the new relkind to a check in
> > process_owned_by(). Will fix this and look for any such oversights.
>
> This doesn't seem to have materialised. Are you still working on this?
>
Yes, I will be posting to this thread soon. Sorry about the silence.
Thanks,
Amit
^ permalink raw reply [nested|flat] 161+ messages in thread
* Re: Declarative partitioning
@ 2015-10-20 17:34 Thom Brown <thom@linux.com>
parent: Amit Langote <amitlangote09@gmail.com>
0 siblings, 0 replies; 161+ messages in thread
From: Thom Brown @ 2015-10-20 17:34 UTC (permalink / raw)
To: Amit Langote <amitlangote09@gmail.com>; +Cc: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; pgsql-hackers
On 20 October 2015 at 18:34, Amit Langote <amitlangote09@gmail.com> wrote:
>
>
> On Wednesday, 21 October 2015, Thom Brown <thom@linux.com> wrote:
>>
>> On 18 August 2015 at 12:23, Amit Langote <amitlangote09@gmail.com> wrote:
>> > Hi Thom,
>> >
>> > On Tue, Aug 18, 2015 at 8:02 PM, Thom Brown <thom@linux.com> wrote:
>> >>
>> >>
>> >> Wow, didn't expect to see that email this morning.
>> >>
>> >> A very quick test:
>> >>
>> >> CREATE TABLE purchases (purchase_id serial, purchase_time timestamp,
>> >> item
>> >> text) partition by range on ((extract(year from
>> >> purchase_time)),(extract(month from purchase_time)));
>> >> ERROR: referenced relation "purchases" is not a table or foreign table
>> >>
>> >
>> > Thanks for the quick test.
>> >
>> > Damn, I somehow missed adding the new relkind to a check in
>> > process_owned_by(). Will fix this and look for any such oversights.
>>
>> This doesn't seem to have materialised. Are you still working on this?
>
>
> Yes, I will be posting to this thread soon. Sorry about the silence.
Thanks. I'd like to test it again.
Thom
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 161+ messages in thread
* Re: Declarative partitioning
@ 2015-10-30 10:08 Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
parent: Simon Riggs <simon@2ndquadrant.com>
0 siblings, 1 reply; 161+ messages in thread
From: Amit Langote @ 2015-10-30 10:08 UTC (permalink / raw)
To: Simon Riggs <simon@2ndquadrant.com>; pgsql-hackers
On 2015/08/26 23:01, Simon Riggs wrote:
> The following review has been posted through the commitfest application:
>
...
>
> Needs planner work and tests of that. ALTER TABLE etc can wait.
>
> The new status of this patch is: Waiting on Author
Alright, after spending much time on this I came up with the attached. I'm
sincerely sorry this took so long.
I'll describe below what's in those patches.
The DDL and catalogs part are not much different from what I had last
described though I took a few steps to simplify things. I dropped the
multi-level partitioning bit and changed range partition creation syntax a
little. Ability to specify both min and max for every range partition had
made life much difficult to construct and use the internal representation.
Anyway, attached document patch (and regression tests to some degree)
might give a picture of what user commands look like as of now, although
things are very much subject to overhaul in this general area of user
visible commands and features.
As pointed out by Simon, there is a need to invent a better internal
representation of partitioning constraints/metadata to address the
planning considerations more effectively and scalably. To that end, I have
tried to implement a number of things - There is a proposed
set_partitioned_rel_size() that calls planner's partitioning module
(proposed) which takes RelOptInfo of a partitioned table and returns a
list of OIDs of partitions that are selected to be scanned based on the
baserestrictinfo (other partitions are pruned!) The partitioned rel size
is then computed using size estimates for selected partitions. Later,
proposed set_partitioned_rel_pathlist() will create a Append/MergeAppend
path with cheapest paths for individual partitions as its subpaths. It
seems important to point out that the selected partitions each get
simple_rte_array and a simple_rel_array slots (note that those arrays are
required to be repalloc'd to make those slots available). The partition
reloptinfos are of kind RELOPT_PARTITION_REL which are functionally quite
similar to RELOPT_OTHER_MEMBER_REL but different in that they do not
require using any auxiliary structures like AppendRelInfo and
append_rel_list. The parent-partition mapping would still be necessary and
is achieved using a field called parent_relid of partition reloptinfo.
The proposed partitioning module (in the planner) figures out which of the
clauses to use for partition pruning. The rules of matching clauses to
partition key look similar to those used to match them to an index, at
least with respect to the key representation. Key info includes attribute
number, expression tree (for expression key columns), operator family OID,
per key column. While clauses matched to an index would be returned as it
is to make index paths with, things work differently for clauses matched
to partition key column(s). For each matched clause, the Const operand and
the operator would be added to operand list and operator list,
respectively, for the matched column. Operands are later evaluated to
datums. The datum arrays along with operator OIDs are passed along to
another (general-purpose) partitioning module (proposed). That module
returns a list of OIDs of selected partitions after comparing the clause
operand datums with the partition bounds.
The operator strategies (of passed operators) coupled with partitioning
method (list or range, currently) determine how partitions are selected.
For some partitioning methods, faster partition selection is enabled, for
example, being able to use binary search helps make selecting range
partitions reasonably fast. Note that the clauses matched for a given
column are effectively ANDed with one another. At the moment, using OR
clauses disables partition pruning. But it would be a matter of improving
the structure used to pass around the operands and operators to also
convey the logical structure of clauses. Also, even though range
partitioning allows multi-column key, only the clauses referencing the
first key are used to select partitions. Multi-column range partition
pruning implementation is saved for later. Note that partition bounds are
themselves stored as arrays of datums computed on-demand (cached in
RelationData as part of a structure called PartitionDesc which stores
other info about partitions like OIDs.)
There are not many executor changes currently except for those made to
ensure sane I/U/D behaviors (made to respective ExecI/U/D functions). We
can certainly think about implementing specialized scan node for
partitioned tables but I put it off for now.
Attached user documentation and regression test patches, though not quite
comprehensive, should help one get started.
Thanks,
Amit
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Attachments:
[application/gzip] declarative-partitioning-wip-1.patch.tar.gz (71.2K, ../../563341AE.5010207@lab.ntt.co.jp/2-declarative-partitioning-wip-1.patch.tar.gz)
download
[text/x-diff] decl-partitioning-docs.patch (19.8K, ../../563341AE.5010207@lab.ntt.co.jp/3-decl-partitioning-docs.patch)
download | inline diff:
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 97ef618..ce02af9 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -214,6 +214,16 @@
</row>
<row>
+ <entry><link linkend="catalog-pg-partition"><structname>pg_partition</structname></link></entry>
+ <entry>information about partitions, including partition boundary</entry>
+ </row>
+
+ <row>
+ <entry><link linkend="catalog-pg-partitioned-rel"><structname>pg_partitioned_rel</structname></link></entry>
+ <entry>information about partitioned tables, including the partition key</entry>
+ </row>
+
+ <row>
<entry><link linkend="catalog-pg-pltemplate"><structname>pg_pltemplate</structname></link></entry>
<entry>template data for procedural languages</entry>
</row>
@@ -4629,6 +4639,161 @@
</sect1>
+ <sect1 id="catalog-pg-partition">
+ <title><structname>pg_partition</structname></title>
+
+ <indexterm zone="catalog-pg-partition">
+ <primary>pg_partition</primary>
+ </indexterm>
+
+ <para>
+ The catalog <structname>pg_partition</structname> stores
+ information about partitions of partitioned tables.
+ </para>
+
+ <table>
+ <title><structname>pg_partition</> Columns</title>
+
+ <tgroup cols="4">
+ <thead>
+ <row>
+ <entry>Name</entry>
+ <entry>Type</entry>
+ <entry>References</entry>
+ <entry>Description</entry>
+ </row>
+ </thead>
+
+ <tbody>
+
+ <row>
+ <entry><structfield>partrelid</structfield></entry>
+ <entry><type>oid</type></entry>
+ <entry><literal><link linkend="catalog-pg-class"><structname>pg_class</structname></link>.oid</literal></entry>
+ <entry>The OID of the <structname>pg_class</> entry for this partition</entry>
+ </row>
+
+ <row>
+ <entry><structfield>partparent</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 partitioned table this partition is part of</entry>
+ </row>
+
+ <row>
+ <entry><structfield>partlistvals</structfield></entry>
+ <entry><type>anyarray</type></entry>
+ <entry></entry>
+ <entry>For partitions of a list partitioned table, list of values assigned to this partition</entry>
+ </row>
+
+ <row>
+ <entry><structfield>partrangemaxs</structfield></entry>
+ <entry><type>anyarray</type></entry>
+ <entry></entry>
+ <entry>For partitions of a range partitioned table, array of maximum values per partition key column</entry>
+ </row>
+
+ </tbody>
+ </tgroup>
+ </table>
+
+ </sect1>
+
+ <sect1 id="catalog-pg-partitioned-rel">
+ <title><structname>pg_partitioned_rel</structname></title>
+
+ <indexterm zone="catalog-pg-partitioned-rel">
+ <primary>pg_partitioned_rel</primary>
+ </indexterm>
+
+ <para>
+ The catalog <structname>pg_partitioned</structname> stores
+ information about partition key of partitioned tables.
+ </para>
+
+ <table>
+ <title><structname>pg_partitioned_rel</> 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 (method); <literal>l</> = list partitioned table,
+ <literal>r</> = range partitioned table
+ </entry>
+ </row>
+
+ <row>
+ <entry><structfield>partnatts</structfield></entry>
+ <entry><type>int2</type></entry>
+ <entry></entry>
+ <entry>The number of columns in partition key</entry>
+ </row>
+
+ <row>
+ <entry><structfield>partkey</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
+ correspond to table columns used as partition key. For example, a value
+ of <literal>1 3</literal> would mean that the first and the third table
+ columns make up the partition key. A zero in this array indicates that the
+ corresponding partition key column is an expression over the table columns,
+ rather than a simple column reference.
+ </entry>
+ </row>
+
+ <row>
+ <entry><structfield>partclass</structfield></entry>
+ <entry><type>oidvector</type></entry>
+ <entry><literal><link linkend="catalog-pg-opclass"><structname>pg_opclass</structname></link>.oid</literal></entry>
+ <entry>
+ For each column in the partition key, this contains the OID of
+ the operator class to use. See
+ <link linkend="catalog-pg-opclass"><structname>pg_opclass</structname></link> for details.
+ </entry>
+ </row>
+
+ <row>
+ <entry><structfield>partexprs</structfield></entry>
+ <entry><type>pg_node_tree</type></entry>
+ <entry></entry>
+ <entry>
+ Expression trees (in <function>nodeToString()</function>
+ representation) for partition key columns that are not simple column
+ references. This is a list with one element for each zero
+ entry in <structfield>partkey</>. Null if all partition key columns
+ are simple references.
+ </entry>
+ </row>
+
+ </tbody>
+ </tgroup>
+ </table>
+
+ </sect1>
<sect1 id="catalog-pg-pltemplate">
<title><structname>pg_pltemplate</structname></title>
diff --git a/doc/src/sgml/ref/alter_table.sgml b/doc/src/sgml/ref/alter_table.sgml
index aca40f5..07b3000 100644
--- a/doc/src/sgml/ref/alter_table.sgml
+++ b/doc/src/sgml/ref/alter_table.sgml
@@ -77,12 +77,23 @@ ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable>
NOT OF
OWNER TO { <replaceable class="PARAMETER">new_owner</replaceable> | CURRENT_USER | SESSION_USER }
REPLICA IDENTITY { DEFAULT | USING INDEX <replaceable class="PARAMETER">index_name</replaceable> | FULL | NOTHING }
+ ATTACH PARTITION <replaceable class="PARAMETER">partition_name</replaceable> FOR VALUES { <replaceable class="PARAMETER">list_partition_values</replaceable> | <replaceable class="PARAMETER">range_partition_values</replaceable> } USING [ TABLE ] <replaceable class="PARAMETER">table_name</replaceable>
+ DETACH PARTITION <replaceable class="PARAMETER">partition_name</replaceable> [ USING [ TABLE ] <replaceable class="PARAMETER">table_name</replaceable> ]
<phrase>and <replaceable class="PARAMETER">table_constraint_using_index</replaceable> is:</phrase>
[ CONSTRAINT <replaceable class="PARAMETER">constraint_name</replaceable> ]
{ UNIQUE | PRIMARY KEY } USING INDEX <replaceable class="PARAMETER">index_name</replaceable>
[ DEFERRABLE | NOT DEFERRABLE ] [ INITIALLY DEFERRED | INITIALLY IMMEDIATE ]
+
+<phrase>and <replaceable class="PARAMETER">list_partition_values</replaceable> is:</phrase>
+
+[ IN ] ( { <replaceable class="PARAMETER">expression</replaceable> } [, ...] )
+
+<phrase>and <replaceable class="PARAMETER">range_partition_values</replaceable> is:</phrase>
+
+LESS THAN ( { <replaceable class="PARAMETER">expression</replaceable> } [, ...] )
+
</synopsis>
</refsynopsisdiv>
@@ -704,6 +715,32 @@ ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable>
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>ATTACH PARTITION <replaceable class="PARAMETER">partition_name</replaceable> FOR VALUES { <replaceable class="PARAMETER">list_partition_values</replaceable> | <replaceable class="PARAMETER">range_partition_values</replaceable> } USING [ TABLE ] <replaceable class="PARAMETER">table_name</replaceable></literal></term>
+ <listitem>
+ <para>
+ This form attaches a table as partition of the target table (presumably
+ partitioned.) To be attached as a partition, the source table must
+ already contain all the same columns as the parent. The columns must
+ have matching data types, and if they have <literal>NOT NULL</literal>
+ constraints in the target table then they must also have <literal>
+ NOT NULL</literal> constraints in the source table. The table is renamed
+ to the specified partition name. [Note: this command is currently
+ unimplemented.]
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><literal>DETACH PARTITION <replaceable class="PARAMETER">partition_name</replaceable> [ USING [ TABLE ] <replaceable class="PARAMETER">table_name</replaceable> ] </literal></term>
+ <listitem>
+ <para>
+ This form detaches a partition from the target table (presumably
+ partitioned.) The resulting table can optionally have a different
+ name after being detached.
+ </para>
+ </listitem>
+ </varlistentry>
</variablelist>
</para>
diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml
index a2d0b0c..2fa7bc8 100644
--- a/doc/src/sgml/ref/create_table.sgml
+++ b/doc/src/sgml/ref/create_table.sgml
@@ -28,6 +28,7 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
[, ... ]
] )
[ INHERITS ( <replaceable>parent_table</replaceable> [, ... ] ) ]
+[ PARTITION BY {RANGE | LIST} ON ( { <replaceable class="parameter">column_name</replaceable> | ( <replaceable class="parameter">expression</replaceable> ) } [ <replaceable class="parameter">opclass</replaceable> ] [, ...] )]
[ WITH ( <replaceable class="PARAMETER">storage_parameter</replaceable> [= <replaceable class="PARAMETER">value</replaceable>] [, ... ] ) | WITH OIDS | WITHOUT OIDS ]
[ ON COMMIT { PRESERVE ROWS | DELETE ROWS | DROP } ]
[ TABLESPACE <replaceable class="PARAMETER">tablespace_name</replaceable> ]
@@ -42,6 +43,13 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
[ ON COMMIT { PRESERVE ROWS | DELETE ROWS | DROP } ]
[ TABLESPACE <replaceable class="PARAMETER">tablespace_name</replaceable> ]
+CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name</replaceable>
+ PARTITION OF <replaceable class="PARAMETER">table_name</replaceable>
+ FOR VALUES { <replaceable class="PARAMETER">list_partition_values</replaceable> | <replaceable class="PARAMETER">range_partition_values</replaceable> }
+[ WITH ( <replaceable class="PARAMETER">storage_parameter</replaceable> [= <replaceable class="PARAMETER">value</replaceable>] [, ... ] ) ]
+[ ON COMMIT { PRESERVE ROWS | DELETE ROWS | DROP } ]
+[ TABLESPACE <replaceable class="PARAMETER">tablespace_name</replaceable> ]
+
<phrase>where <replaceable class="PARAMETER">column_constraint</replaceable> is:</phrase>
[ CONSTRAINT <replaceable class="PARAMETER">constraint_name</replaceable> ]
@@ -70,6 +78,14 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
{ INCLUDING | EXCLUDING } { DEFAULTS | CONSTRAINTS | INDEXES | STORAGE | COMMENTS | ALL }
+<phrase>and <replaceable class="PARAMETER">list_partition_values</replaceable> is:</phrase>
+
+[ IN ] ( { <replaceable class="PARAMETER">expression</replaceable> } [, ...] )
+
+<phrase>and <replaceable class="PARAMETER">range_partition_values</replaceable> is:</phrase>
+
+LESS THAN ( { <replaceable class="PARAMETER">expression</replaceable> } [, ...] )
+
<phrase><replaceable class="PARAMETER">index_parameters</replaceable> in <literal>UNIQUE</literal>, <literal>PRIMARY KEY</literal>, and <literal>EXCLUDE</literal> constraints are:</phrase>
[ WITH ( <replaceable class="PARAMETER">storage_parameter</replaceable> [= <replaceable class="PARAMETER">value</replaceable>] [, ... ] ) ]
@@ -230,6 +246,40 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
</varlistentry>
<varlistentry>
+ <term><literal>PARTITION OF <replaceable class="PARAMETER">table_name</replaceable> FOR VALUES { <replaceable class="PARAMETER">list_partition_values</replaceable> | <replaceable class="PARAMETER">range_partition_values</replaceable> }</literal></term>
+ <listitem>
+ <para>
+ Creates the table as a <firstterm>partition</firstterm> of the
+ specified partitioned table.
+ </para>
+
+ <para>
+ When a table is created as partition, the data types of the
+ columns, constraints are determined by the underlying partitioned
+ table and are not specified by the <literal>CREATE TABLE</literal>
+ command nor can they be added or altered after the fact using
+ ALTER TABLE. But the <literal>CREATE TABLE</literal> command
+ command can specify table storage parameters and tablespace for the
+ partition (and can be altered after the fact). The <literal>
+ FOR VALUES</literal> specification describes partition bounds,
+ that is, either a list of values or of maximum values of partition
+ key columns for list partitions and range partitions, respectively.
+ </para>
+
+ <para>
+ Care must be taken to specify correct values for the bounds.
+ Specifying bounds such that new partition overlaps with some existing
+ partition results in error. Rules for whether such overlaps occurs
+ are different based on the partitioning method used. For list
+ bounds, make sure that the new partition's list of values does
+ not contain any values already present in some existing partition's
+ list. For range bounds, make sure that the new partition's rangemax
+ value is greater than the last existing partition's rangemax.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><replaceable class="PARAMETER">column_name</replaceable></term>
<listitem>
<para>
@@ -314,6 +364,32 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
</varlistentry>
<varlistentry>
+ <term><literal>PARTITION BY {RANGE | LIST} ON ( { <replaceable class="parameter">column_name</replaceable> | ( <replaceable class="parameter">expression</replaceable> ) } [ <replaceable class="parameter">opclass</replaceable> ] [, ...] )</literal></term>
+ <listitem>
+ <para>
+ The optional <literal>PARTITION BY</> clause specifies a list
+ of columns (or expressions) to use as the partition key of table.
+ Currently, <literal>LIST</> and <literal>RANGE</> can be specified
+ as the partitioning method. Such a table (hereafter referred to as
+ partitioned table) is divided into sub-tables called partitions which
+ are created after the fact.
+ </para>
+
+ <para>
+ Scanning a partitioned table results in scanning its partitions
+ and appending the results. Rows inserted into a partitioned table
+ are mapped to and stored in a partition based on values of partition
+ key columns unless an appropriate partition has not been defined yet.
+ </para>
+
+ <para>
+ There are currently a number of limitations on constraints that
+ can be defined on partitioned tables as detailed elsewhere.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><literal>LIKE <replaceable>source_table</replaceable> [ <replaceable>like_option</replaceable> ... ]</literal></term>
<listitem>
<para>
@@ -498,6 +574,10 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
primary key constraint defined for the table. (Otherwise it
would just be the same constraint listed twice.)
</para>
+
+ <para>
+ UNIQUE constraints are not supported on partitioned tables at the moment.
+ </para>
</listitem>
</varlistentry>
@@ -526,6 +606,10 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
different from other sets of columns named by any unique
constraint defined for the same table.
</para>
+
+ <para>
+ Primary key constraints are not supported on partitioned tables at the moment.
+ </para>
</listitem>
</varlistentry>
@@ -576,6 +660,10 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
exclusion constraint on a subset of the table; internally this creates a
partial index. Note that parentheses are required around the predicate.
</para>
+
+ <para>
+ Exclusion constraints are not supported on partitioned tables at the moment.
+ </para>
</listitem>
</varlistentry>
@@ -699,6 +787,10 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
associated with the foreign key constraint can be performed more
efficiently.
</para>
+
+ <para>
+ Partitioned tables cannot use the REFERENCES clause at the moment.
+ </para>
</listitem>
</varlistentry>
@@ -1119,7 +1211,9 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
<para>
Unique constraints and primary keys are not inherited in the
current implementation. This makes the combination of
- inheritance and unique constraints rather dysfunctional.
+ inheritance and unique constraints rather dysfunctional. As
+ noted before, they cannot be used with partitioned tables
+ either.
</para>
<para>
@@ -1341,6 +1435,76 @@ CREATE TABLE employees OF employee_type (
salary WITH OPTIONS DEFAULT 1000
);
</programlisting></para>
+
+ <para>
+ Create a range partitioned table and partitions:
+<programlisting>
+CREATE TABLE employee (
+ name text,
+ age int NOT NULL
+)
+PARTITION BY RANGE ON (age);
+
+CREATE TABLE employees_less_than_30
+ PARTITION OF employee
+ FOR VALUES LESS THAN (30)
+ WITH (fillfactor=70)
+ TABLESPACE diskvol1;
+
+CREATE TABLE employees_between_30_and_40
+ PARTITION OF employee
+ FOR VALUES LESS THAN (40)
+ WITH (fillfactor=70)
+ TABLESPACE diskvol2;
+</programlisting></para>
+
+ <para>
+ Create a list partitioned table and partitions:
+<programlisting>
+CREATE TABLE store (
+ id int,
+ city text NOT NULL
+)
+PARTITION BY LIST ON (city);
+
+CREATE TABLE store_east
+ PARTITION OF store
+ FOR VALUES IN ('New York', 'Boston');
+
+CREATE TABLE store_west
+ PARTITION OF store
+ FOR VALUES ('San Fransisco', 'Seattle');
+</programlisting></para>
+
+ <para>
+ Create a multi-column range partitioned table and partitions:
+<programlisting>
+CREATE TABLE sales (
+ tos timestamp NOT NULL,
+ item text
+)
+PARTITION BY RANGE ON (extract(year from tos), extract(month from tos));
+
+CREATE TABLE sales_before_2015
+ PARTITION OF sales
+ FOR VALUES LESS THAN (2015, 1);
+
+CREATE TABLE sales_2015_q1
+ PARTITION OF sales
+ FOR VALUES LESS THAN (2015, 4);
+
+CREATE TABLE sales_2015_q2
+ PARTITION OF sales
+ FOR VALUES LESS THAN (2015, 7);
+
+CREATE TABLE sales_2015_q3
+ PARTITION OF sales
+ FOR VALUES LESS THAN (2015, 10);
+
+CREATE TABLE sales_2015_q4
+ PARTITION OF sales
+ FOR VALUES LESS THAN (2016, 1);
+</programlisting></para>
</refsect1>
<refsect1 id="SQL-CREATETABLE-compatibility">
@@ -1511,6 +1675,16 @@ CREATE TABLE employees OF employee_type (
effect can be had using the OID feature.
</para>
</refsect2>
+
+ <refsect2>
+ <title>Partitioned Tables</title>
+
+ <para>
+ <literal>PARTITION BY</literal> and <literal>PARTITION OF</literal> (including
+ <literal>FOR VALUES</literal>) are <productname>PostgreSQL</productname>
+ extensions.
+ </para>
+ </refsect2>
</refsect1>
[text/x-diff] decl-partitioning-tests.patch (55.4K, ../../563341AE.5010207@lab.ntt.co.jp/4-decl-partitioning-tests.patch)
download | inline diff:
diff --git a/src/test/regress/expected/partitioned_table.out b/src/test/regress/expected/partitioned_table.out
new file mode 100644
index 0000000..da2e784
--- /dev/null
+++ b/src/test/regress/expected/partitioned_table.out
@@ -0,0 +1,1044 @@
+--
+-- create a range partitioned table
+--
+CREATE TABLE person_partitioned (
+ name text,
+ age int,
+ city text
+) PARTITION BY RANGE ON (age);
+-- select (empty result yet)
+EXPLAIN (costs off) SELECT * FROM person_partitioned;
+ QUERY PLAN
+--------------------------
+ Result
+ One-Time Filter: false
+(2 rows)
+
+SELECT * FROM person_partitioned;
+ name | age | city
+------+-----+------
+(0 rows)
+
+-- cannot insert data into it without creating a partition
+INSERT INTO person_partitioned VALUES ('Bob', 23, 'Boston'); -- fail
+ERROR: cannot find partition for the new row for relation "person_partitioned"
+DETAIL: Failing row contains (Bob, 23, Boston).
+-- create a couple of range partitions
+CREATE TABLE person_partitioned_30
+ PARTITION OF person_partitioned
+ FOR VALUES LESS THAN (30);
+CREATE TABLE person_partitioned_50
+ PARTITION OF person_partitioned
+ FOR VALUES LESS THAN (50);
+INSERT INTO person_partitioned VALUES ('Bob', 23, 'Boston'); -- ok
+INSERT INTO person_partitioned VALUES ('Alice', 32, 'Austin'); -- ok
+INSERT INTO person_partitioned VALUES ('Jon', 52, 'Seattle'); -- fail
+ERROR: cannot find partition for the new row for relation "person_partitioned"
+DETAIL: Failing row contains (Jon, 52, Seattle).
+-- select (non-empty result)
+EXPLAIN (costs off) SELECT * FROM person_partitioned;
+ QUERY PLAN
+-----------------------------------------
+ Append
+ -> Seq Scan on person_partitioned_30
+ -> Seq Scan on person_partitioned_50
+(3 rows)
+
+SELECT * FROM person_partitioned;
+ name | age | city
+-------+-----+--------
+ Bob | 23 | Boston
+ Alice | 32 | Austin
+(2 rows)
+
+-- no partition selected
+EXPLAIN (costs off) SELECT * FROM person_partitioned WHERE age > 50;
+ QUERY PLAN
+--------------------------
+ Result
+ One-Time Filter: false
+(2 rows)
+
+SELECT * FROM person_partitioned WHERE age > 50;
+ name | age | city
+------+-----+------
+(0 rows)
+
+-- one partition selected
+EXPLAIN (costs off) SELECT * FROM person_partitioned WHERE age < 25;
+ QUERY PLAN
+-----------------------------------------
+ Append
+ -> Seq Scan on person_partitioned_30
+ Filter: (age < 25)
+(3 rows)
+
+SELECT * FROM person_partitioned WHERE age < 25;
+ name | age | city
+------+-----+--------
+ Bob | 23 | Boston
+(1 row)
+
+-- cannot update partition directly
+UPDATE person_partitioned_50 SET city = 'Dallas' WHERE name = 'Alice';
+ERROR: cannot update a partition of another table
+HINT: Perform UPDATE on the parent instead.
+-- so update parent
+EXPLAIN (costs off) UPDATE person_partitioned SET city = 'Dallas' WHERE name = 'Alice';
+ QUERY PLAN
+-----------------------------------------------------
+ Update on person_partitioned
+ -> Result
+ -> Append
+ -> Seq Scan on person_partitioned_30
+ Filter: (name = 'Alice'::text)
+ -> Seq Scan on person_partitioned_50
+ Filter: (name = 'Alice'::text)
+(7 rows)
+
+UPDATE person_partitioned SET city = 'Dallas' WHERE name = 'Alice';
+-- following update changes the value of partition key
+UPDATE person_partitioned SET age = 52 WHERE name = 'Alice'; -- fail
+ERROR: cannot perform update that causes a row to change partition
+UPDATE person_partitioned SET age = 42 WHERE name = 'Alice'; -- ok
+--
+-- create a list partitioend table
+--
+CREATE TABLE city_partitioned (
+ name text
+) PARTITION BY LIST ON (name);
+-- select (empty result yet)
+EXPLAIN (costs off) SELECT * FROM city_partitioned;
+ QUERY PLAN
+--------------------------
+ Result
+ One-Time Filter: false
+(2 rows)
+
+SELECT * FROM city_partitioned;
+ name
+------
+(0 rows)
+
+-- cannot insert data into it without creating a partition
+INSERT INTO city_partitioned VALUES ('Boston'); -- fail
+ERROR: cannot find partition for the new row for relation "city_partitioned"
+DETAIL: Failing row contains (Boston).
+-- create a couple of list partitions
+CREATE TABLE city_east
+ PARTITION OF city_partitioned
+ FOR VALUES IN ('New York', 'Boston');
+CREATE TABLE city_west
+ PARTITION OF city_partitioned
+ FOR VALUES IN ('Los Angeles');
+INSERT INTO city_partitioned VALUES ('Boston'); -- ok
+INSERT INTO city_partitioned VALUES ('Austin'); -- fail
+ERROR: cannot find partition for the new row for relation "city_partitioned"
+DETAIL: Failing row contains (Austin).
+-- select (non-empty result)
+EXPLAIN (costs off) SELECT * FROM city_partitioned;
+ QUERY PLAN
+-----------------------------
+ Append
+ -> Seq Scan on city_east
+ -> Seq Scan on city_west
+(3 rows)
+
+SELECT * FROM city_partitioned;
+ name
+--------
+ Boston
+(1 row)
+
+-- no partition selected
+EXPLAIN (costs off) SELECT * FROM city_partitioned WHERE name = 'Austin';
+ QUERY PLAN
+--------------------------
+ Result
+ One-Time Filter: false
+(2 rows)
+
+SELECT * FROM city_partitioned WHERE name = 'Austin';
+ name
+------
+(0 rows)
+
+-- one partition selected
+EXPLAIN (costs off) SELECT * FROM city_partitioned WHERE name = 'Boston';
+ QUERY PLAN
+-----------------------------------------
+ Append
+ -> Seq Scan on city_east
+ Filter: (name = 'Boston'::text)
+(3 rows)
+
+SELECT * FROM city_partitioned WHERE name = 'Boston';
+ name
+--------
+ Boston
+(1 row)
+
+-- can delete from partition directly
+DELETE FROM city_east WHERE name = 'Boston'; -- ok
+-- or from parent
+DELETE FROM city_east WHERE name = 'Boston'; -- 0 deleted
+-- dropping partitioned table requires CASCADE if partitions exist
+DROP TABLE person_partitioned; -- fail
+ERROR: cannot drop partitioned table person_partitioned because other objects depend on it
+DETAIL: partition person_partitioned_30 depends on partitioned table person_partitioned
+partition person_partitioned_50 depends on partitioned table person_partitioned
+HINT: Use DROP ... CASCADE to drop the dependent objects too.
+DROP TABLE person_partitioned CASCADE; -- ok
+NOTICE: drop cascades to 2 other objects
+DETAIL: drop cascades to partition person_partitioned_30
+drop cascades to partition person_partitioned_50
+DROP TABLE city_partitioned CASCADE;
+NOTICE: drop cascades to 2 other objects
+DETAIL: drop cascades to partition city_east
+drop cascades to partition city_west
+--
+-- test some planner bits
+--
+CREATE TABLE rangetab (a int) PARTITION BY RANGE ON (a);
+CREATE TABLE rangetab1 PARTITION OF rangetab FOR VALUES LESS THAN (10);
+CREATE INDEX rangetab1_a_idx ON rangetab1 (a);
+CREATE TABLE rangetab2 PARTITION OF rangetab FOR VALUES LESS THAN (20);
+CREATE INDEX rangetab2_a_idx ON rangetab2 (a);
+CREATE TABLE rangetab3 PARTITION OF rangetab FOR VALUES LESS THAN (30);
+CREATE INDEX rangetab3_a_idx ON rangetab3 (a);
+CREATE TABLE rangetab4 PARTITION OF rangetab FOR VALUES LESS THAN (40);
+CREATE INDEX rangetab4_a_idx ON rangetab3 (a);
+INSERT INTO rangetab SELECT generate_series(1, 39);
+-- scan rangetab2 and rangetab3
+EXPLAIN (costs off) SELECT * FROM rangetab WHERE a BETWEEN 15 AND 25;
+ QUERY PLAN
+-----------------------------------------------------
+ Append
+ -> Bitmap Heap Scan on rangetab2
+ Recheck Cond: ((a >= 15) AND (a <= 25))
+ -> Bitmap Index Scan on rangetab2_a_idx
+ Index Cond: ((a >= 15) AND (a <= 25))
+ -> Bitmap Heap Scan on rangetab3
+ Recheck Cond: ((a >= 15) AND (a <= 25))
+ -> Bitmap Index Scan on rangetab4_a_idx
+ Index Cond: ((a >= 15) AND (a <= 25))
+(9 rows)
+
+-- scan rangetab1 and rangetab2
+EXPLAIN (costs off) SELECT * FROM rangetab WHERE a <= 10;
+ QUERY PLAN
+--------------------------------------------------
+ Append
+ -> Bitmap Heap Scan on rangetab1
+ Recheck Cond: (a <= 10)
+ -> Bitmap Index Scan on rangetab1_a_idx
+ Index Cond: (a <= 10)
+ -> Bitmap Heap Scan on rangetab2
+ Recheck Cond: (a <= 10)
+ -> Bitmap Index Scan on rangetab2_a_idx
+ Index Cond: (a <= 10)
+(9 rows)
+
+-- scan rangetab2, rangetab3, rangetab4
+EXPLAIN (costs off) SELECT * FROM rangetab WHERE a > 10;
+ QUERY PLAN
+--------------------------------------------------
+ Append
+ -> Bitmap Heap Scan on rangetab2
+ Recheck Cond: (a > 10)
+ -> Bitmap Index Scan on rangetab2_a_idx
+ Index Cond: (a > 10)
+ -> Bitmap Heap Scan on rangetab3
+ Recheck Cond: (a > 10)
+ -> Bitmap Index Scan on rangetab4_a_idx
+ Index Cond: (a > 10)
+ -> Seq Scan on rangetab4
+ Filter: (a > 10)
+(11 rows)
+
+-- scan rangetab1
+EXPLAIN (costs off) SELECT * FROM rangetab WHERE a < 10 AND a < 20;
+ QUERY PLAN
+---------------------------------------------------
+ Append
+ -> Bitmap Heap Scan on rangetab1
+ Recheck Cond: ((a < 10) AND (a < 20))
+ -> Bitmap Index Scan on rangetab1_a_idx
+ Index Cond: ((a < 10) AND (a < 20))
+(5 rows)
+
+EXPLAIN (costs off) SELECT * FROM rangetab WHERE a = 1 AND a < 20;
+ QUERY PLAN
+--------------------------------------------------
+ Append
+ -> Bitmap Heap Scan on rangetab1
+ Recheck Cond: ((a < 20) AND (a = 1))
+ -> Bitmap Index Scan on rangetab1_a_idx
+ Index Cond: ((a < 20) AND (a = 1))
+(5 rows)
+
+-- scan rangetab4
+EXPLAIN (costs off) SELECT * FROM rangetab WHERE a > 35 AND a > 25;
+ QUERY PLAN
+-----------------------------------------
+ Append
+ -> Seq Scan on rangetab4
+ Filter: ((a > 35) AND (a > 25))
+(3 rows)
+
+EXPLAIN (costs off) SELECT * FROM rangetab WHERE a = 35 AND a > 20;
+ QUERY PLAN
+-----------------------------------------
+ Append
+ -> Seq Scan on rangetab4
+ Filter: ((a > 20) AND (a = 35))
+(3 rows)
+
+-- scan nothing
+EXPLAIN (costs off) SELECT * FROM rangetab WHERE a = 10 AND a < 5; -- mutually refute
+ QUERY PLAN
+--------------------------
+ Result
+ One-Time Filter: false
+(2 rows)
+
+EXPLAIN (costs off) SELECT * FROM rangetab WHERE a = 10 AND a > 15; -- ditto
+ QUERY PLAN
+--------------------------
+ Result
+ One-Time Filter: false
+(2 rows)
+
+EXPLAIN (costs off) SELECT * FROM rangetab WHERE a > 40; -- no partitions beyond 40
+ QUERY PLAN
+--------------------------
+ Result
+ One-Time Filter: false
+(2 rows)
+
+-- scan everything
+EXPLAIN (costs off) SELECT * FROM rangetab WHERE a > 1;
+ QUERY PLAN
+--------------------------------------------------
+ Append
+ -> Bitmap Heap Scan on rangetab1
+ Recheck Cond: (a > 1)
+ -> Bitmap Index Scan on rangetab1_a_idx
+ Index Cond: (a > 1)
+ -> Bitmap Heap Scan on rangetab2
+ Recheck Cond: (a > 1)
+ -> Bitmap Index Scan on rangetab2_a_idx
+ Index Cond: (a > 1)
+ -> Bitmap Heap Scan on rangetab3
+ Recheck Cond: (a > 1)
+ -> Bitmap Index Scan on rangetab4_a_idx
+ Index Cond: (a > 1)
+ -> Seq Scan on rangetab4
+ Filter: (a > 1)
+(15 rows)
+
+EXPLAIN (costs off) SELECT * FROM rangetab WHERE a = 10 OR a > 1; -- OR clause turns pruning off (for now)
+ QUERY PLAN
+--------------------------------------------------------
+ Append
+ -> Bitmap Heap Scan on rangetab1
+ Recheck Cond: ((a = 10) OR (a > 1))
+ -> BitmapOr
+ -> Bitmap Index Scan on rangetab1_a_idx
+ Index Cond: (a = 10)
+ -> Bitmap Index Scan on rangetab1_a_idx
+ Index Cond: (a > 1)
+ -> Bitmap Heap Scan on rangetab2
+ Recheck Cond: ((a = 10) OR (a > 1))
+ -> BitmapOr
+ -> Bitmap Index Scan on rangetab2_a_idx
+ Index Cond: (a = 10)
+ -> Bitmap Index Scan on rangetab2_a_idx
+ Index Cond: (a > 1)
+ -> Bitmap Heap Scan on rangetab3
+ Recheck Cond: ((a = 10) OR (a > 1))
+ -> BitmapOr
+ -> Bitmap Index Scan on rangetab4_a_idx
+ Index Cond: (a = 10)
+ -> Bitmap Index Scan on rangetab4_a_idx
+ Index Cond: (a > 1)
+ -> Seq Scan on rangetab4
+ Filter: ((a = 10) OR (a > 1))
+(24 rows)
+
+EXPLAIN (costs off) SELECT * FROM rangetab WHERE a IN (1, 11, 21); -- array op turns pruning off (for now)
+ QUERY PLAN
+--------------------------------------------------------------
+ Append
+ -> Bitmap Heap Scan on rangetab1
+ Recheck Cond: (a = ANY ('{1,11,21}'::integer[]))
+ -> Bitmap Index Scan on rangetab1_a_idx
+ Index Cond: (a = ANY ('{1,11,21}'::integer[]))
+ -> Bitmap Heap Scan on rangetab2
+ Recheck Cond: (a = ANY ('{1,11,21}'::integer[]))
+ -> Bitmap Index Scan on rangetab2_a_idx
+ Index Cond: (a = ANY ('{1,11,21}'::integer[]))
+ -> Bitmap Heap Scan on rangetab3
+ Recheck Cond: (a = ANY ('{1,11,21}'::integer[]))
+ -> Bitmap Index Scan on rangetab4_a_idx
+ Index Cond: (a = ANY ('{1,11,21}'::integer[]))
+ -> Seq Scan on rangetab4
+ Filter: (a = ANY ('{1,11,21}'::integer[]))
+(15 rows)
+
+CREATE TABLE plaintab (a int, b text);
+CREATE INDEX plaintab_a_idx ON plaintab (a);
+INSERT INTO plaintab SELECT generate_series(1, 15), 'c';
+EXPLAIN (costs off) SELECT * FROM rangetab WHERE a = (SELECT max(a) FROM plaintab); -- only use Consts to prune
+ QUERY PLAN
+---------------------------------------------------------------------------------
+ Append
+ InitPlan 2 (returns $1)
+ -> Result
+ InitPlan 1 (returns $0)
+ -> Limit
+ -> Index Only Scan Backward using plaintab_a_idx on plaintab
+ Index Cond: (a IS NOT NULL)
+ -> Bitmap Heap Scan on rangetab1
+ Recheck Cond: (a = $1)
+ -> Bitmap Index Scan on rangetab1_a_idx
+ Index Cond: (a = $1)
+ -> Bitmap Heap Scan on rangetab2
+ Recheck Cond: (a = $1)
+ -> Bitmap Index Scan on rangetab2_a_idx
+ Index Cond: (a = $1)
+ -> Bitmap Heap Scan on rangetab3
+ Recheck Cond: (a = $1)
+ -> Bitmap Index Scan on rangetab4_a_idx
+ Index Cond: (a = $1)
+ -> Seq Scan on rangetab4
+ Filter: (a = $1)
+(21 rows)
+
+-- might as well check prepared statement
+PREPARE x AS SELECT * FROM rangetab WHERE a = $1;
+EXPLAIN EXECUTE x(1); -- scan rangetab
+ QUERY PLAN
+-------------------------------------------------------------------------------------
+ Append (cost=4.26..14.95 rows=13 width=4)
+ -> Bitmap Heap Scan on rangetab1 (cost=4.26..14.95 rows=13 width=4)
+ Recheck Cond: (a = 1)
+ -> Bitmap Index Scan on rangetab1_a_idx (cost=0.00..4.25 rows=13 width=0)
+ Index Cond: (a = 1)
+(5 rows)
+
+EXPLAIN EXECUTE x(10); -- scan rangetab2
+ QUERY PLAN
+-------------------------------------------------------------------------------------
+ Append (cost=4.26..14.95 rows=13 width=4)
+ -> Bitmap Heap Scan on rangetab2 (cost=4.26..14.95 rows=13 width=4)
+ Recheck Cond: (a = 10)
+ -> Bitmap Index Scan on rangetab2_a_idx (cost=0.00..4.25 rows=13 width=0)
+ Index Cond: (a = 10)
+(5 rows)
+
+DEALLOCATE x;
+-- check equivalence processing
+EXPLAIN (costs off) SELECT * FROM rangetab t1, plaintab t2 WHERE t1.a = t2.a AND t2.a = 1; -- scan only rangetab1
+ QUERY PLAN
+--------------------------------------------------------
+ Nested Loop
+ -> Append
+ -> Bitmap Heap Scan on rangetab1 t1
+ Recheck Cond: (a = 1)
+ -> Bitmap Index Scan on rangetab1_a_idx
+ Index Cond: (a = 1)
+ -> Materialize
+ -> Bitmap Heap Scan on plaintab t2
+ Recheck Cond: (a = 1)
+ -> Bitmap Index Scan on plaintab_a_idx
+ Index Cond: (a = 1)
+(11 rows)
+
+DROP TABLE rangetab CASCADE;
+NOTICE: drop cascades to 4 other objects
+DETAIL: drop cascades to partition rangetab1
+drop cascades to partition rangetab2
+drop cascades to partition rangetab3
+drop cascades to partition rangetab4
+-- why not check for list partitioned case, too
+CREATE TABLE listtab (a text) PARTITION BY LIST ON (a);
+CREATE TABLE listtab1 PARTITION OF listtab FOR VALUES IN ('a', 'b');
+CREATE INDEX listtab1_a_idx ON listtab1 (a);
+CREATE TABLE listtab2 PARTITION OF listtab FOR VALUES IN ('c');
+CREATE INDEX listtab2_a_idx ON listtab2 (a);
+-- one might think only scans parttab1 - scans everything
+EXPLAIN (costs off) SELECT * FROM listtab WHERE a IN ('a', 'b'); -- array op turns off pruning (for now)
+ QUERY PLAN
+-------------------------------------------------------
+ Append
+ -> Bitmap Heap Scan on listtab1
+ Recheck Cond: (a = ANY ('{a,b}'::text[]))
+ -> Bitmap Index Scan on listtab1_a_idx
+ Index Cond: (a = ANY ('{a,b}'::text[]))
+ -> Bitmap Heap Scan on listtab2
+ Recheck Cond: (a = ANY ('{a,b}'::text[]))
+ -> Bitmap Index Scan on listtab2_a_idx
+ Index Cond: (a = ANY ('{a,b}'::text[]))
+(9 rows)
+
+EXPLAIN (costs off) SELECT * FROM listtab WHERE a = 'a' OR a = 'b'; -- OR turns off pruning (for now)
+ QUERY PLAN
+------------------------------------------------------------
+ Append
+ -> Bitmap Heap Scan on listtab1
+ Recheck Cond: ((a = 'a'::text) OR (a = 'b'::text))
+ -> BitmapOr
+ -> Bitmap Index Scan on listtab1_a_idx
+ Index Cond: (a = 'a'::text)
+ -> Bitmap Index Scan on listtab1_a_idx
+ Index Cond: (a = 'b'::text)
+ -> Bitmap Heap Scan on listtab2
+ Recheck Cond: ((a = 'a'::text) OR (a = 'b'::text))
+ -> BitmapOr
+ -> Bitmap Index Scan on listtab2_a_idx
+ Index Cond: (a = 'a'::text)
+ -> Bitmap Index Scan on listtab2_a_idx
+ Index Cond: (a = 'b'::text)
+(15 rows)
+
+-- scan listtab1
+EXPLAIN (costs off) SELECT * FROM listtab WHERE a = 'b';
+ QUERY PLAN
+-------------------------------------------------
+ Append
+ -> Bitmap Heap Scan on listtab1
+ Recheck Cond: (a = 'b'::text)
+ -> Bitmap Index Scan on listtab1_a_idx
+ Index Cond: (a = 'b'::text)
+(5 rows)
+
+-- check equivalence processing
+EXPLAIN (costs off) SELECT * FROM listtab t1, plaintab t2 WHERE t1.a = t2.b AND t2.b = 'c'; -- scan only listtab2
+ QUERY PLAN
+-------------------------------------------------------
+ Nested Loop
+ -> Append
+ -> Bitmap Heap Scan on listtab2 t1
+ Recheck Cond: (a = 'c'::text)
+ -> Bitmap Index Scan on listtab2_a_idx
+ Index Cond: (a = 'c'::text)
+ -> Materialize
+ -> Seq Scan on plaintab t2
+ Filter: (b = 'c'::text)
+(9 rows)
+
+DROP TABLE plaintab;
+DROP TABLE listtab CASCADE;
+NOTICE: drop cascades to 2 other objects
+DETAIL: drop cascades to partition listtab1
+drop cascades to partition listtab2
+--
+-- ddl related tests
+--
+-- cannot use system columns in partition key
+CREATE TABLE fail_oid_pk (
+ a int
+) PARTITION BY RANGE ON (oid) WITH OIDS; -- fail
+ERROR: cannot use system column "oid" in partition key
+-- cannot use more than 1 column as partition key for list partitioned table
+CREATE TABLE two_col_tab (
+ a1 int,
+ a2 int
+) PARTITION BY LIST ON (a1, a2); -- fail
+ERROR: cannot use more than one column in partition key
+DETAIL: Only one column allowed with list partitioning.
+-- cannot use more than 16 columns as partition key for range partitioned table
+CREATE TABLE wide_tab (
+ a1 int,
+ a2 int,
+ a3 int,
+ a4 int,
+ a5 int,
+ a6 int,
+ a7 int,
+ a8 int,
+ a9 int,
+ a10 int,
+ a11 int,
+ a12 int,
+ a13 int,
+ a14 int,
+ a15 int,
+ a16 int
+) PARTITION BY RANGE ON (a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16); -- ok
+DROP TABLE wide_tab CASCADE;
+CREATE TABLE extra_wide_tab (
+ a1 int,
+ a2 int,
+ a3 int,
+ a4 int,
+ a5 int,
+ a6 int,
+ a7 int,
+ a8 int,
+ a9 int,
+ a10 int,
+ a11 int,
+ a12 int,
+ a13 int,
+ a14 int,
+ a15 int,
+ a16 int,
+ a17 int
+) PARTITION BY RANGE ON (a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17); -- fail
+ERROR: cannot use more than 16 columns in partition key
+-- At the moment, partitioning requires to use columns with data type having
+-- a suitable btree operator class
+CREATE TABLE point_tab (a point) PARTITION BY LIST ON (a); -- fail
+ERROR: data type point has no default btree operator class
+HINT: You must specify an existing btree operator class or define one for the data type.
+CREATE TABLE point_tab (a point) PARTITION BY LIST ON (a USING point_ops); -- fail
+ERROR: operator class "point_ops" does not exist for access method "btree"
+CREATE TABLE point_tab (a point) PARTITION BY RANGE ON (a); -- fail
+ERROR: data type point has no default btree operator class
+HINT: You must specify an existing btree operator class or define one for the data type.
+CREATE TABLE point_tab (a point) PARTITION BY RANGE ON (a USING point_ops); -- fail
+ERROR: operator class "point_ops" does not exist for access method "btree"
+-- fail to create a partitioned table with PRIMARY KEY constraint
+CREATE TABLE pk_fail (a int PRIMARY KEY) PARTITION BY RANGE ON (a);
+ERROR: primary key constraints are not supported on partitioned tables
+LINE 1: CREATE TABLE pk_fail (a int PRIMARY KEY) PARTITION BY RANGE ...
+ ^
+-- fail to create a partitioned table with foreign key constraint
+CREATE TABLE pk_rel(a int PRIMARY KEY);
+CREATE TABLE pk_fail (a int REFERENCES pk_rel(a)) PARTITION BY RANGE ON (a);
+ERROR: foreign key constraints are not supported on partitioned tables
+LINE 1: CREATE TABLE pk_fail (a int REFERENCES pk_rel(a)) PARTITION ...
+ ^
+DROP TABLE pk_rel;
+-- fail to let a partitioned table be referenced in a foreign key
+CREATE TABLE foo (a int) PARTITION BY RANGE ON (a);
+CREATE TABLE bar_ref_foo (a int REFERENCES foo(a));
+ERROR: cannot reference relation "foo"
+DETAIL: Referencing partitioned tables in foreign key constraints is not supported.
+DROP TABLE foo CASCADE;
+-- fail to create a partitioned table with prohibited expressions in key
+CREATE TABLE fail_const_key (a int) PARTITION BY RANGE ON (('a'));
+ERROR: cannot use a constant expression as partition key
+CREATE TABLE fail_agg_in_key (a int) PARTITION BY RANGE ON ((avg(a)));
+ERROR: aggregate functions are not allowed in partition key expressions
+CREATE TABLE fail_window_fun_in_key (a int, b int) PARTITION BY RANGE ON ((avg(a) OVER (PARTITION BY b)));
+ERROR: window functions are not allowed in partition key expressions
+--
+-- creating partitions with sane FOR VALUES specification
+--
+CREATE TABLE range_partitioned (
+ a int
+) PARTITION BY RANGE ON (a);
+CREATE TABLE fail_partition
+ PARTITION OF range_partitioned
+ FOR VALUES IN (1, 2); -- fail
+ERROR: invalid FOR VALUES specification for a range partition
+DROP TABLE range_partitioned CASCADE;
+CREATE TABLE list_partitioned (
+ a int
+) PARTITION BY LIST ON (a);
+CREATE TABLE fail_partition
+ PARTITION OF list_partitioned
+ FOR VALUES LESS THAN (100); -- fail
+ERROR: invalid FOR VALUES specification for a list partition
+DROP TABLE list_partitioned CASCADE;
+--
+-- creating range partitions sanely
+--
+CREATE TABLE sales (
+ tos timestamp NOT NULL,
+ item text
+) PARTITION BY RANGE ON (extract(year from tos), extract(month from tos));
+CREATE TABLE sales_before_2015
+ PARTITION OF sales
+ FOR VALUES LESS THAN (2015, 1); -- ok
+CREATE TABLE sales_2015_q1
+ PARTITION OF sales
+ FOR VALUES LESS THAN (2015, 4); -- ok
+-- effective partition range empty or overlapping
+CREATE TABLE sales_2015_q2
+ PARTITION OF sales
+ FOR VALUES LESS THAN (2015, 4); -- fail
+ERROR: cannot create range partition with empty range
+HINT: Please specify END value that is greater than max bound of the last partition
+CREATE TABLE sales_2015_q2
+ PARTITION OF sales
+ FOR VALUES LESS THAN (2015, 3); -- fail
+ERROR: cannot create range partition with range overlapping existing partitions
+HINT: Please specify END value that is greater than max bound of the last partition
+-- more bound values than key columns
+CREATE TABLE sales_2015_q2
+ PARTITION OF sales
+ FOR VALUES LESS THAN (2015, 3, 1); -- fail
+ERROR: range partition FOR VALUES specification does not match the partition key
+DROP TABLE sales CASCADE;
+NOTICE: drop cascades to 2 other objects
+DETAIL: drop cascades to partition sales_before_2015
+drop cascades to partition sales_2015_q1
+--
+-- creating list partitions sanely
+--
+CREATE TABLE store (
+ id int,
+ city text NOT NULL
+) PARTITION BY LIST ON (city);
+CREATE TABLE store_east
+ PARTITION OF store
+ FOR VALUES IN ('New York', 'Boston'); -- ok
+CREATE TABLE store_west
+ PARTITION OF store
+ FOR VALUES ('San Fransisco', 'Seattle'); -- ok
+-- overlap with some existing partition
+CREATE TABLE store_ca
+ PARTITION OF store
+ FOR VALUES ('Los Angeles', 'San Fransisco'); -- fail
+ERROR: cannot create list partition that overlaps with an existing partition
+DETAIL: New partition's definition overlaps with partition "store_west".
+DROP TABLE store CASCADE;
+NOTICE: drop cascades to 2 other objects
+DETAIL: drop cascades to partition store_east
+drop cascades to partition store_west
+--
+-- relpersistence of partitioned table and partitions
+--
+CREATE TEMP TABLE foo (
+ a int
+) PARTITION BY RANGE ON (a);
+CREATE TEMP TABLE foo_1
+ PARTITION OF foo
+ FOR VALUES LESS THAN (100); -- ok
+-- cannot be non-temp partition of temp table
+CREATE TABLE foo_2
+ PARTITION OF foo
+ FOR VALUES LESS THAN (100); -- fail
+ERROR: cannot create a non-temporary partition of temporary table "foo"
+DROP TABLE foo CASCADE;
+NOTICE: drop cascades to partition foo_1
+CREATE UNLOGGED TABLE foo (
+ a int
+) PARTITION BY RANGE ON (a);
+CREATE UNLOGGED TABLE foo_1
+ PARTITION OF foo
+ FOR VALUES LESS THAN (100);
+-- cannot be logged/permanent partition of unlogged table
+CREATE TABLE foo_2
+ PARTITION OF foo
+ FOR VALUES LESS THAN (100); -- fail
+ERROR: cannot create a permanent partition of unlogged table "foo"
+DROP TABLE foo CASCADE;
+NOTICE: drop cascades to partition foo_1
+CREATE TABLE foo (
+ a int
+) PARTITION BY RANGE ON (a);
+-- cannot be temp partition of non-temp table
+CREATE TEMP TABLE foo_2
+ PARTITION OF foo
+ FOR VALUES LESS THAN (200); -- fail
+ERROR: cannot create a temporary partition of non-temporary table
+-- cannot be unlogged partition of permanent table
+CREATE UNLOGGED TABLE foo_1
+ PARTITION OF foo
+ FOR VALUES LESS THAN (100); -- fail
+ERROR: cannot create a unlogged partition of permanent table "foo"
+DROP TABLE foo CASCADE;
+--
+-- ALTER TABLE tests
+--
+CREATE TABLE foo (
+ a int
+) PARTITION BY RANGE ON (a);
+CREATE TABLE foo_1
+ PARTITION OF foo
+ FOR VALUES LESS THAN (10);
+-- cannot add column to a partition
+ALTER TABLE foo_1 ADD COLUMN b char; -- fail
+ERROR: "foo_1" is not a table, composite type, or foreign table
+ALTER TABLE foo ADD COLUMN b char; -- ok
+SELECT * FROM foo;
+ a | b
+---+---
+(0 rows)
+
+-- cannot drop a column from a partition
+ALTER TABLE foo_1 DROP COLUMN b; -- fail
+ERROR: "foo_1" is not a table, composite type, or foreign table
+ALTER TABLE foo DROP COLUMN b; -- ok
+SELECT * FROM foo;
+ a
+---
+(0 rows)
+
+-- cannot drop a partition key column
+ALTER TABLE foo DROP COLUMN a;
+ERROR: cannot drop partition key column "a"
+-- cannot alter NOT NULL constraint of column of a partition
+ALTER TABLE foo_1 ALTER a SET NOT NULL; -- fail
+ERROR: "foo_1" is not a table or foreign table
+ALTER TABLE foo ALTER a SET NOT NULL; -- ok
+\d foo_1;
+ Table "public.foo_1"
+ Column | Type | Modifiers
+--------+---------+-----------
+ a | integer | not null
+
+ALTER TABLE foo_1 ALTER a DROP NOT NULL; -- fail
+ERROR: "foo_1" is not a table or foreign table
+ALTER TABLE foo ALTER a DROP NOT NULL; -- ok
+\d foo_1;
+ Table "public.foo_1"
+ Column | Type | Modifiers
+--------+---------+-----------
+ a | integer |
+
+-- cannot alter column default of a partition
+ALTER TABLE foo_1 ALTER COLUMN a SET DEFAULT 0; -- fail
+ERROR: "foo_1" is not a table, view, or foreign table
+ALTER TABLE foo ALTER COLUMN a SET DEFAULT 0; -- ok
+INSERT INTO foo DEFAULT VALUES;
+SELECT * FROM foo;
+ a
+---
+ 0
+(1 row)
+
+ALTER TABLE foo_1 ALTER COLUMN a DROP DEFAULT; -- fail
+ERROR: "foo_1" is not a table, view, or foreign table
+ALTER TABLE foo ALTER COLUMN a DROP DEFAULT; -- ok
+INSERT INTO foo DEFAULT VALUES; -- fail (null default)
+ERROR: null value for partition key column "a" not allowed
+-- cannot alter column type
+ALTER TABLE foo ADD COLUMN b text;
+INSERT INTO foo VALUES (9, 'ab');
+ALTER TABLE foo_1 ALTER b TYPE char; -- fail
+ERROR: "foo_1" is not a table, composite type, or foreign table
+ALTER TABLE foo ALTER b TYPE char; -- fail (found values too long)
+ERROR: value too long for type character(1)
+UPDATE foo SET b = 'a' WHERE length(b) > 1;
+ALTER TABLE foo ALTER b TYPE char; -- ok
+SELECT * FROM foo;
+ a | b
+---+---
+ 0 |
+ 9 | a
+(2 rows)
+
+-- cannot alter type of a partition key column
+ALTER TABLE foo ALTER a TYPE char;
+ERROR: cannot alter type of partition key column "a"
+-- cannot add/drop a table constraint on a partition
+ALTER TABLE foo_1 ADD CONSTRAINT foo_chk_a CHECK (a >= 0); -- fail
+ERROR: "foo_1" is not a table or foreign table
+ALTER TABLE foo ADD CONSTRAINT foo_chk_a CHECK (a >= 0); -- ok
+INSERT INTO foo VALUES (-1); -- fails
+ERROR: new row for relation "foo" violates check constraint "foo_chk_a"
+DETAIL: Failing row contains (-1, null).
+INSERT INTO foo VALUES (0);
+UPDATE foo SET a = -1 WHERE = a = 0 -- fails
+-- cannot change persistence of a partition
+ALTER TABLE foo_1 SET UNLOGGED; -- fail
+ERROR: syntax error at or near "="
+LINE 1: UPDATE foo SET a = -1 WHERE = a = 0
+ ^
+ALTER TABLE foo SET UNLOGGED; -- ok
+\d foo_1
+ Unlogged table "public.foo_1"
+ Column | Type | Modifiers
+--------+--------------+-----------
+ a | integer |
+ b | character(1) |
+
+--
+-- OTOH, there are some AT commands that are OK to do on partitions
+--
+-- can validate a constraint
+ALTER TABLE foo DROP CONSTRAINT foo_chk_a;
+INSERT INTO foo VALUES (-1);
+ALTER TABLE foo ADD CONSTRAINT foo_chk_a CHECK (a >= 0) NOT VALID;
+ALTER TABLE foo_1 VALIDATE CONSTRAINT foo_chk_a; -- fail
+ERROR: check constraint "foo_chk_a" is violated by some row
+DELETE FROM foo WHERE a = -1;
+ALTER TABLE foo_1 VALIDATE CONSTRAINT foo_chk_a; -- ok
+-- or validate it on the whole partitioned table
+ALTER TABLE foo DROP CONSTRAINT foo_chk_a;
+INSERT INTO foo VALUES (-1); -- ok
+ALTER TABLE foo ADD CONSTRAINT foo_chk_a CHECK (a >= 0) NOT VALID;
+ALTER TABLE foo VALIDATE CONSTRAINT foo_chk_a; -- fail
+ERROR: check constraint "foo_chk_a" is violated by some row
+DELETE FROM foo WHERE a = -1;
+ALTER TABLE foo VALIDATE CONSTRAINT foo_chk_a; -- ok
+-- can set/reset attribute options
+ALTER TABLE foo_1 ALTER a SET (n_distinct=10); -- ok
+-- can set attribute storage
+ALTER TABLE foo ADD COLUMN b text;
+ERROR: column "b" of relation "foo" already exists
+\d foo_1
+ Unlogged table "public.foo_1"
+ Column | Type | Modifiers
+--------+--------------+-----------
+ a | integer |
+ b | character(1) |
+
+ALTER TABLE foo_1 ALTER b SET STORAGE EXTERNAL;
+\d foo_1
+ Unlogged table "public.foo_1"
+ Column | Type | Modifiers
+--------+--------------+-----------
+ a | integer |
+ b | character(1) |
+
+-- can create index-creating constraint
+DELETE FROM foo;
+ALTER TABLE foo_1 ADD UNIQUE (a);
+\d foo_1
+ Unlogged table "public.foo_1"
+ Column | Type | Modifiers
+--------+--------------+-----------
+ a | integer |
+ b | character(1) |
+Indexes:
+ "foo_1_a_key" UNIQUE CONSTRAINT, btree (a)
+
+-- can create a constraint using index
+CREATE INDEX foo_1_b_idx ON foo_1 (b);
+ALTER TABLE foo_1 ADD UNIQUE USING foo_1_b_idx;
+ERROR: syntax error at or near "foo_1_b_idx"
+LINE 1: ALTER TABLE foo_1 ADD UNIQUE USING foo_1_b_idx;
+ ^
+\d foo_1
+ Unlogged table "public.foo_1"
+ Column | Type | Modifiers
+--------+--------------+-----------
+ a | integer |
+ b | character(1) |
+Indexes:
+ "foo_1_a_key" UNIQUE CONSTRAINT, btree (a)
+ "foo_1_b_idx" btree (b)
+
+-- can drop index constraints (others cannot be created at all)
+ALTER TABLE foo_1 DROP CONSTRAINT foo_1_a_key;
+-- can set/reset CLUSTER ON
+ALTER TABLE foo_1 CLUSTER ON foo_1_a_idx;
+ERROR: index "foo_1_a_idx" for table "foo_1" does not exist
+\d foo_1
+ Unlogged table "public.foo_1"
+ Column | Type | Modifiers
+--------+--------------+-----------
+ a | integer |
+ b | character(1) |
+Indexes:
+ "foo_1_b_idx" btree (b)
+
+ALTER TABLE foo_1 SET WITHOUT CLUSTER;
+\d foo_1
+ Unlogged table "public.foo_1"
+ Column | Type | Modifiers
+--------+--------------+-----------
+ a | integer |
+ b | character(1) |
+Indexes:
+ "foo_1_b_idx" btree (b)
+
+-- can set/reset table storage options
+ALTER TABLE foo_1 SET (fillfactor=80)
+\d+ foo_1
+ Unlogged table "public.foo_1"
+ Column | Type | Modifiers | Storage | Stats target | Description
+--------+--------------+-----------+----------+--------------+-------------
+ a | integer | | plain | |
+ b | character(1) | | external | |
+Indexes:
+ "foo_1_b_idx" btree (b)
+
+ALTER TABLE foo_1 RESET (fillfactor)
+\d+ foo_1
+ Unlogged table "public.foo_1"
+ Column | Type | Modifiers | Storage | Stats target | Description
+--------+--------------+-----------+----------+--------------+-------------
+ a | integer | | plain | |
+ b | character(1) | | external | |
+Indexes:
+ "foo_1_b_idx" btree (b)
+
+-- can set replica identity
+ALTER TABLE foo ALTER a SET NOT NULL;
+ERROR: syntax error at or near "ALTER"
+LINE 2: ALTER TABLE foo_1 RESET (fillfactor)
+ ^
+ALTER TABLE foo_1 REPLICA IDENTITY USING INDEX foo_1_a_idx;
+ERROR: index "foo_1_a_idx" for table "foo_1" does not exist
+\d foo_1
+ Unlogged table "public.foo_1"
+ Column | Type | Modifiers
+--------+--------------+-----------
+ a | integer |
+ b | character(1) |
+Indexes:
+ "foo_1_b_idx" btree (b)
+
+ALTER TABLE foo_1 REPLICA IDENTITY NOTHING;
+\d+ foo_1
+ Unlogged table "public.foo_1"
+ Column | Type | Modifiers | Storage | Stats target | Description
+--------+--------------+-----------+----------+--------------+-------------
+ a | integer | | plain | |
+ b | character(1) | | external | |
+Indexes:
+ "foo_1_b_idx" btree (b)
+Replica Identity: NOTHING
+
+-- can set statistics on a column
+ALTER TABLE foo_1 ALTER a SET STATISTICS 1;
+\d+ foo_1
+ Unlogged table "public.foo_1"
+ Column | Type | Modifiers | Storage | Stats target | Description
+--------+--------------+-----------+----------+--------------+-------------
+ a | integer | | plain | 1 |
+ b | character(1) | | external | |
+Indexes:
+ "foo_1_b_idx" btree (b)
+Replica Identity: NOTHING
+
+--
+-- some more tests
+--
+-- cannot drop paritions
+DROP TABLE foo_1; -- fail
+ERROR: cannot drop table "foo_1"
+DETAIL: "foo_1" is a partition of table "foo".
+HINT: You will need to detach the partition first.
+-- detach partition
+ALTER TABLE foo DETACH PARTITION foo_1 USING foo_1_table;
+-- now drop 'table' foo_1_table
+DROP TABLE foo_1_table; -- ok
+-- feature not implemented message
+ALTER TABLE foo ATTACH PARTITION foo_1 FOR VALUES LESS THAN (10) USING TABLE foo_1_table;
+ERROR: ALTER TABLE ATTACH PARTITION (...) USING TABLE not implemented
+DROP TABLE foo CASCADE;
+--
+-- some commands are not ready for either partitioned tables or partitions
+--
+CREATE TABLE command_fail_tab (a int) PARTITION BY RANGE ON (a);
+CREATE TABLE command_fail_partition PARTITION OF command_fail_tab FOR VALUES LESS THAN (10);
+VACUUM command_fail_tab; -- fail
+ERROR: cannot vacuum partitioned table "command_fail_tab"
+HINT: Run VACUUM on individual partitions one-by-one.
+CLUSTER command_fail_tab; -- fail
+ERROR: cannot cluster partitioned table "command_fail_tab"
+HINT: Run CLUSTER on individual partitions one-by-one.
+REINDEX TABLE command_fail_tab; -- fail
+ERROR: cannot reindex partitioned table "command_fail_tab"
+HINT: Run REINDEX on individual partitions one-by-one.
+ANALYZE command_fail_tab; -- fail
+ERROR: cannot analyze partitioned table "command_fail_tab"
+DETAIL: ANALYZE on partitioned tables is not implemented yet.
+HINT: ANALYZE individual partitions separately.
+COPY command_fail_tab TO stdout; -- fail;
+ERROR: cannot copy from partitioned table "command_fail_tab"
+HINT: Try the COPY (SELECT ...) TO variant.
+COPY command_fail_partition FROM stdin; -- fail
+ERROR: cannot copy to a partition of another table
+HINT: Perform COPY on the parent instead.
+DROP TABLE command_fail_tab CASCADE;
+NOTICE: drop cascades to partition command_fail_partition
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index c63abf4..38d78b8 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -60,7 +60,7 @@ test: create_index create_view
# ----------
# Another group of parallel tests
# ----------
-test: create_aggregate create_function_3 create_cast constraints triggers inherit create_table_like typed_table vacuum drop_if_exists updatable_views rolenames roleattributes
+test: create_aggregate create_function_3 create_cast constraints triggers inherit create_table_like typed_table vacuum drop_if_exists updatable_views rolenames roleattributes partitioned_table
# ----------
# sanity_check does a vacuum, affecting the sort order of SELECT *
diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule
index 88dcd64..b98865e 100644
--- a/src/test/regress/serial_schedule
+++ b/src/test/regress/serial_schedule
@@ -159,3 +159,4 @@ test: with
test: xml
test: event_trigger
test: stats
+test: partitioned_table
diff --git a/src/test/regress/sql/partitioned_table.sql b/src/test/regress/sql/partitioned_table.sql
new file mode 100644
index 0000000..e58e427
--- /dev/null
+++ b/src/test/regress/sql/partitioned_table.sql
@@ -0,0 +1,551 @@
+--
+-- create a range partitioned table
+--
+CREATE TABLE person_partitioned (
+ name text,
+ age int,
+ city text
+) PARTITION BY RANGE ON (age);
+
+-- select (empty result yet)
+EXPLAIN (costs off) SELECT * FROM person_partitioned;
+SELECT * FROM person_partitioned;
+
+-- cannot insert data into it without creating a partition
+INSERT INTO person_partitioned VALUES ('Bob', 23, 'Boston'); -- fail
+
+-- create a couple of range partitions
+CREATE TABLE person_partitioned_30
+ PARTITION OF person_partitioned
+ FOR VALUES LESS THAN (30);
+CREATE TABLE person_partitioned_50
+ PARTITION OF person_partitioned
+ FOR VALUES LESS THAN (50);
+
+INSERT INTO person_partitioned VALUES ('Bob', 23, 'Boston'); -- ok
+INSERT INTO person_partitioned VALUES ('Alice', 32, 'Austin'); -- ok
+INSERT INTO person_partitioned VALUES ('Jon', 52, 'Seattle'); -- fail
+
+-- select (non-empty result)
+EXPLAIN (costs off) SELECT * FROM person_partitioned;
+SELECT * FROM person_partitioned;
+
+-- no partition selected
+EXPLAIN (costs off) SELECT * FROM person_partitioned WHERE age > 50;
+SELECT * FROM person_partitioned WHERE age > 50;
+
+-- one partition selected
+EXPLAIN (costs off) SELECT * FROM person_partitioned WHERE age < 25;
+SELECT * FROM person_partitioned WHERE age < 25;
+
+-- cannot update partition directly
+UPDATE person_partitioned_50 SET city = 'Dallas' WHERE name = 'Alice';
+
+-- so update parent
+EXPLAIN (costs off) UPDATE person_partitioned SET city = 'Dallas' WHERE name = 'Alice';
+UPDATE person_partitioned SET city = 'Dallas' WHERE name = 'Alice';
+
+-- following update changes the value of partition key
+UPDATE person_partitioned SET age = 52 WHERE name = 'Alice'; -- fail
+UPDATE person_partitioned SET age = 42 WHERE name = 'Alice'; -- ok
+
+--
+-- create a list partitioend table
+--
+CREATE TABLE city_partitioned (
+ name text
+) PARTITION BY LIST ON (name);
+
+-- select (empty result yet)
+EXPLAIN (costs off) SELECT * FROM city_partitioned;
+SELECT * FROM city_partitioned;
+
+-- cannot insert data into it without creating a partition
+INSERT INTO city_partitioned VALUES ('Boston'); -- fail
+
+-- create a couple of list partitions
+CREATE TABLE city_east
+ PARTITION OF city_partitioned
+ FOR VALUES IN ('New York', 'Boston');
+CREATE TABLE city_west
+ PARTITION OF city_partitioned
+ FOR VALUES IN ('Los Angeles');
+
+INSERT INTO city_partitioned VALUES ('Boston'); -- ok
+INSERT INTO city_partitioned VALUES ('Austin'); -- fail
+
+-- select (non-empty result)
+EXPLAIN (costs off) SELECT * FROM city_partitioned;
+SELECT * FROM city_partitioned;
+
+-- no partition selected
+EXPLAIN (costs off) SELECT * FROM city_partitioned WHERE name = 'Austin';
+SELECT * FROM city_partitioned WHERE name = 'Austin';
+
+-- one partition selected
+EXPLAIN (costs off) SELECT * FROM city_partitioned WHERE name = 'Boston';
+SELECT * FROM city_partitioned WHERE name = 'Boston';
+
+-- can delete from partition directly
+DELETE FROM city_east WHERE name = 'Boston'; -- ok
+
+-- or from parent
+DELETE FROM city_east WHERE name = 'Boston'; -- 0 deleted
+
+-- dropping partitioned table requires CASCADE if partitions exist
+DROP TABLE person_partitioned; -- fail
+DROP TABLE person_partitioned CASCADE; -- ok
+DROP TABLE city_partitioned CASCADE;
+
+
+--
+-- test some planner bits
+--
+CREATE TABLE rangetab (a int) PARTITION BY RANGE ON (a);
+CREATE TABLE rangetab1 PARTITION OF rangetab FOR VALUES LESS THAN (10);
+CREATE INDEX rangetab1_a_idx ON rangetab1 (a);
+CREATE TABLE rangetab2 PARTITION OF rangetab FOR VALUES LESS THAN (20);
+CREATE INDEX rangetab2_a_idx ON rangetab2 (a);
+CREATE TABLE rangetab3 PARTITION OF rangetab FOR VALUES LESS THAN (30);
+CREATE INDEX rangetab3_a_idx ON rangetab3 (a);
+CREATE TABLE rangetab4 PARTITION OF rangetab FOR VALUES LESS THAN (40);
+CREATE INDEX rangetab4_a_idx ON rangetab3 (a);
+INSERT INTO rangetab SELECT generate_series(1, 39);
+
+-- scan rangetab2 and rangetab3
+EXPLAIN (costs off) SELECT * FROM rangetab WHERE a BETWEEN 15 AND 25;
+
+-- scan rangetab1 and rangetab2
+EXPLAIN (costs off) SELECT * FROM rangetab WHERE a <= 10;
+
+-- scan rangetab2, rangetab3, rangetab4
+EXPLAIN (costs off) SELECT * FROM rangetab WHERE a > 10;
+
+-- scan rangetab1
+EXPLAIN (costs off) SELECT * FROM rangetab WHERE a < 10 AND a < 20;
+EXPLAIN (costs off) SELECT * FROM rangetab WHERE a = 1 AND a < 20;
+
+-- scan rangetab4
+EXPLAIN (costs off) SELECT * FROM rangetab WHERE a > 35 AND a > 25;
+EXPLAIN (costs off) SELECT * FROM rangetab WHERE a = 35 AND a > 20;
+
+-- scan nothing
+EXPLAIN (costs off) SELECT * FROM rangetab WHERE a = 10 AND a < 5; -- mutually refute
+EXPLAIN (costs off) SELECT * FROM rangetab WHERE a = 10 AND a > 15; -- ditto
+EXPLAIN (costs off) SELECT * FROM rangetab WHERE a > 40; -- no partitions beyond 40
+
+-- scan everything
+EXPLAIN (costs off) SELECT * FROM rangetab WHERE a > 1;
+EXPLAIN (costs off) SELECT * FROM rangetab WHERE a = 10 OR a > 1; -- OR clause turns pruning off (for now)
+EXPLAIN (costs off) SELECT * FROM rangetab WHERE a IN (1, 11, 21); -- array op turns pruning off (for now)
+
+CREATE TABLE plaintab (a int, b text);
+CREATE INDEX plaintab_a_idx ON plaintab (a);
+INSERT INTO plaintab SELECT generate_series(1, 15), 'c';
+
+EXPLAIN (costs off) SELECT * FROM rangetab WHERE a = (SELECT max(a) FROM plaintab); -- only use Consts to prune
+
+-- might as well check prepared statement
+PREPARE x AS SELECT * FROM rangetab WHERE a = $1;
+EXPLAIN EXECUTE x(1); -- scan rangetab
+EXPLAIN EXECUTE x(10); -- scan rangetab2
+DEALLOCATE x;
+
+-- check equivalence processing
+EXPLAIN (costs off) SELECT * FROM rangetab t1, plaintab t2 WHERE t1.a = t2.a AND t2.a = 1; -- scan only rangetab1
+
+DROP TABLE rangetab CASCADE;
+
+-- why not check for list partitioned case, too
+CREATE TABLE listtab (a text) PARTITION BY LIST ON (a);
+CREATE TABLE listtab1 PARTITION OF listtab FOR VALUES IN ('a', 'b');
+CREATE INDEX listtab1_a_idx ON listtab1 (a);
+CREATE TABLE listtab2 PARTITION OF listtab FOR VALUES IN ('c');
+CREATE INDEX listtab2_a_idx ON listtab2 (a);
+
+-- one might think only scans parttab1 - scans everything
+EXPLAIN (costs off) SELECT * FROM listtab WHERE a IN ('a', 'b'); -- array op turns off pruning (for now)
+EXPLAIN (costs off) SELECT * FROM listtab WHERE a = 'a' OR a = 'b'; -- OR turns off pruning (for now)
+
+-- scan listtab1
+EXPLAIN (costs off) SELECT * FROM listtab WHERE a = 'b';
+
+-- check equivalence processing
+EXPLAIN (costs off) SELECT * FROM listtab t1, plaintab t2 WHERE t1.a = t2.b AND t2.b = 'c'; -- scan only listtab2
+
+DROP TABLE plaintab;
+DROP TABLE listtab CASCADE;
+
+--
+-- ddl related tests
+--
+
+-- cannot use system columns in partition key
+CREATE TABLE fail_oid_pk (
+ a int
+) PARTITION BY RANGE ON (oid) WITH OIDS; -- fail
+
+-- cannot use more than 1 column as partition key for list partitioned table
+CREATE TABLE two_col_tab (
+ a1 int,
+ a2 int
+) PARTITION BY LIST ON (a1, a2); -- fail
+
+-- cannot use more than 16 columns as partition key for range partitioned table
+CREATE TABLE wide_tab (
+ a1 int,
+ a2 int,
+ a3 int,
+ a4 int,
+ a5 int,
+ a6 int,
+ a7 int,
+ a8 int,
+ a9 int,
+ a10 int,
+ a11 int,
+ a12 int,
+ a13 int,
+ a14 int,
+ a15 int,
+ a16 int
+) PARTITION BY RANGE ON (a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16); -- ok
+DROP TABLE wide_tab CASCADE;
+
+CREATE TABLE extra_wide_tab (
+ a1 int,
+ a2 int,
+ a3 int,
+ a4 int,
+ a5 int,
+ a6 int,
+ a7 int,
+ a8 int,
+ a9 int,
+ a10 int,
+ a11 int,
+ a12 int,
+ a13 int,
+ a14 int,
+ a15 int,
+ a16 int,
+ a17 int
+) PARTITION BY RANGE ON (a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17); -- fail
+
+-- At the moment, partitioning requires to use columns with data type having
+-- a suitable btree operator class
+CREATE TABLE point_tab (a point) PARTITION BY LIST ON (a); -- fail
+CREATE TABLE point_tab (a point) PARTITION BY LIST ON (a USING point_ops); -- fail
+CREATE TABLE point_tab (a point) PARTITION BY RANGE ON (a); -- fail
+CREATE TABLE point_tab (a point) PARTITION BY RANGE ON (a USING point_ops); -- fail
+
+-- fail to create a partitioned table with PRIMARY KEY constraint
+CREATE TABLE pk_fail (a int PRIMARY KEY) PARTITION BY RANGE ON (a);
+
+-- fail to create a partitioned table with foreign key constraint
+CREATE TABLE pk_rel(a int PRIMARY KEY);
+CREATE TABLE pk_fail (a int REFERENCES pk_rel(a)) PARTITION BY RANGE ON (a);
+DROP TABLE pk_rel;
+
+-- fail to let a partitioned table be referenced in a foreign key
+CREATE TABLE foo (a int) PARTITION BY RANGE ON (a);
+CREATE TABLE bar_ref_foo (a int REFERENCES foo(a));
+DROP TABLE foo CASCADE;
+
+-- fail to create a partitioned table with prohibited expressions in key
+CREATE TABLE fail_const_key (a int) PARTITION BY RANGE ON (('a'));
+CREATE TABLE fail_agg_in_key (a int) PARTITION BY RANGE ON ((avg(a)));
+CREATE TABLE fail_window_fun_in_key (a int, b int) PARTITION BY RANGE ON ((avg(a) OVER (PARTITION BY b)));
+
+--
+-- creating partitions with sane FOR VALUES specification
+--
+CREATE TABLE range_partitioned (
+ a int
+) PARTITION BY RANGE ON (a);
+
+CREATE TABLE fail_partition
+ PARTITION OF range_partitioned
+ FOR VALUES IN (1, 2); -- fail
+DROP TABLE range_partitioned CASCADE;
+
+CREATE TABLE list_partitioned (
+ a int
+) PARTITION BY LIST ON (a);
+
+CREATE TABLE fail_partition
+ PARTITION OF list_partitioned
+ FOR VALUES LESS THAN (100); -- fail
+DROP TABLE list_partitioned CASCADE;
+
+--
+-- creating range partitions sanely
+--
+CREATE TABLE sales (
+ tos timestamp NOT NULL,
+ item text
+) PARTITION BY RANGE ON (extract(year from tos), extract(month from tos));
+
+CREATE TABLE sales_before_2015
+ PARTITION OF sales
+ FOR VALUES LESS THAN (2015, 1); -- ok
+
+CREATE TABLE sales_2015_q1
+ PARTITION OF sales
+ FOR VALUES LESS THAN (2015, 4); -- ok
+
+-- effective partition range empty or overlapping
+CREATE TABLE sales_2015_q2
+ PARTITION OF sales
+ FOR VALUES LESS THAN (2015, 4); -- fail
+CREATE TABLE sales_2015_q2
+ PARTITION OF sales
+ FOR VALUES LESS THAN (2015, 3); -- fail
+
+-- more bound values than key columns
+CREATE TABLE sales_2015_q2
+ PARTITION OF sales
+ FOR VALUES LESS THAN (2015, 3, 1); -- fail
+DROP TABLE sales CASCADE;
+
+--
+-- creating list partitions sanely
+--
+CREATE TABLE store (
+ id int,
+ city text NOT NULL
+) PARTITION BY LIST ON (city);
+
+CREATE TABLE store_east
+ PARTITION OF store
+ FOR VALUES IN ('New York', 'Boston'); -- ok
+
+CREATE TABLE store_west
+ PARTITION OF store
+ FOR VALUES ('San Fransisco', 'Seattle'); -- ok
+
+-- overlap with some existing partition
+CREATE TABLE store_ca
+ PARTITION OF store
+ FOR VALUES ('Los Angeles', 'San Fransisco'); -- fail
+
+DROP TABLE store CASCADE;
+
+--
+-- relpersistence of partitioned table and partitions
+--
+CREATE TEMP TABLE foo (
+ a int
+) PARTITION BY RANGE ON (a);
+
+CREATE TEMP TABLE foo_1
+ PARTITION OF foo
+ FOR VALUES LESS THAN (100); -- ok
+
+-- cannot be non-temp partition of temp table
+CREATE TABLE foo_2
+ PARTITION OF foo
+ FOR VALUES LESS THAN (100); -- fail
+DROP TABLE foo CASCADE;
+
+CREATE UNLOGGED TABLE foo (
+ a int
+) PARTITION BY RANGE ON (a);
+
+CREATE UNLOGGED TABLE foo_1
+ PARTITION OF foo
+ FOR VALUES LESS THAN (100);
+
+-- cannot be logged/permanent partition of unlogged table
+CREATE TABLE foo_2
+ PARTITION OF foo
+ FOR VALUES LESS THAN (100); -- fail
+DROP TABLE foo CASCADE;
+
+CREATE TABLE foo (
+ a int
+) PARTITION BY RANGE ON (a);
+
+-- cannot be temp partition of non-temp table
+CREATE TEMP TABLE foo_2
+ PARTITION OF foo
+ FOR VALUES LESS THAN (200); -- fail
+
+-- cannot be unlogged partition of permanent table
+CREATE UNLOGGED TABLE foo_1
+ PARTITION OF foo
+ FOR VALUES LESS THAN (100); -- fail
+DROP TABLE foo CASCADE;
+
+--
+-- ALTER TABLE tests
+--
+CREATE TABLE foo (
+ a int
+) PARTITION BY RANGE ON (a);
+
+CREATE TABLE foo_1
+ PARTITION OF foo
+ FOR VALUES LESS THAN (10);
+
+-- cannot add column to a partition
+ALTER TABLE foo_1 ADD COLUMN b char; -- fail
+ALTER TABLE foo ADD COLUMN b char; -- ok
+SELECT * FROM foo;
+
+-- cannot drop a column from a partition
+ALTER TABLE foo_1 DROP COLUMN b; -- fail
+ALTER TABLE foo DROP COLUMN b; -- ok
+SELECT * FROM foo;
+
+-- cannot drop a partition key column
+ALTER TABLE foo DROP COLUMN a;
+
+-- cannot alter NOT NULL constraint of column of a partition
+ALTER TABLE foo_1 ALTER a SET NOT NULL; -- fail
+ALTER TABLE foo ALTER a SET NOT NULL; -- ok
+\d foo_1;
+
+ALTER TABLE foo_1 ALTER a DROP NOT NULL; -- fail
+ALTER TABLE foo ALTER a DROP NOT NULL; -- ok
+\d foo_1;
+
+-- cannot alter column default of a partition
+ALTER TABLE foo_1 ALTER COLUMN a SET DEFAULT 0; -- fail
+ALTER TABLE foo ALTER COLUMN a SET DEFAULT 0; -- ok
+
+INSERT INTO foo DEFAULT VALUES;
+SELECT * FROM foo;
+
+ALTER TABLE foo_1 ALTER COLUMN a DROP DEFAULT; -- fail
+ALTER TABLE foo ALTER COLUMN a DROP DEFAULT; -- ok
+
+INSERT INTO foo DEFAULT VALUES; -- fail (null default)
+
+-- cannot alter column type
+ALTER TABLE foo ADD COLUMN b text;
+INSERT INTO foo VALUES (9, 'ab');
+ALTER TABLE foo_1 ALTER b TYPE char; -- fail
+ALTER TABLE foo ALTER b TYPE char; -- fail (found values too long)
+UPDATE foo SET b = 'a' WHERE length(b) > 1;
+ALTER TABLE foo ALTER b TYPE char; -- ok
+SELECT * FROM foo;
+
+-- cannot alter type of a partition key column
+ALTER TABLE foo ALTER a TYPE char;
+
+-- cannot add/drop a table constraint on a partition
+ALTER TABLE foo_1 ADD CONSTRAINT foo_chk_a CHECK (a >= 0); -- fail
+ALTER TABLE foo ADD CONSTRAINT foo_chk_a CHECK (a >= 0); -- ok
+
+INSERT INTO foo VALUES (-1); -- fails
+INSERT INTO foo VALUES (0);
+UPDATE foo SET a = -1 WHERE = a = 0 -- fails
+
+-- cannot change persistence of a partition
+ALTER TABLE foo_1 SET UNLOGGED; -- fail
+ALTER TABLE foo SET UNLOGGED; -- ok
+\d foo_1
+
+--
+-- OTOH, there are some AT commands that are OK to do on partitions
+--
+
+-- can validate a constraint
+ALTER TABLE foo DROP CONSTRAINT foo_chk_a;
+INSERT INTO foo VALUES (-1);
+ALTER TABLE foo ADD CONSTRAINT foo_chk_a CHECK (a >= 0) NOT VALID;
+ALTER TABLE foo_1 VALIDATE CONSTRAINT foo_chk_a; -- fail
+DELETE FROM foo WHERE a = -1;
+ALTER TABLE foo_1 VALIDATE CONSTRAINT foo_chk_a; -- ok
+
+-- or validate it on the whole partitioned table
+ALTER TABLE foo DROP CONSTRAINT foo_chk_a;
+INSERT INTO foo VALUES (-1); -- ok
+ALTER TABLE foo ADD CONSTRAINT foo_chk_a CHECK (a >= 0) NOT VALID;
+ALTER TABLE foo VALIDATE CONSTRAINT foo_chk_a; -- fail
+DELETE FROM foo WHERE a = -1;
+ALTER TABLE foo VALIDATE CONSTRAINT foo_chk_a; -- ok
+
+-- can set/reset attribute options
+ALTER TABLE foo_1 ALTER a SET (n_distinct=10); -- ok
+
+-- can set attribute storage
+ALTER TABLE foo ADD COLUMN b text;
+\d foo_1
+ALTER TABLE foo_1 ALTER b SET STORAGE EXTERNAL;
+\d foo_1
+
+-- can create index-creating constraint
+DELETE FROM foo;
+ALTER TABLE foo_1 ADD UNIQUE (a);
+\d foo_1
+
+-- can create a constraint using index
+CREATE INDEX foo_1_b_idx ON foo_1 (b);
+ALTER TABLE foo_1 ADD UNIQUE USING foo_1_b_idx;
+\d foo_1
+
+-- can drop index constraints (others cannot be created at all)
+ALTER TABLE foo_1 DROP CONSTRAINT foo_1_a_key;
+
+-- can set/reset CLUSTER ON
+ALTER TABLE foo_1 CLUSTER ON foo_1_a_idx;
+\d foo_1
+ALTER TABLE foo_1 SET WITHOUT CLUSTER;
+\d foo_1
+
+-- can set/reset table storage options
+ALTER TABLE foo_1 SET (fillfactor=80)
+\d+ foo_1
+ALTER TABLE foo_1 RESET (fillfactor)
+\d+ foo_1
+
+-- can set replica identity
+ALTER TABLE foo ALTER a SET NOT NULL;
+ALTER TABLE foo_1 REPLICA IDENTITY USING INDEX foo_1_a_idx;
+\d foo_1
+
+ALTER TABLE foo_1 REPLICA IDENTITY NOTHING;
+\d+ foo_1
+
+-- can set statistics on a column
+ALTER TABLE foo_1 ALTER a SET STATISTICS 1;
+\d+ foo_1
+
+--
+-- some more tests
+--
+
+-- cannot drop paritions
+DROP TABLE foo_1; -- fail
+
+-- detach partition
+ALTER TABLE foo DETACH PARTITION foo_1 USING foo_1_table;
+
+-- now drop 'table' foo_1_table
+DROP TABLE foo_1_table; -- ok
+
+-- feature not implemented message
+ALTER TABLE foo ATTACH PARTITION foo_1 FOR VALUES LESS THAN (10) USING TABLE foo_1_table;
+
+DROP TABLE foo CASCADE;
+
+--
+-- some commands are not ready for either partitioned tables or partitions
+--
+CREATE TABLE command_fail_tab (a int) PARTITION BY RANGE ON (a);
+CREATE TABLE command_fail_partition PARTITION OF command_fail_tab FOR VALUES LESS THAN (10);
+
+VACUUM command_fail_tab; -- fail
+CLUSTER command_fail_tab; -- fail
+REINDEX TABLE command_fail_tab; -- fail
+ANALYZE command_fail_tab; -- fail
+
+COPY command_fail_tab TO stdout; -- fail;
+COPY command_fail_partition FROM stdin; -- fail
+1
+2
+\.
+
+DROP TABLE command_fail_tab CASCADE;
^ permalink raw reply [nested|flat] 161+ messages in thread
* Re: Declarative partitioning
@ 2015-11-05 16:29 Robert Haas <robertmhaas@gmail.com>
parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
0 siblings, 1 reply; 161+ messages in thread
From: Robert Haas @ 2015-11-05 16:29 UTC (permalink / raw)
To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; +Cc: Simon Riggs <simon@2ndquadrant.com>; pgsql-hackers
On Fri, Oct 30, 2015 at 6:08 AM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:
> The DDL and catalogs part are not much different from what I had last
> described though I took a few steps to simplify things. I dropped the
> multi-level partitioning bit
Hmm, that doesn't sound good to me. I think multi-level partitioning
is a reasonably important use case.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 161+ messages in thread
* Re: Declarative partitioning
@ 2015-11-20 09:18 Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
parent: Robert Haas <robertmhaas@gmail.com>
0 siblings, 1 reply; 161+ messages in thread
From: Amit Langote @ 2015-11-20 09:18 UTC (permalink / raw)
To: Robert Haas <robertmhaas@gmail.com>; +Cc: Simon Riggs <simon@2ndquadrant.com>; pgsql-hackers
On 2015/11/06 1:29, Robert Haas wrote:
> On Fri, Oct 30, 2015 at 6:08 AM, Amit Langote
> <Langote_Amit_f8@lab.ntt.co.jp> wrote:
>> The DDL and catalogs part are not much different from what I had last
>> described though I took a few steps to simplify things. I dropped the
>> multi-level partitioning bit
>
> Hmm, that doesn't sound good to me. I think multi-level partitioning
> is a reasonably important use case.
I agree. I'm in the process of reformulating this proposal from the
syntax, catalog and DDL -centric perspective and will re-incorporate
multi-level partitioning notion into it. It was a mistake to drop it.
I am thinking of introducing an explicit notion of sub-partition key and
sub-partitions (of the top parent as far as syntactic notation is
concerned). I guess it would not be unreasonable to think that most
use-cases that multi-level partitioning is used for require at most 2
levels. It will enable us to use a more intuitive syntax and make
internals easier to manage.
Thanks,
Amit
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 161+ messages in thread
* Re: Declarative partitioning
@ 2015-11-20 10:20 Simon Riggs <simon@2ndQuadrant.com>
parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
0 siblings, 1 reply; 161+ messages in thread
From: Simon Riggs @ 2015-11-20 10:20 UTC (permalink / raw)
To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; +Cc: Robert Haas <robertmhaas@gmail.com>; Simon Riggs <simon@2ndquadrant.com>; pgsql-hackers
On 20 November 2015 at 09:18, Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
wrote:
> On 2015/11/06 1:29, Robert Haas wrote:
> > On Fri, Oct 30, 2015 at 6:08 AM, Amit Langote
> > <Langote_Amit_f8@lab.ntt.co.jp> wrote:
> >> The DDL and catalogs part are not much different from what I had last
> >> described though I took a few steps to simplify things. I dropped the
> >> multi-level partitioning bit
> >
> > Hmm, that doesn't sound good to me. I think multi-level partitioning
> > is a reasonably important use case.
>
> I agree. I'm in the process of reformulating this proposal from the
> syntax, catalog and DDL -centric perspective and will re-incorporate
> multi-level partitioning notion into it. It was a mistake to drop it.
>
Drop it?? I think he means "in this initial patch", right Amit L ?
I don't really understand why parallel query was pursued in small pieces,
but partitioning needs to happen all in one huge patch. Wishing too many
things is going to slow down this feature.
--
Simon Riggs http://www.2ndQuadrant.com/
<http://www.2ndquadrant.com/;
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
^ permalink raw reply [nested|flat] 161+ messages in thread
* Re: Declarative partitioning
@ 2015-11-20 11:09 Amit Langote <amitlangote09@gmail.com>
parent: Simon Riggs <simon@2ndQuadrant.com>
0 siblings, 1 reply; 161+ messages in thread
From: Amit Langote @ 2015-11-20 11:09 UTC (permalink / raw)
To: Simon Riggs <simon@2ndquadrant.com>; +Cc: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; Robert Haas <robertmhaas@gmail.com>; pgsql-hackers
On Fri, Nov 20, 2015 at 7:20 PM, Simon Riggs <simon@2ndquadrant.com> wrote:
> On 20 November 2015 at 09:18, Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
> wrote:
>>
>> On 2015/11/06 1:29, Robert Haas wrote:
>> > On Fri, Oct 30, 2015 at 6:08 AM, Amit Langote
>> > <Langote_Amit_f8@lab.ntt.co.jp> wrote:
>> >> The DDL and catalogs part are not much different from what I had last
>> >> described though I took a few steps to simplify things. I dropped the
>> >> multi-level partitioning bit
>> >
>> > Hmm, that doesn't sound good to me. I think multi-level partitioning
>> > is a reasonably important use case.
>>
>> I agree. I'm in the process of reformulating this proposal from the
>> syntax, catalog and DDL -centric perspective and will re-incorporate
>> multi-level partitioning notion into it. It was a mistake to drop it.
>
>
> Drop it?? I think he means "in this initial patch", right Amit L ?
Yes, there was some notion of multi-level partitioning in the earlier
patch but I removed it from the version I posted on Oct 30. I do
intend to re-introduce it though.
Thanks,
Amit
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 161+ messages in thread
* Re: Declarative partitioning
@ 2015-11-20 11:44 Alvaro Herrera <alvherre@2ndquadrant.com>
parent: Amit Langote <amitlangote09@gmail.com>
0 siblings, 1 reply; 161+ messages in thread
From: Alvaro Herrera @ 2015-11-20 11:44 UTC (permalink / raw)
To: Amit Langote <amitlangote09@gmail.com>; +Cc: Simon Riggs <simon@2ndquadrant.com>; Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; Robert Haas <robertmhaas@gmail.com>; pgsql-hackers
Amit Langote wrote:
> On Fri, Nov 20, 2015 at 7:20 PM, Simon Riggs <simon@2ndquadrant.com> wrote:
> > Drop it?? I think he means "in this initial patch", right Amit L ?
>
> Yes, there was some notion of multi-level partitioning in the earlier
> patch but I removed it from the version I posted on Oct 30. I do
> intend to re-introduce it though.
I'm with Simon. In my own experience, it's crazy to try to introduce a
huge feature such as this one in a single enormous commit. The last
patch you posted was 300 kb without any SGML changes.
The way parallel query is being introduced is a good example to follow
(or logical multi-master replication, for that matter) --- one
infrastructure piece at a time.
Let's build incrementally.
--
Álvaro Herrera http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 161+ messages in thread
* Re: Declarative partitioning
@ 2015-11-23 17:23 Robert Haas <robertmhaas@gmail.com>
parent: Alvaro Herrera <alvherre@2ndquadrant.com>
0 siblings, 2 replies; 161+ messages in thread
From: Robert Haas @ 2015-11-23 17:23 UTC (permalink / raw)
To: Alvaro Herrera <alvherre@2ndquadrant.com>; +Cc: Amit Langote <amitlangote09@gmail.com>; Simon Riggs <simon@2ndquadrant.com>; Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; pgsql-hackers
On Fri, Nov 20, 2015 at 6:44 AM, Alvaro Herrera
<alvherre@2ndquadrant.com> wrote:
> Amit Langote wrote:
>> On Fri, Nov 20, 2015 at 7:20 PM, Simon Riggs <simon@2ndquadrant.com> wrote:
>> > Drop it?? I think he means "in this initial patch", right Amit L ?
>>
>> Yes, there was some notion of multi-level partitioning in the earlier
>> patch but I removed it from the version I posted on Oct 30. I do
>> intend to re-introduce it though.
>
> I'm with Simon. In my own experience, it's crazy to try to introduce a
> huge feature such as this one in a single enormous commit. The last
> patch you posted was 300 kb without any SGML changes.
>
> The way parallel query is being introduced is a good example to follow
> (or logical multi-master replication, for that matter) --- one
> infrastructure piece at a time.
>
> Let's build incrementally.
I support building incrementally, but I don't see why we want to
change the catalog structure and then change it again. That seems
like it makes the project more work, not less.
To me, it seems like there is a pretty obvious approach here: each
table can be either a plain table, or a partition root (which can look
just like an empty inheritance parent). Then multi-level partitioning
falls right out of that design without needing to do anything extra.
If you want a single level of partitioning, you partition the original
table. If you want two levels of partitioning, you partition the
partitions. If you want three levels of partitioning, you partition
those. It's being made out here that limiting ourselves to a single
of partitioning makes things simpler, but it's not very clear to me
that this is actually true.
I think it is also worth getting the syntax right from the beginning.
Even if we decide that patch #1 won't support multi-level
partitioning, we should have a plan for the syntax that can be
extended to multi-level partitioning. If we decide after releasing
partitioning with one syntax that we really wish we'd used some other
syntax, that is going to be a really big problem - deprecating the use
of => or introducing standard_conforming_strings were projects that
took many years to complete. We really only get one shot to get that
right. That doesn't mean it's all got to be there in version one, but
there had better be a way to extend it to all the things we want to do
later or we are going to be deeply, deeply sad.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 161+ messages in thread
* Re: Declarative partitioning
@ 2015-11-23 18:44 Alvaro Herrera <alvherre@2ndquadrant.com>
parent: Robert Haas <robertmhaas@gmail.com>
1 sibling, 1 reply; 161+ messages in thread
From: Alvaro Herrera @ 2015-11-23 18:44 UTC (permalink / raw)
To: Robert Haas <robertmhaas@gmail.com>; +Cc: Amit Langote <amitlangote09@gmail.com>; Simon Riggs <simon@2ndquadrant.com>; Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; pgsql-hackers
Robert Haas wrote:
> I support building incrementally, but I don't see why we want to
> change the catalog structure and then change it again. That seems
> like it makes the project more work, not less.
I agree with what you say. I thought you were saying that the
implementation had to provide multi-partitioning from the get-go, not
just the design.
> To me, it seems like there is a pretty obvious approach here: each
> table can be either a plain table, or a partition root (which can look
> just like an empty inheritance parent). Then multi-level partitioning
> falls right out of that design without needing to do anything extra.
Sounds reasonable.
> I think it is also worth getting the syntax right from the beginning.
Yes, that's critical. We could implement the whole thing in gram.y and
then have the unsupported cases throw errors; then it's easy to see that
there are no grammar conflicts to deal with later.
--
Álvaro Herrera http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 161+ messages in thread
* Re: Declarative partitioning
@ 2015-11-23 22:03 Robert Haas <robertmhaas@gmail.com>
parent: Alvaro Herrera <alvherre@2ndquadrant.com>
0 siblings, 1 reply; 161+ messages in thread
From: Robert Haas @ 2015-11-23 22:03 UTC (permalink / raw)
To: Alvaro Herrera <alvherre@2ndquadrant.com>; +Cc: Amit Langote <amitlangote09@gmail.com>; Simon Riggs <simon@2ndquadrant.com>; Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; pgsql-hackers
On Mon, Nov 23, 2015 at 1:44 PM, Alvaro Herrera
<alvherre@2ndquadrant.com> wrote:
> Robert Haas wrote:
>> I support building incrementally, but I don't see why we want to
>> change the catalog structure and then change it again. That seems
>> like it makes the project more work, not less.
>
> I agree with what you say. I thought you were saying that the
> implementation had to provide multi-partitioning from the get-go, not
> just the design.
Well, I *hope* that's going to fall out naturally. If it doesn't, I
can live with that. But I hope it will.
>> To me, it seems like there is a pretty obvious approach here: each
>> table can be either a plain table, or a partition root (which can look
>> just like an empty inheritance parent). Then multi-level partitioning
>> falls right out of that design without needing to do anything extra.
>
> Sounds reasonable.
Cool.
>> I think it is also worth getting the syntax right from the beginning.
>
> Yes, that's critical. We could implement the whole thing in gram.y and
> then have the unsupported cases throw errors; then it's easy to see that
> there are no grammar conflicts to deal with later.
That's worth considering, too.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 161+ messages in thread
* Re: Declarative partitioning
@ 2015-12-02 06:41 Michael Paquier <michael.paquier@gmail.com>
parent: Robert Haas <robertmhaas@gmail.com>
0 siblings, 1 reply; 161+ messages in thread
From: Michael Paquier @ 2015-12-02 06:41 UTC (permalink / raw)
To: Robert Haas <robertmhaas@gmail.com>; +Cc: Alvaro Herrera <alvherre@2ndquadrant.com>; Amit Langote <amitlangote09@gmail.com>; Simon Riggs <simon@2ndquadrant.com>; Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; pgsql-hackers
On Tue, Nov 24, 2015 at 7:03 AM, Robert Haas <robertmhaas@gmail.com> wrote:
> On Mon, Nov 23, 2015 at 1:44 PM, Alvaro Herrera
> <alvherre@2ndquadrant.com> wrote:
>> Robert Haas wrote:
>>> I support building incrementally, but I don't see why we want to
>>> change the catalog structure and then change it again. That seems
>>> like it makes the project more work, not less.
>>
>> I agree with what you say. I thought you were saying that the
>> implementation had to provide multi-partitioning from the get-go, not
>> just the design.
>
> Well, I *hope* that's going to fall out naturally. If it doesn't, I
> can live with that. But I hope it will.
>
>>> To me, it seems like there is a pretty obvious approach here: each
>>> table can be either a plain table, or a partition root (which can look
>>> just like an empty inheritance parent). Then multi-level partitioning
>>> falls right out of that design without needing to do anything extra.
>>
>> Sounds reasonable.
>
> Cool.
>
>>> I think it is also worth getting the syntax right from the beginning.
>>
>> Yes, that's critical. We could implement the whole thing in gram.y and
>> then have the unsupported cases throw errors; then it's easy to see that
>> there are no grammar conflicts to deal with later.
>
> That's worth considering, too.
It seems that the consensus is to rework a bit more this patch.
Returned with feedback then?
--
Michael
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 161+ messages in thread
* Re: Declarative partitioning
@ 2015-12-02 06:55 Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
parent: Michael Paquier <michael.paquier@gmail.com>
0 siblings, 1 reply; 161+ messages in thread
From: Amit Langote @ 2015-12-02 06:55 UTC (permalink / raw)
To: Michael Paquier <michael.paquier@gmail.com>; Robert Haas <robertmhaas@gmail.com>; +Cc: Alvaro Herrera <alvherre@2ndquadrant.com>; Amit Langote <amitlangote09@gmail.com>; Simon Riggs <simon@2ndquadrant.com>; pgsql-hackers
On 2015/12/02 15:41, Michael Paquier wrote:
> It seems that the consensus is to rework a bit more this patch.
> Returned with feedback then?
Yes, as far as this commitfest is concerned. Or "moved to the next
commitfest"? Not sure exactly which makes sense.
Thanks,
Amit
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 161+ messages in thread
* Re: Declarative partitioning
@ 2015-12-02 07:36 Michael Paquier <michael.paquier@gmail.com>
parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
0 siblings, 0 replies; 161+ messages in thread
From: Michael Paquier @ 2015-12-02 07:36 UTC (permalink / raw)
To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; +Cc: Robert Haas <robertmhaas@gmail.com>; Alvaro Herrera <alvherre@2ndquadrant.com>; Amit Langote <amitlangote09@gmail.com>; Simon Riggs <simon@2ndquadrant.com>; pgsql-hackers
On Wed, Dec 2, 2015 at 3:55 PM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:
> On 2015/12/02 15:41, Michael Paquier wrote:
>> It seems that the consensus is to rework a bit more this patch.
>> Returned with feedback then?
>
> Yes, as far as this commitfest is concerned. Or "moved to the next
> commitfest"? Not sure exactly which makes sense.
OK, then, switched as returned with feedback.
--
Michael
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 161+ messages in thread
* Re: Declarative partitioning
@ 2015-12-14 07:14 Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
parent: Robert Haas <robertmhaas@gmail.com>
1 sibling, 1 reply; 161+ messages in thread
From: Amit Langote @ 2015-12-14 07:14 UTC (permalink / raw)
To: Robert Haas <robertmhaas@gmail.com>; Alvaro Herrera <alvherre@2ndquadrant.com>; +Cc: Amit Langote <amitlangote09@gmail.com>; Simon Riggs <simon@2ndquadrant.com>; pgsql-hackers
On 2015/11/24 2:23, Robert Haas wrote:
> To me, it seems like there is a pretty obvious approach here: each
> table can be either a plain table, or a partition root (which can look
> just like an empty inheritance parent). Then multi-level partitioning
> falls right out of that design without needing to do anything extra.
> If you want a single level of partitioning, you partition the original
> table. If you want two levels of partitioning, you partition the
> partitions. If you want three levels of partitioning, you partition
> those. It's being made out here that limiting ourselves to a single
> of partitioning makes things simpler, but it's not very clear to me
> that this is actually true.
>
> I think it is also worth getting the syntax right from the beginning.
> Even if we decide that patch #1 won't support multi-level
> partitioning, we should have a plan for the syntax that can be
> extended to multi-level partitioning. If we decide after releasing
> partitioning with one syntax that we really wish we'd used some other
> syntax, that is going to be a really big problem - deprecating the use
> of => or introducing standard_conforming_strings were projects that
> took many years to complete. We really only get one shot to get that
> right. That doesn't mean it's all got to be there in version one, but
> there had better be a way to extend it to all the things we want to do
> later or we are going to be deeply, deeply sad.
Previously proposed design would support multi-level partitioning with
some adjustments. One of the reasons to not include it in the recent patch
was a lack of clarity about proper syntax and catalog organization. That
wasn't really nice. So, here is a revised proposal for the syntax and some
general notes. I will adjust my previous patch to follow along any
feedback I receive here.
I think we can provide explicit SUBPARTITION keyword-based syntax for
multi-level partitioning. It's not unreasonable to expect that all
partitions at a given level are partitioned on the same key. IOW, why
require to declare partition key for each partition separately? Instead,
using SUBPARTITION BY on the master table seems better.
Syntax to create a partitioned table (up to 2 levels of partitioning):
CREATE TABLE foo (
...
)
PARTITION BY R/L ON (key0)
SUBPARTITION BY R/L ON (key1)
[(PARTITION foo_1 FOR VALUES <val> [<storage_params>] [<tblspc>]
[(SUBPARTITION foo_1_1 FOR VALUES <val> [<storage_params>] [<tblspc>],
...)], ...)];
The above creates two pg_partitioned_rel entries for foo with partlevel 0
and 1, for key0 and key1, respectively. For foo_1 and foo_1_1, this
creates pg_partition entries, with foo and foo_1 as partparent,
respectively.
Why just 2 levels? - it seems commonplace and makes the syntax more
intuitive? I guess it might be possible to generalize the syntax for
multi-level partitioning. Ideas? If we want to support the notion of
sub-partition template in future, that would require some thought, more
importantly proper catalog organization for the same.
To add a partition to table after-the-fact:
ALTER TABLE foo
CREATE PARTITION foo1 FOR VALUES <val> [<storage_params>] [<tblspc>]
[(SUBPARTITION foo11 FOR VALUES <val> [<storage_params>] [<tblspc>], ...)];
To add a sub-partition to an existing partition:
ALTER TABLE foo
MODIFY PARTITION foo_1
CREATE SUBPARTITION foo_1_ FOR VALUES (val) [<storage_params>] [<tblspc>];
I considered ADD/ALTER instead of CREATE/MODIFY, but there exist grammar
conflicts with ADD/ALTER column.
What about ALTER TABLE? - Instead of allowing ALTER TABLE to be applied
directly to partitions on case-by-case basis (they are tables under the
hood after all), we should restrict AT to the master table. Most of the AT
changes implicitly propagate from the master table to its partitions. Some
of them could be directly applied to partitions and/or sub-partitions such
as rename, storage manipulations like - changing tablespace, storage
parameters (reloptions), etc.:
ALTER TABLE foo
RENAME PARTITION <partition-name> TO <new-name>;
ALTER TABLE foo
RENAME SUBPARTITION <sub-partition-name> TO <new-name>;
ALTER TABLE foo
SET TABLESPACE ... [DEFAULT] FOR PARTITION <partition-name>;
ALTER TABLE foo
SET TABLESPACE ... FOR SUBPARTITION <sub-partition-name>;
ALTER TABLE foo
SET (storage_parameter = value) [DEFAULT] FOR PARTITION <partition-name>;
ALTER TABLE foo
SET (storage_parameter = value) FOR SUBPARTITION <sub-partition-name>;
Note that the keyword DEFAULT in some cases above means, do not apply the
new setting to existing sub-partitions of the partition, rather use the
new setting for future sub-partitions. In case of SET TABLESPACE, if FOR
PARTITION clause is not specified, all partitions (actually "leaf"
partitions) of the master table are moved to the new tablespace; is that
necessary or should we just disallow that and instead output an error
asking to use [DEFAULT] FOR PARTITION/FOR SUBPARTITION to move only
specific partitions/sub-partitions?
By the way, should we also allow changing the logging of
partitions/sub-partitions as follows?
ALTER TABLE foo
MODIFY PARTITION <partition-name> SET {LOGGED | UNLOGGED};
ALTER TABLE foo
MODIFY SUBPARTITION <sub-partition-name> SET {LOGGED | UNLOGGED};
What about index constraints, ie, PRIMARY KEY, UNIQUE and EXCLUSION
constraints - 2 things must be clear here: cannot create these constraints
on individual partitions and all partition columns (key0 + key1) must be
the leading columns of the key. On a related note, creating index on the
master table should create the index on all "leaf" partitions. The index
on the mater table itself would be just a logical index. Should we allow
creating or dropping indexes on partitions directly?
It would be interesting to talk about features like SPLIT, MERGE, EXCHANGE
but I'm inclined to leave them as future enhancements. For a functionality
similar to EXCHANGE, there are commands like ATTACH/DETACH in the latest
patch. We could extend them to also consider sub-partitions:
ALTER TABLE foo
ATTACH PARTITION <partition-name> FOR VALUES (val)
USING [ TABLE ] <table-name> [SET VALID];
ALTER TABLE foo
MODIFY PARTITION foo_1 ATTACH SUBPARTITION <sub-partition-name> FOR VALUES
(val)
USING [ TABLE ] <table-name> [SET VALID];
ALTER TABLE foo
DETACH PARTITION foo_1
USING [ TABLE ] <table-name>;
ALTER TABLE foo
MODIFY PARTITION foo_1 DETACH SUBPARTITION foo_1_1
USING [ TABLE ] <table-name>;
Note that all commands described above refer to sub-partitions directly
using names. These names refer to pg_class.relname of corresponding
relations, so any partition/sub-partition names have to be unique across
the database in a given schema. Maybe, if we stored partition name in
pg_partition, system could generate suitable pg_class name itself and
relieve that restriction. It would then be possible to refer to
sub-partitions using its pg_class name or given name qualified by the
partition name.
Let me also outline some significant internal implementation details:
* Introduce a new relkind - RELKIND_PARTITIONED_REL. Master/root tables in
partitioning hierarchies are of this relkind. Following are some key
properties of such relations -
They have a pg_partitioned_rel catalog entry containing information about
the partition key (and sub-partition key, if any). RelationBuildDesc()
initializes the partition key fields in RelationData. It consists of
linked-list of structs corresponding to keys at successive levels.
If happen to be target in I/U/D (/COPY FROM), corresponding
ResultRelInfo's partition key and partition descriptor fields are
initialized. The latter consists of information about its immediate
(level-1) partitions.
In case of U/D, rewrite phase adds tableoid junk attribute to the target
list in addition to a traditional one (ctid) for ExecUpdate and
ExecDelete.
* Next up is the notion of relations that are partitions at any level
(including non-leaf levels). Following are some key properties of such
relations -
They each have a pg_partition entry.
They cannot be targets in I/U and COPY FROM.
One cannot define rules, triggers, and RLS policies on them. Although,
AR triggers defined on a partitioned master table are propagated to the
"leaf" partitions.
Note that RELKIND_PARTITIONED_REL relations and non-leaf partitions do not
have any storage.
* How to handle multi-level partitioning -
To map tuples to a "leaf" partition, CopyFrom(), ExecInsert() (also,
ExecUpdate()) invoke a function called get_partition_for_tuple(). The
function encapsulates details of partitioning such as partitioning method
(R/L) and multi-level recursion.
A similar recursive approach could be used within planner, too. Single
invocation of function get_rel_partitions() from, say,
set_partitioned_rel_size() would return the list of "leaf" partitions of
rel excluding pruned partitions (and sub-partitions thereof). That is,
quals are matched against partition keys at all levels in the same
invocation.
* Revised catalog structures:
pg_partitioned_rel:
partrelid Oid
partmethod char
partlevel int16
partnatts int16
partkey int2vector
partclass oidvector
partexprs pg_node_tree
pg_partition:
partrelid Oid
partparent Oid
partname NameData
partlistvals anyarray
partrangemaxs anyarray
partvalid bool
partsubtemplate bool
partreloptions text
parttablespace Oid
Fields of pg_partition following partrangemaxs are yet unused.
Thoughts?
Thanks,
Amit
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 161+ messages in thread
* Re: Declarative partitioning
@ 2015-12-17 18:56 Robert Haas <robertmhaas@gmail.com>
parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
0 siblings, 1 reply; 161+ messages in thread
From: Robert Haas @ 2015-12-17 18:56 UTC (permalink / raw)
To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; +Cc: Alvaro Herrera <alvherre@2ndquadrant.com>; Amit Langote <amitlangote09@gmail.com>; Simon Riggs <simon@2ndquadrant.com>; pgsql-hackers
On Mon, Dec 14, 2015 at 2:14 AM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:
> Syntax to create a partitioned table (up to 2 levels of partitioning):
>
> CREATE TABLE foo (
> ...
> )
> PARTITION BY R/L ON (key0)
> SUBPARTITION BY R/L ON (key1)
> [(PARTITION foo_1 FOR VALUES <val> [<storage_params>] [<tblspc>]
> [(SUBPARTITION foo_1_1 FOR VALUES <val> [<storage_params>] [<tblspc>],
> ...)], ...)];
>
> The above creates two pg_partitioned_rel entries for foo with partlevel 0
> and 1, for key0 and key1, respectively. For foo_1 and foo_1_1, this
> creates pg_partition entries, with foo and foo_1 as partparent,
> respectively.
>
> Why just 2 levels? - it seems commonplace and makes the syntax more
> intuitive? I guess it might be possible to generalize the syntax for
> multi-level partitioning. Ideas? If we want to support the notion of
> sub-partition template in future, that would require some thought, more
> importantly proper catalog organization for the same.
I do not think this is a particularly good idea. You're going to need
to dump each partition separately at least in --binary-upgrade mode,
because each is going to have its own magic OIDs that need to be
restored, and also because there will most likely be at least some
properties that are going to vary between partitions. You could
require that every partition have exactly the same set of columns,
constraints, rules, triggers, policies, attribute defaults, comments,
column comments, and everything else that might be different from one
partition to another, and further require that they have exactly
matching indexes. It would take a fair amount of code to prohibit all
that, but it could be done. However, do we really want that? There
may well be some things were we want to enforce that the parent and
the child are exactly identical, but I doubt we want that for
absolutely every property, current and future, of the partition. And
even if you did, because of the --binary-upgrade stuff, you still need
to to be able to dump them separately.
Therefore, I believe it is a whole lot better to make the primary
syntax for table partitioning something where you issue a CREATE
statement for the parent and then a CREATE statement for each child.
If we want to also have a convenience syntax so that people who want
to create a parent and a bunch of children in one fell swoop can do
so, fine.
I would not choose to model the syntax for creating partitions on
Oracle. I don't find that syntax particularly nice or easy to
remember. I say PARTITION BY RANGE, and then inside the parentheses I
use the PARTITION keyword for each partition? Really? But I think
copying the style while having the details be incompatible is an even
worse idea.
> What about ALTER TABLE? - Instead of allowing ALTER TABLE to be applied
> directly to partitions on case-by-case basis (they are tables under the
> hood after all), we should restrict AT to the master table. Most of the AT
> changes implicitly propagate from the master table to its partitions. Some
> of them could be directly applied to partitions and/or sub-partitions such
> as rename, storage manipulations like - changing tablespace, storage
> parameters (reloptions), etc.:
>
> ALTER TABLE foo
> RENAME PARTITION <partition-name> TO <new-name>;
>
> ALTER TABLE foo
> RENAME SUBPARTITION <sub-partition-name> TO <new-name>;
>
> ALTER TABLE foo
> SET TABLESPACE ... [DEFAULT] FOR PARTITION <partition-name>;
>
> ALTER TABLE foo
> SET TABLESPACE ... FOR SUBPARTITION <sub-partition-name>;
>
> ALTER TABLE foo
> SET (storage_parameter = value) [DEFAULT] FOR PARTITION <partition-name>;
>
> ALTER TABLE foo
> SET (storage_parameter = value) FOR SUBPARTITION <sub-partition-name>;
I don't think this is a very good idea. This is basically proposing
that for every DDL command that you can apply to a table, you have to
spell it differently for a partition. That seems like a lot of extra
work for no additional functionality.
> By the way, should we also allow changing the logging of
> partitions/sub-partitions as follows?
Again, I think you're coming at this from the wrong direction.
Instead of saying we're going to disallow all changes to the
partitions and then deciding we need to allow certain changes after
all, I think we should allow everything that is currently allowed for
an inherited table and then decide which of those things we need to
prohibit, and why. For example, if you insist that a child table has
to have a tuple descriptor that matches the parent, that can improve
efficiency: Append won't need to project, and so on. But it now
becomes very difficult to support taking a stand-alone table and
making it a partition of an existing partitioned table, because the
set of dropped columns might not match. Having to give an error in
that case amounts to "we're sorry, we can't attach your partition to
the partitioning hierarchy because of some invisible state that you
can't see" isn't very nice. Now I'm not saying that isn't the right
decision, but I think the design choices here need to be carefully
thought about.
Stepping away from that particular example, a blanket prohibition on
changing any attribute of a child table seems like it will prohibit a
lot of useful things that really ought to work. And again, I don't
think it's a good idea to implement separate syntax for changing a
partition vs. changing a table. If I want to set a partition as
unlogged, I should be able to say ALTER TABLE partition_name UNLOGGED
or maybe ALTER PARTITION partition_name UNLOGGED, not be forced to use
some new grammar production that looks completely different.
> What about index constraints, ie, PRIMARY KEY, UNIQUE and EXCLUSION
> constraints - 2 things must be clear here: cannot create these constraints
> on individual partitions and all partition columns (key0 + key1) must be
> the leading columns of the key. On a related note, creating index on the
> master table should create the index on all "leaf" partitions. The index
> on the mater table itself would be just a logical index. Should we allow
> creating or dropping indexes on partitions directly?
I don't find this to be particularly clear. You are assuming that
nobody wants to create a constraint that a certain value is unique
within a partition. That might not be a real common thing to want to
do, but it could certainly be useful to somebody, and the current
system with table inheritance allows it. For example, suppose that we
have orders partitioned on the order_date column, by month. The user
might want to create a UNIQUE index on order_id on each partition.
Maybe they start over with order_id 1 at the beginning of each month.
But even if, as is more likely, the order IDs keep counting up from
month to month, they don't want to be forced to include the whole
partitioning key in the index in order to have it marked UNIQUE. That
may be enough, in practice, to ensure the global uniqueness of order
IDs even though the system doesn't technically enforce it in all
cases.
If you want an index created on the parent to cascade down to all
children, that's a big project to get right. Suppose I create an
index on the parent table. After a while, I notice that it's getting
bloated, so I created another index with the same definition. Now, I
drop one of the two indexes. One of the two indexes from each child
table needs to go away, and moreover it can't be picked arbitrarily -
it has to be the one that was created at the same time as the parent
index I'm dropping. If you want it to behave like this, you need a
whole system of bookkeeping to make it work right.
For version 1, I'd go the other way and prohibit index definitions on
the empty parent rels. Let people create indexes on the children as
they wish. In a future version, we can add stuff to cascade from
parents to children.
> It would be interesting to talk about features like SPLIT, MERGE, EXCHANGE
> but I'm inclined to leave them as future enhancements. For a functionality
> similar to EXCHANGE, there are commands like ATTACH/DETACH in the latest
> patch. We could extend them to also consider sub-partitions:
We don't need to have these in the first version, but we have to make
some architectural decisions that affect how feasible they are to
implement and in which cases, as noted above.
> One cannot define rules, triggers, and RLS policies on them. Although,
> AR triggers defined on a partitioned master table are propagated to the
> "leaf" partitions.
What value do you see us getting out of restricting these particular things?
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 161+ messages in thread
* Re: Declarative partitioning
@ 2015-12-22 01:51 Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
parent: Robert Haas <robertmhaas@gmail.com>
0 siblings, 1 reply; 161+ messages in thread
From: Amit Langote @ 2015-12-22 01:51 UTC (permalink / raw)
To: Robert Haas <robertmhaas@gmail.com>; +Cc: Alvaro Herrera <alvherre@2ndquadrant.com>; Amit Langote <amitlangote09@gmail.com>; Simon Riggs <simon@2ndquadrant.com>; pgsql-hackers
On 2015/12/18 3:56, Robert Haas wrote:
> On Mon, Dec 14, 2015 at 2:14 AM, Amit Langote
> <Langote_Amit_f8@lab.ntt.co.jp> wrote:
>> Syntax to create a partitioned table (up to 2 levels of partitioning):
>>
>> CREATE TABLE foo (
>> ...
>> )
>> PARTITION BY R/L ON (key0)
>> SUBPARTITION BY R/L ON (key1)
>> [(PARTITION foo_1 FOR VALUES <val> [<storage_params>] [<tblspc>]
>> [(SUBPARTITION foo_1_1 FOR VALUES <val> [<storage_params>] [<tblspc>],
>> ...)], ...)];
>>
>> The above creates two pg_partitioned_rel entries for foo with partlevel 0
>> and 1, for key0 and key1, respectively. For foo_1 and foo_1_1, this
>> creates pg_partition entries, with foo and foo_1 as partparent,
>> respectively.
>>
>> Why just 2 levels? - it seems commonplace and makes the syntax more
>> intuitive? I guess it might be possible to generalize the syntax for
>> multi-level partitioning. Ideas? If we want to support the notion of
>> sub-partition template in future, that would require some thought, more
>> importantly proper catalog organization for the same.
>
> I do not think this is a particularly good idea. You're going to need
> to dump each partition separately at least in --binary-upgrade mode,
> because each is going to have its own magic OIDs that need to be
> restored, and also because there will most likely be at least some
> properties that are going to vary between partitions. You could
> require that every partition have exactly the same set of columns,
> constraints, rules, triggers, policies, attribute defaults, comments,
> column comments, and everything else that might be different from one
> partition to another, and further require that they have exactly
> matching indexes. It would take a fair amount of code to prohibit all
> that, but it could be done. However, do we really want that? There
> may well be some things were we want to enforce that the parent and
> the child are exactly identical, but I doubt we want that for
> absolutely every property, current and future, of the partition. And
> even if you did, because of the --binary-upgrade stuff, you still need
> to to be able to dump them separately.
>
> Therefore, I believe it is a whole lot better to make the primary
> syntax for table partitioning something where you issue a CREATE
> statement for the parent and then a CREATE statement for each child.
> If we want to also have a convenience syntax so that people who want
> to create a parent and a bunch of children in one fell swoop can do
> so, fine.
Regarding --binary-upgrade dump mode, how about we teach pg_dump to dump
each partition separately using ALTER TABLE parent ADD PARTITION
especially for the "magic OIDs" reason? It may very well be a CREATE
PARTITION-style command though. Note that each such command could specify
properties that can be different per partition. I said in my email,
perhaps not so clearly, that "only" WITH options, tablespace and
relpersistence can be different per partition. But I can see why that may
be severely restrictive at this stage.
By the way, what do you think about SUBPARTITION keyword-based syntax for
multi-level partitioning? Should we instead require that each partition
has its own PARTITION BY in its creation command?
>
> I would not choose to model the syntax for creating partitions on
> Oracle. I don't find that syntax particularly nice or easy to
> remember. I say PARTITION BY RANGE, and then inside the parentheses I
> use the PARTITION keyword for each partition? Really? But I think
> copying the style while having the details be incompatible is an even
> worse idea.
As for the convenience syntax (if at all), how about:
CREATE TABLE foo (
...
)
PARTITION BY ... ON (...)
SUBPARTITION BY ... ON (...)
opt_partition_list;
where opt_partition_list is:
PARTITIONS (
partname FOR VALUES ... [WITH] [TABLESPACE] opt_subpart_list
[, ...]
)
where opt_subpart_list is:
SUBPARTITIONS (
subpartname FOR VALUES ... [WITH] [ TABLESPACE]
[, ...]
)
PARTITIONS, SUBPARTITIONS would be new unreserved keywords. Or we can do
away with them.
>> What about ALTER TABLE? - Instead of allowing ALTER TABLE to be applied
>> directly to partitions on case-by-case basis (they are tables under the
>> hood after all), we should restrict AT to the master table. Most of the AT
>> changes implicitly propagate from the master table to its partitions. Some
>> of them could be directly applied to partitions and/or sub-partitions such
>> as rename, storage manipulations like - changing tablespace, storage
>> parameters (reloptions), etc.:
>>
>> ALTER TABLE foo
>> RENAME PARTITION <partition-name> TO <new-name>;
>>
>> ALTER TABLE foo
>> RENAME SUBPARTITION <sub-partition-name> TO <new-name>;
>>
>> ALTER TABLE foo
>> SET TABLESPACE ... [DEFAULT] FOR PARTITION <partition-name>;
>>
>> ALTER TABLE foo
>> SET TABLESPACE ... FOR SUBPARTITION <sub-partition-name>;
>>
>> ALTER TABLE foo
>> SET (storage_parameter = value) [DEFAULT] FOR PARTITION <partition-name>;
>>
>> ALTER TABLE foo
>> SET (storage_parameter = value) FOR SUBPARTITION <sub-partition-name>;
>
> I don't think this is a very good idea. This is basically proposing
> that for every DDL command that you can apply to a table, you have to
> spell it differently for a partition. That seems like a lot of extra
> work for no additional functionality.
I didn't mean to propose the special syntax for every available DDL
command. Just those that modify properties that could be different per
partition. I thought there would be very few such properties and hence not
a lot of special commands. But then again, I may be wrong about what
those properties are.
>
>> By the way, should we also allow changing the logging of
>> partitions/sub-partitions as follows?
>
> Again, I think you're coming at this from the wrong direction.
> Instead of saying we're going to disallow all changes to the
> partitions and then deciding we need to allow certain changes after
> all, I think we should allow everything that is currently allowed for
> an inherited table and then decide which of those things we need to
> prohibit, and why. For example, if you insist that a child table has
> to have a tuple descriptor that matches the parent, that can improve
> efficiency: Append won't need to project, and so on. But it now
> becomes very difficult to support taking a stand-alone table and
> making it a partition of an existing partitioned table, because the
> set of dropped columns might not match. Having to give an error in
> that case amounts to "we're sorry, we can't attach your partition to
> the partitioning hierarchy because of some invisible state that you
> can't see" isn't very nice. Now I'm not saying that isn't the right
> decision, but I think the design choices here need to be carefully
> thought about.
Yeah, I am concerned about the ATTACH PARTITION USING TABLE case for the
very point you mention. And I can see how it may result from the
restrictive model I propose. FWIW, other databases impose a number of
restrictions on the partition roll-in case but not sure if for the
internal reasons we might want to.
>
> Stepping away from that particular example, a blanket prohibition on
> changing any attribute of a child table seems like it will prohibit a
> lot of useful things that really ought to work. And again, I don't
> think it's a good idea to implement separate syntax for changing a
> partition vs. changing a table. If I want to set a partition as
> unlogged, I should be able to say ALTER TABLE partition_name UNLOGGED
> or maybe ALTER PARTITION partition_name UNLOGGED, not be forced to use
> some new grammar production that looks completely different.
Okay. ALTER PARTITION may be the way to go.
>
>> What about index constraints, ie, PRIMARY KEY, UNIQUE and EXCLUSION
>> constraints - 2 things must be clear here: cannot create these constraints
>> on individual partitions and all partition columns (key0 + key1) must be
>> the leading columns of the key. On a related note, creating index on the
>> master table should create the index on all "leaf" partitions. The index
>> on the mater table itself would be just a logical index. Should we allow
>> creating or dropping indexes on partitions directly?
>
> I don't find this to be particularly clear. You are assuming that
> nobody wants to create a constraint that a certain value is unique
> within a partition. That might not be a real common thing to want to
> do, but it could certainly be useful to somebody, and the current
> system with table inheritance allows it. For example, suppose that we
> have orders partitioned on the order_date column, by month. The user
> might want to create a UNIQUE index on order_id on each partition.
> Maybe they start over with order_id 1 at the beginning of each month.
> But even if, as is more likely, the order IDs keep counting up from
> month to month, they don't want to be forced to include the whole
> partitioning key in the index in order to have it marked UNIQUE. That
> may be enough, in practice, to ensure the global uniqueness of order
> IDs even though the system doesn't technically enforce it in all
> cases.
Okay, I didn't consider that one may want to create UNIQUE constraint per
partition. Do they need to be allowed to be different per partition?
Sorry, I could not understand your last sentence regarding global
uniqueness. Given the restrictions on its definition, in what ways could
the system fail to enforce it?
>
> If you want an index created on the parent to cascade down to all
> children, that's a big project to get right. Suppose I create an
> index on the parent table. After a while, I notice that it's getting
> bloated, so I created another index with the same definition. Now, I
> drop one of the two indexes. One of the two indexes from each child
> table needs to go away, and moreover it can't be picked arbitrarily -
> it has to be the one that was created at the same time as the parent
> index I'm dropping. If you want it to behave like this, you need a
> whole system of bookkeeping to make it work right.
>
> For version 1, I'd go the other way and prohibit index definitions on
> the empty parent rels. Let people create indexes on the children as
> they wish. In a future version, we can add stuff to cascade from
> parents to children.
Okay, I may be missing the subtleties of global uniqueness case. I agree
we can leave it out of version 1.
>
>> It would be interesting to talk about features like SPLIT, MERGE, EXCHANGE
>> but I'm inclined to leave them as future enhancements. For a functionality
>> similar to EXCHANGE, there are commands like ATTACH/DETACH in the latest
>> patch. We could extend them to also consider sub-partitions:
>
> We don't need to have these in the first version, but we have to make
> some architectural decisions that affect how feasible they are to
> implement and in which cases, as noted above.
>
>> One cannot define rules, triggers, and RLS policies on them. Although,
>> AR triggers defined on a partitioned master table are propagated to the
>> "leaf" partitions.
>
> What value do you see us getting out of restricting these particular things?
Perhaps, I'm wrong in thinking that we should limit these to be associated
with only the top-level partitioned table.
Thanks a lot for the comments!
Regards,
Amit
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 161+ messages in thread
* Re: Declarative partitioning
@ 2016-01-15 10:48 Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
0 siblings, 2 replies; 161+ messages in thread
From: Amit Langote @ 2016-01-15 10:48 UTC (permalink / raw)
To: pgsql-hackers; +Cc: Robert Haas <robertmhaas@gmail.com>
Thought a bit more on some points (see below). To anyone interested in
getting involved with the review, I'm working on getting a revised version
of the patch out soon. However, I must also mention that we need to reach
consensus on some broader design issues before any meaningful (or
fruitful) code review could be done. Please feel free to post your
thoughts about design, syntax, etc.
On 2015/12/22 10:51, Amit Langote wrote:
> On 2015/12/18 3:56, Robert Haas wrote:
>> On Mon, Dec 14, 2015 at 2:14 AM, Amit Langote wrote:
>>> Syntax to create a partitioned table (up to 2 levels of partitioning):
>>>
>>> CREATE TABLE foo (
>>> ...
>>> )
>>> PARTITION BY R/L ON (key0)
>>> SUBPARTITION BY R/L ON (key1)
>>> [(PARTITION foo_1 FOR VALUES <val> [<storage_params>] [<tblspc>]
>>> [(SUBPARTITION foo_1_1 FOR VALUES <val> [<storage_params>] [<tblspc>],
>>> ...)], ...)];
>>>
[ ... ]
>>
>> Therefore, I believe it is a whole lot better to make the primary
>> syntax for table partitioning something where you issue a CREATE
>> statement for the parent and then a CREATE statement for each child.
>> If we want to also have a convenience syntax so that people who want
>> to create a parent and a bunch of children in one fell swoop can do
>> so, fine.
>
[ ... ]
>
> By the way, what do you think about SUBPARTITION keyword-based syntax for
> multi-level partitioning? Should we instead require that each partition
> has its own PARTITION BY in its creation command?
If we have a CREATE statement for each partition, how do we generalize
that to partitions at different levels? For example, if we use something
like the following to create a partition of parent_name:
CREATE PARTITION partition_name OF parent_name FOR VALUES ...
WITH ... TABLESPACE ...
Do we then say:
CREATE PARTITION subpartition_name OF partition_name ...
to create a level 2 partition (sub-partition) of parent_name? Obviously,
as is readily apparent from the command, it is still a direct partition of
partition_name for all internal purposes (consider partition list caching
in relcache, recursive tuple routing, etc.) save some others.
I ask that also because it's related to the choice of syntax to use to
declare the partition key for the multi-level case. I'm considering the
SUBPARTITION BY notation and perhaps we could generalize it to more than
just 2 levels. So, for the above case, parent_name would have been created as:
CREATE TABLE parent_name PARTITION BY ... SUBPARTITION BY ...
Needless to say, when subpartition_name is created with the command we saw
a moment ago, the root partitioned table would be locked. In fact, adding
a partition anywhere in the hierarchy needs an exclusive lock on the root
table. Also, partition rule (the FOR VALUES clause) would be validated
against PARTITION BY or SUBPARTITION BY clause at the respective level.
Although, I must admit I feel a little uneasy about the inherent asymmetry
in using SUBPARTITION BY for key declaration whereas piggybacking CREATE
PARTITION for creating sub-partitions. Is there a better way?
> As for the convenience syntax (if at all), how about:
>
> CREATE TABLE foo (
> ...
> )
> PARTITION BY ... ON (...)
> SUBPARTITION BY ... ON (...)
> opt_partition_list;
>
> where opt_partition_list is:
>
> PARTITIONS (
> partname FOR VALUES ... [WITH] [TABLESPACE] opt_subpart_list
> [, ...]
> )
>
> where opt_subpart_list is:
>
> SUBPARTITIONS (
> subpartname FOR VALUES ... [WITH] [ TABLESPACE]
> [, ...]
> )
Do we want this at all? It seems difficult to generalize this to
multi-level hierarchy of more than 2 levels.
>>> What about ALTER TABLE? - Instead of allowing ALTER TABLE to be applied
>>> directly to partitions on case-by-case basis (they are tables under the
>>> hood after all), we should restrict AT to the master table. Most of the AT
>>> changes implicitly propagate from the master table to its partitions. Some
>>> of them could be directly applied to partitions and/or sub-partitions such
>>> as rename, storage manipulations like - changing tablespace, storage
>>> parameters (reloptions), etc.:
[ ... ]
>>
>> I don't think this is a very good idea. This is basically proposing
>> that for every DDL command that you can apply to a table, you have to
>> spell it differently for a partition. That seems like a lot of extra
>> work for no additional functionality.
[ ... ]
>>> By the way, should we also allow changing the logging of
>>> partitions/sub-partitions as follows?
>>
>> Again, I think you're coming at this from the wrong direction.
>> Instead of saying we're going to disallow all changes to the
>> partitions and then deciding we need to allow certain changes after
>> all, I think we should allow everything that is currently allowed for
>> an inherited table and then decide which of those things we need to
>> prohibit, and why. For example, if you insist that a child table has
>> to have a tuple descriptor that matches the parent, that can improve
>> efficiency: Append won't need to project, and so on. But it now
>> becomes very difficult to support taking a stand-alone table and
>> making it a partition of an existing partitioned table, because the
>> set of dropped columns might not match. Having to give an error in
>> that case amounts to "we're sorry, we can't attach your partition to
>> the partitioning hierarchy because of some invisible state that you
>> can't see" isn't very nice. Now I'm not saying that isn't the right
>> decision, but I think the design choices here need to be carefully
>> thought about.
>
> Yeah, I am concerned about the ATTACH PARTITION USING TABLE case for the
> very point you mention. And I can see how it may result from the
> restrictive model I propose. FWIW, other databases impose a number of
> restrictions on the partition roll-in case but not sure if for the
> internal reasons we might want to.
After thinking some more on this - I think that identical tuple
descriptors may not just be a nice-to-have but critical in some cases. For
example, consider built-in/trigger-less tuple routing. I'd imagine that
the partition to insert a tuple into would be determined just before
calling heap_insert() in ExecInsert() and CopyFrom(). That means the
HeapTuple that is passed to heap_insert() to insert into the partition
would be based on the root table's tuple descriptor. Note also that the
tuple would have passed through BR, IR triggers, constraints of the root
table. When the data is eventually queried from partitions directly, or
well even via the root table (considering existing executor capabilities),
partition's tuple descriptor at that point had better match the data that
went onto the disk. That means we had better keep at least the following
things in sync: number of attributes, name, position (attnum), type,
notnull-ness of individual attributes. So in order to do that, recursively
apply ADD/DROP COLUMN, SET WITH/WITHOUT OIDS, RENAME COLUMN, ALTER COLUMN
TYPE, SET/DROP NOT NULL on the root table to all the partitions and
prevent those sub-commands to be directly applied to any table
(partitions) in the partitioning hierarchy but the root. I further don't
see the point of allowing to set (or drop) column defaults in partitions
because now INSERT or COPY FROM cannot be directly applied to partitions.
Similar argument could be made for BR, IR triggers and CHECK constraints.
Am I missing something in all of this?
An alternative to doing any of that very well may be to design
trigger-less tuple routing to be smarter about possible mismatch of the
tuple descriptors but I haven't given that a lot of thought. Is that
really an alternative worth looking into?
Coming to the ATTACH PARTITION case, I am not quite sure how we can make
it as forgiving as ALTER TABLE INHERIT is given that we'd expect things
like trigger-less tuple routing to work out-of-the-box as described above.
That said, I would not dare propose an INCLUDING DROPPED extension to LIKE
or would it stick if I do? ;)
Unrelated to the above, I have been thinking about the planner
considerations for partition hierarchies with minimal changes considering
that we'd want to keep it that way for the first cut:
1) At least some optimizer/prep changes to handle adding partitions to
PlannerInfo's append_rel_list would be necessary.
2) To be able to use constraint exclusion, we'd need to create equivalent
CHECK constraints (non-dumpable) when partitions are created and perform
all the necessary bookkeeping. For time being.
You might wonder why (1) requires any special handling - would't
"partition hierarchies" still be "inheritance hierarchies" and so would
work with existing code albeit with unnecessary overheads like Var
translation lists? Let me think out loud about general background on this
so someone can correct me if it seems I'm going at it all wrong -
On one hand, I think to keep treating "partition hierarchies" as
"inheritance hierachies" might have some issues. I am afraid that
documented inheritance semantics may not be what we want to keep using for
the new partitioned tables. By that, I mean all the user-facing behaviors
where inheritance has some bearing. Should it also affect new partitioned
tables? Consider whether inheritance semantics would render infeasible
some of the things that we'd like to introduce for the new partitioned
tables such as automatic tuple routing, or keep us from improving planner
smarts and executor capabilities for partitioned tables over what we
already have.
OTOH, I may be looking at it wrongly. We would not be required to enforce
user-facing inheritance behaviors on the new partitioned tables after all.
That is to say - it's just that new partitioned tables could still use
relevant inheritance infrastructure behind-the-scenes for planning,
execution and a few other things and not care about abiding by regular
inheritance semantics. I should just go ahead and add special cases in all
places where existing inheritance handling code stands to cause trouble
down the line for partitioned tables. We might want to mention that we do
so somewhere in documentation and also note that regular inheritance
semantics does not apply. While it sounds probably fine as implementation
for the feature released initially, a day will inevitably come when this
behind-the-scenes implementation will be changed to something more
amenable to better optimization. But that's for future...
There's a great chance that not everyone cares right now about this part
of the new partitioning but just want to put it out there. There are more
contentious issues like the syntax, partitioning maintenance commands that
we plan to support (now or later) and such.
Any advice is greatly appreciated.
Thanks,
Amit
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 161+ messages in thread
* Re: Declarative partitioning
@ 2016-01-17 00:47 Corey Huinker <corey.huinker@gmail.com>
parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
1 sibling, 1 reply; 161+ messages in thread
From: Corey Huinker @ 2016-01-17 00:47 UTC (permalink / raw)
To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; +Cc: pgsql-hackers; Robert Haas <robertmhaas@gmail.com>
>
>
> If we have a CREATE statement for each partition, how do we generalize
> that to partitions at different levels? For example, if we use something
> like the following to create a partition of parent_name:
>
> CREATE PARTITION partition_name OF parent_name FOR VALUES ...
> WITH ... TABLESPACE ...
>
> Do we then say:
>
> CREATE PARTITION subpartition_name OF partition_name ...
>
That's how I'd want it for partitions created after the initial partitioned
table is created.
I'd like to be able to identify the parent partition by it's own
partitioning parameters rather than name, like the way we can derive the
name of an index in ON CONFLICT. But I see no clean way to do that, and if
one did come up, we'd simply allow the user to replace
<partition_name>
with
table_name PARTITION partition_spec [...PARTITION partition_spec [
...PARTITION turtles_all_the_way_down]]).
Again, totally fine with forcing the maintenance script to know or discover
the name of the partition to be subpartitioned...for now.
> to create a level 2 partition (sub-partition) of parent_name? Obviously,
> as is readily apparent from the command, it is still a direct partition of
> partition_name for all internal purposes (consider partition list caching
> in relcache, recursive tuple routing, etc.) save some others.
>
> I ask that also because it's related to the choice of syntax to use to
> declare the partition key for the multi-level case. I'm considering the
> SUBPARTITION BY notation and perhaps we could generalize it to more than
> just 2 levels. So, for the above case, parent_name would have been created
> as:
>
> CREATE TABLE parent_name PARTITION BY ... SUBPARTITION BY ...
> Needless to say, when subpartition_name is created with the command we saw
> a moment ago, the root partitioned table would be locked. In fact, adding
> a partition anywhere in the hierarchy needs an exclusive lock on the root
> table. Also, partition rule (the FOR VALUES clause) would be validated
> against PARTITION BY or SUBPARTITION BY clause at the respective level.
>
> Although, I must admit I feel a little uneasy about the inherent asymmetry
> in using SUBPARTITION BY for key declaration whereas piggybacking CREATE
> PARTITION for creating sub-partitions. Is there a better way?
>
Provided that the syntax allows for N levels of partitioning, I don't care
if it's
PARTITION BY.., PARTITION BY..., PARTITION BY ...
or
PARTITION BY.., SUBPARTITION BY..., SUBPARTITION BY ...
The first is probably better for meta-coding purposes, but the second makes
it clear which partition layer is first.
>
> > As for the convenience syntax (if at all), how about:
> >
> > CREATE TABLE foo (
> > ...
> > )
> > PARTITION BY ... ON (...)
> > SUBPARTITION BY ... ON (...)
> > opt_partition_list;
> >
> > where opt_partition_list is:
> >
> > PARTITIONS (
> > partname FOR VALUES ... [WITH] [TABLESPACE] opt_subpart_list
> > [, ...]
> > )
> >
> > where opt_subpart_list is:
> >
> > SUBPARTITIONS (
> > subpartname FOR VALUES ... [WITH] [ TABLESPACE]
> > [, ...]
> > )
>
> Do we want this at all? It seems difficult to generalize this to
> multi-level hierarchy of more than 2 levels.
>
I want this.
Granted the syntax of a 3+ level partitioning would be cumbersome, but it
is what the user wanted, and the nested PARTITION/SUBPARTITION. In those
cases, the user might opt to not create more than the default first
subpartition to keep the syntax sane, or we might auto-generate default
partitions (with a VALUES clause of whatever "all values" is for that
datatype...again, this is an area where leveraging range types would be
most valuable).
> On one hand, I think to keep treating "partition hierarchies" as
> "inheritance hierachies" might have some issues. I am afraid that
> documented inheritance semantics may not be what we want to keep using for
> the new partitioned tables. By that, I mean all the user-facing behaviors
> where inheritance has some bearing. Should it also affect new partitioned
> tables? Consider whether inheritance semantics would render infeasible
> some of the things that we'd like to introduce for the new partitioned
> tables such as automatic tuple routing, or keep us from improving planner
> smarts and executor capabilities for partitioned tables over what we
> already have.
>
I feel that Automatic tuple routing should be considered they key benefit
of "real" partitions over inherited tables. Trigger maintenance is most of
the work of custom partitioning schemes, at least the ones I've written.
There's a great chance that not everyone cares right now about this part
> of the new partitioning but just want to put it out there. There are more
> contentious issues like the syntax, partitioning maintenance commands that
> we plan to support (now or later) and such.
>
What I've read so far addresses most of my concerns.
Still somewhat on my mind:
1. ability to describe partition bounds via range types, regardless of
whether the Automatic Tuple Routing uses those types internally.
2. syntax for splitting a partition in two, merging two adjacent partitions
(you probably touched on these earlier and I missed it or forgot).
3. ability to swap a partition with a table not currently associated with
the partitioned table.
4. The applicability of this syntax to materialized views, allowing us to
do REFRESH CONCURRENTLY a few parts at a time, or only refreshing the data
we know needs it.
Items 2 and 3 don't have to be implemented right away, as they're separate
ALTER commands. 4 is a pipe dream. With Item 1 I ask only that we don't
pick a syntax that prevents description via range types.
^ permalink raw reply [nested|flat] 161+ messages in thread
* Re: Declarative partitioning
@ 2016-01-22 04:41 Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
parent: Corey Huinker <corey.huinker@gmail.com>
0 siblings, 1 reply; 161+ messages in thread
From: Amit Langote @ 2016-01-22 04:41 UTC (permalink / raw)
To: Corey Huinker <corey.huinker@gmail.com>; +Cc: pgsql-hackers; Robert Haas <robertmhaas@gmail.com>
Hi,
On 2016/01/17 9:47, Corey Huinker wrote:
>> If we have a CREATE statement for each partition, how do we generalize
>> that to partitions at different levels? For example, if we use something
>> like the following to create a partition of parent_name:
>>
>> CREATE PARTITION partition_name OF parent_name FOR VALUES ...
>> WITH ... TABLESPACE ...
>>
>> Do we then say:
>>
>> CREATE PARTITION subpartition_name OF partition_name ...
>
> That's how I'd want it for partitions created after the initial partitioned
> table is created.
>
> I'd like to be able to identify the parent partition by it's own
> partitioning parameters rather than name, like the way we can derive the
> name of an index in ON CONFLICT. But I see no clean way to do that, and if
> one did come up, we'd simply allow the user to replace
> <partition_name>
> with
> table_name PARTITION partition_spec [...PARTITION partition_spec [
> ...PARTITION turtles_all_the_way_down]]).
>
> Again, totally fine with forcing the maintenance script to know or discover
> the name of the partition to be subpartitioned...for now.
I'm thinking of going with this last option.
So for now, you create an empty partitioned table specifying all the
partition keys without being able to define any partitions in the same
statement. Partitions (and partitions thereof, if any) will be created
using CREATE PARTITION statements, one for each.
>> to create a level 2 partition (sub-partition) of parent_name? Obviously,
>> as is readily apparent from the command, it is still a direct partition of
>> partition_name for all internal purposes (consider partition list caching
>> in relcache, recursive tuple routing, etc.) save some others.
>>
>> I ask that also because it's related to the choice of syntax to use to
>> declare the partition key for the multi-level case. I'm considering the
>> SUBPARTITION BY notation and perhaps we could generalize it to more than
>> just 2 levels. So, for the above case, parent_name would have been created
>> as:
>>
>> CREATE TABLE parent_name PARTITION BY ... SUBPARTITION BY ...
>
>> Needless to say, when subpartition_name is created with the command we saw
>> a moment ago, the root partitioned table would be locked. In fact, adding
>> a partition anywhere in the hierarchy needs an exclusive lock on the root
>> table. Also, partition rule (the FOR VALUES clause) would be validated
>> against PARTITION BY or SUBPARTITION BY clause at the respective level.
>>
>> Although, I must admit I feel a little uneasy about the inherent asymmetry
>> in using SUBPARTITION BY for key declaration whereas piggybacking CREATE
>> PARTITION for creating sub-partitions. Is there a better way?
>
> Provided that the syntax allows for N levels of partitioning, I don't care
> if it's
> PARTITION BY.., PARTITION BY..., PARTITION BY ...
> or
> PARTITION BY.., SUBPARTITION BY..., SUBPARTITION BY ...
>
> The first is probably better for meta-coding purposes, but the second makes
> it clear which partition layer is first.
I prefer second one too, for clarity. In any case, the PARTITION /
SUBPARTITION BY clauses are ordered. That is, successive clauses specify
partition key for corresponding successive levels of the hierarchy.
> There's a great chance that not everyone cares right now about this part
>> of the new partitioning but just want to put it out there. There are more
>> contentious issues like the syntax, partitioning maintenance commands that
>> we plan to support (now or later) and such.
>>
>
> What I've read so far addresses most of my concerns.
>
> Still somewhat on my mind:
>
> 1. ability to describe partition bounds via range types, regardless of
> whether the Automatic Tuple Routing uses those types internally.
Specifying range partitioning bound as PARTITION FOR RANGE <range-literal>
sounds like it offers some flexibility, which can be seen as a good thing.
But it tends to make internal logic slightly complicated.
Whereas, saying PARTITION FOR VALUES LESS THAN (max1, max2, ...) is
notationally simpler still and easier to work with internally. Also, there
will be no confusion about exclusivity of the bound if we document it so.
> 2. syntax for splitting a partition in two, merging two adjacent partitions
> (you probably touched on these earlier and I missed it or forgot).
Maintenance operations like split, merge would not be the first cut.
> 3. ability to swap a partition with a table not currently associated with
> the partitioned table.
EXCHANGE PARTITION-like ability is something on my list. There are a few
hurdles to get there. Especially, source table would have to be physical
tuple descriptor (ie, including dropped columns) compatible with the
partitioned table given some other parts of the design (I touched on those
in my earlier message).
> 4. The applicability of this syntax to materialized views, allowing us to
> do REFRESH CONCURRENTLY a few parts at a time, or only refreshing the data
> we know needs it.
>
> Items 2 and 3 don't have to be implemented right away, as they're separate
> ALTER commands. 4 is a pipe dream. With Item 1 I ask only that we don't
> pick a syntax that prevents description via range types.
Thanks a lot for taking time to comment.
Thanks,
Amit
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 161+ messages in thread
* Re: Declarative partitioning
@ 2016-01-22 13:54 Tomas Vondra <tomas.vondra@2ndquadrant.com>
parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
9 siblings, 2 replies; 161+ messages in thread
From: Tomas Vondra @ 2016-01-22 13:54 UTC (permalink / raw)
To: pgsql-hackers
Hi Amit,
thanks for working on this. Seems the last version of the patch was
submitted more than 2 months ago and I believe large parts of it will
get reworked based on the extensive discussion on this list, so I
haven't looked at the code at all.
I'd like to comment on the one thing and that's the syntax. It seems to
me we're really trying to reinvent the wheel and come up with our own
version of the syntax. Is there a particular reason why not to look at
the syntax of the other databases and adapt as much of the existing
syntax as possible?
I think that's for a few reasons - firstly it makes the life much easier
for the DBAs and users who are either migrating to PostgreSQL or have to
manage a mix of databases. Secondly, it serves as a valuable source of
engineering info, preventing the "I haven't thought of this use case"
problem.
An example of this is the proposed syntax for adding a partition
CREATE TABLE measurement_fail
PARTITION OF measurement
FOR VALUES START ('2006-02-15') END ('2006-03-01');
which seems a bit awkward as both the databases I'm familiar with
(Oracle and Sybase) use ALTER TABLE to do this
ALTER TABLE measurement
ADD PARTITION measurement_fail VALUES LESS THAN ( ... )
And so on for the other commands.
That being said, I entirely agree with Simon (and others) that getting
the planner part work is the crucial part of the patch. But I also think
that a proper abstraction (thanks to good syntax) may be a valuable hint
how to define the catalogs and such.
regards
--
Tomas Vondra http://www.2ndQuadrant.com
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 161+ messages in thread
* Re: Declarative partitioning
@ 2016-01-22 18:42 Corey Huinker <corey.huinker@gmail.com>
parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
0 siblings, 1 reply; 161+ messages in thread
From: Corey Huinker @ 2016-01-22 18:42 UTC (permalink / raw)
To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; +Cc: pgsql-hackers; Robert Haas <robertmhaas@gmail.com>
>
>
> So for now, you create an empty partitioned table specifying all the
> partition keys without being able to define any partitions in the same
> statement. Partitions (and partitions thereof, if any) will be created
> using CREATE PARTITION statements, one for each.
>
...and I would assume that any attempt to insert into a partitioned table
with no partitions (or lacking partitions at a defined level) would be an
error? If so, I'd be ok with that.
> Specifying range partitioning bound as PARTITION FOR RANGE <range-literal>
> sounds like it offers some flexibility, which can be seen as a good thing.
> But it tends to make internal logic slightly complicated.
>
> Whereas, saying PARTITION FOR VALUES LESS THAN (max1, max2, ...) is
> notationally simpler still and easier to work with internally. Also, there
> will be no confusion about exclusivity of the bound if we document it so.
I understand wanting the internal rules to be simple. Oracle clearly went
with VALUES LESS THAN waterfalls for that reason.
What I'm hoping to avoid is:
- having to identify my "year2014" partition by VALUES LESS THAN
'2015-01-01', a bit of cognitive dissonance defining data by what it's not.
- and then hoping that there is a year2013 partition created by someone
with similar sensibilities, the partition definition being incomplete
outside of the context of other partition definitions.
- and then further hoping that nobody drops the year2013 partition, thus
causing new 2013 rows to fall into the year2014 partition, a side effect of
an operation that did not mention the year2014 partition.
Range types do that, and if we're concerned about range type overhead,
we're only dealing with the ranges at DDL time, we can break down the ATR
rules into a more easily digestible form once the partition is modified.
Range continuity can be tested with -|-, but we'd only need to test for
overlaps: gaps in ranges are sometimes a feature, not a bug (ex: I don't
want any rows from future dates and we weren't in business before 1997).
Also, VALUES LESS THAN forces us to use discrete values. There is no way
with to express with VALUES LESS THAN partitions that have float values for
temperature:
ice (,0.0), water [0.0,212.0], steam (212.0,3000.0], plasma (3000.0,).
Yes, I can calculate the day after the last day in a year, I can use
212.0000000001, I can write code to rigorously check that all partitions
are in place. I'd just rather not.
p.s. I'm really excited about what this will bring to Postgres in general
and my organization in particular. This feature alone will help chip away
at our needs for Vertica and Redshift clusters. Let me know if there's
anything I can do to help.
^ permalink raw reply [nested|flat] 161+ messages in thread
* Re: Declarative partitioning
@ 2016-01-25 10:32 Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
parent: Corey Huinker <corey.huinker@gmail.com>
0 siblings, 0 replies; 161+ messages in thread
From: Amit Langote @ 2016-01-25 10:32 UTC (permalink / raw)
To: Corey Huinker <corey.huinker@gmail.com>; +Cc: pgsql-hackers; Robert Haas <robertmhaas@gmail.com>
Hi,
On 2016/01/23 3:42, Corey Huinker wrote:
>> So for now, you create an empty partitioned table specifying all the
>> partition keys without being able to define any partitions in the same
>> statement. Partitions (and partitions thereof, if any) will be created
>> using CREATE PARTITION statements, one for each.
>>
>
> ...and I would assume that any attempt to insert into a partitioned table
> with no partitions (or lacking partitions at a defined level) would be an
> error? If so, I'd be ok with that.
Yes. Basically, for a tuple insert to succeed, there should exist a leaf
partition that the inserted tuple would map to.
>> Specifying range partitioning bound as PARTITION FOR RANGE <range-literal>
>> sounds like it offers some flexibility, which can be seen as a good thing.
>> But it tends to make internal logic slightly complicated.
>>
>> Whereas, saying PARTITION FOR VALUES LESS THAN (max1, max2, ...) is
>> notationally simpler still and easier to work with internally. Also, there
>> will be no confusion about exclusivity of the bound if we document it so.
>
>
> I understand wanting the internal rules to be simple. Oracle clearly went
> with VALUES LESS THAN waterfalls for that reason.
>
> What I'm hoping to avoid is:
> - having to identify my "year2014" partition by VALUES LESS THAN
> '2015-01-01', a bit of cognitive dissonance defining data by what it's not.
> - and then hoping that there is a year2013 partition created by someone
> with similar sensibilities, the partition definition being incomplete
> outside of the context of other partition definitions.
> - and then further hoping that nobody drops the year2013 partition, thus
> causing new 2013 rows to fall into the year2014 partition, a side effect of
> an operation that did not mention the year2014 partition.
I somewhat agree with these concerns. So whenever a left range partition
is deleted, a partition would begin accepting tuples that would have been
mapped to the partition just deleted. Then as you say, partition would
contain data that does not really fit the name. A keen user could then
probably rename the partition to something suitable in the same
transaction. I'd imagine that a partition deleted in this way would be the
leftmost (oldest partition) in most scenarios and there wouldn't be any
future tuples that would map to such partition. Admittedly, that may just
be wishful thinking though.
On the other hand, giving user the ability to specify both min and max
requires a lot of bookkeeping on the system's part. One of my previous
patches had that syntax and I wasn't quite happy with overall flakiness of
the implementation. Granted it would have been slightly easier if I had
used range type machinery and I tried a little but didn't stick with it
for too long. You may have read the following thread:
http://www.postgresql.org/message-id/15387.1423496163@sss.pgh.pa.us
> Range types do that, and if we're concerned about range type overhead,
> we're only dealing with the ranges at DDL time, we can break down the ATR
> rules into a more easily digestible form once the partition is modified.
We still end up with min, max pair per partition along with the
inclusivity of both as being part of the persisted partition rule.
> Range continuity can be tested with -|-, but we'd only need to test for
> overlaps: gaps in ranges are sometimes a feature, not a bug (ex: I don't
> want any rows from future dates and we weren't in business before 1997).
>
> Also, VALUES LESS THAN forces us to use discrete values. There is no way
> with to express with VALUES LESS THAN partitions that have float values for
> temperature:
> ice (,0.0), water [0.0,212.0], steam (212.0,3000.0], plasma (3000.0,).
>
> Yes, I can calculate the day after the last day in a year, I can use
> 212.0000000001, I can write code to rigorously check that all partitions
> are in place. I'd just rather not.
Hmm, this may be a concern.
I have read discussion threads of range types development and one of the
contending issues was around semantics for continuous types.
So here, if all we have is VALUES LESS THAN to define a range partition,
one cannot assign ranges to partitions that cannot be reduced to [) form,
right?
> p.s. I'm really excited about what this will bring to Postgres in general
> and my organization in particular. This feature alone will help chip away
> at our needs for Vertica and Redshift clusters. Let me know if there's
> anything I can do to help.
I am really glad to hear that. Your comments so far are much appreciated.
Thanks,
Amit
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 161+ messages in thread
* Re: Declarative partitioning
@ 2016-01-28 08:45 Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
parent: Tomas Vondra <tomas.vondra@2ndquadrant.com>
1 sibling, 1 reply; 161+ messages in thread
From: Amit Langote @ 2016-01-28 08:45 UTC (permalink / raw)
To: Tomas Vondra <tomas.vondra@2ndquadrant.com>; pgsql-hackers
Hi Tomas,
Thanks for your comments and sorry for replying so late.
On 2016/01/22 22:54, Tomas Vondra wrote:
> thanks for working on this. Seems the last version of the patch was
> submitted more than 2 months ago and I believe large parts of it will get
> reworked based on the extensive discussion on this list, so I haven't
> looked at the code at all.
>
> I'd like to comment on the one thing and that's the syntax. It seems to me
> we're really trying to reinvent the wheel and come up with our own version
> of the syntax. Is there a particular reason why not to look at the syntax
> of the other databases and adapt as much of the existing syntax as possible?
>
> I think that's for a few reasons - firstly it makes the life much easier
> for the DBAs and users who are either migrating to PostgreSQL or have to
> manage a mix of databases. Secondly, it serves as a valuable source of
> engineering info, preventing the "I haven't thought of this use case"
> problem.
>
> An example of this is the proposed syntax for adding a partition
>
> CREATE TABLE measurement_fail
> PARTITION OF measurement
> FOR VALUES START ('2006-02-15') END ('2006-03-01');
>
> which seems a bit awkward as both the databases I'm familiar with (Oracle
> and Sybase) use ALTER TABLE to do this
>
> ALTER TABLE measurement
> ADD PARTITION measurement_fail VALUES LESS THAN ( ... )
Syntax like the one you mention allows to create/modify/move/drop
partitions at 2 levels (generally) using PARTITION and SUBPARTITION
keywords. That might be limiting to some users. I don't have a clear
picture of what a syntax that's general enough would look like, but I
proposed something like what follows:
CREATE TABLE parent (
a int,
b char(10)
) PARTITION BY RANGE ON (a)
SUBPARTITION BY LIST ON ((substring(b from 1 for 2)));
CREATE PARTITION partname OF parent FOR VALUES LESS THAN (100);
CREATE PARTITION subpartname OF partname FOR VALUES IN ('ab');
The latter of the CREATE PARTITION commands lets us create the so-called
sub-partition of 'parent'. Remember that in this scheme, all level 1
partitions are not actually physical tables themselves; only level 2
partitions are. If you stick one more SUBPARTITION BY in parent's
definition, you can:
CREATE PARTITION subsubpartname OF subpartname FOR VALUES ...;
This is something that the Oracle-like syntax won't be able to support.
Although, if we all agree that we'd never want to support such a case then
let's implement something that's familiar viz. the following:
CREATE TABLE parent (
a int,
b char(10)
) PARTITION BY RANGE ON (a)
SUBPARTITION BY LIST ON ((substring(b from 1 for 2)));
ALTER TABLE parent
ADD PARTITION partname FOR VALUES LESS THAN (100);
ALTER TABLE parent
MODIFY PARTITION partname ADD SUBPARTITION subpartname FOR VALUES IN ('ab');
ALTER TABLE parent
MODIFY PARTITION partname DROP SUBPARTITION subpartname;
ALTER TABLE parent
DROP PARTITION partname;
> And so on for the other commands.
>
> That being said, I entirely agree with Simon (and others) that getting the
> planner part work is the crucial part of the patch. But I also think that
> a proper abstraction (thanks to good syntax) may be a valuable hint how to
> define the catalogs and such.
I tried to do that in the November commitfest but decided to just work on
the syntax as it became clear that throwing too many changes at the
reviewers/committers like that may not be such a great idea. Syntax itself
is a big enough change to discuss and reach consensus on. Let's get the
syntax, catalog and some infrastructure for basic features of the new
partitioning to work. We can think about planner enhancements for
partitioned tables that are waiting for declarative partitioning to get in
later.
Thanks,
Amit
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 161+ messages in thread
* Re: Declarative partitioning
@ 2016-02-15 01:55 Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
0 siblings, 2 replies; 161+ messages in thread
From: Amit Langote @ 2016-02-15 01:55 UTC (permalink / raw)
To: pgsql-hackers
Hi,
Attached find a patch series to implement the following syntax:
1. Syntax for defining the partition key as a new clause in CREATE TABLE:
[ PARTITION BY {RANGE | LIST} ON ( { column_name | ( expression ) } [
opclass ] [, ...] )
[ SUBPARTITION BY {RANGE | LIST} ON ( { column_name | ( expression ) }
[ opclass ] [, ...] ) ] ]
2. Syntax to create partitions and sub-partitions (as ALTER TABLE commands
on a partitioned table):
ADD PARTITION name FOR VALUES partition_bound_spec
[ WITH ( storage_parameter [= value] [, ... ] )]
[ TABLESPACE tablespace_name ]
MODIFY PARTITION partition_name ADD SUBPARTITION name FOR VALUES
partition_bound_spec
[ WITH ( storage_parameter [= value] [, ... ] )]
[ TABLESPACE tablespace_name ]
DROP PARTITION name
DROP SUBPARTITION name
ATTACH PARTITION name FOR VALUES partition_bound_spec
USING [ TABLE ] table_name
[ WITH ( storage_parameter [= value] [, ... ] )]
[ TABLESPACE tablespace_name ]
MODIFY PARTITION partition_name ATTACH SUBPARTITION name FOR VALUES
partition_bound_spec
USING [ TABLE ] table_name
[ WITH ( storage_parameter [= value] [, ... ] )]
[ TABLESPACE tablespace_name ]
Where partition_bound_spec is:
FOR VALUES [ IN ] ( expression [, ...] )
FOR VALUES LESS THAN ( expression [, ...] )
DETACH PARTITION name [ USING [ TABLE ] table_name]
DETACH SUBPARTITION name [ USING [ TABLE ] table_name]
Please note that ATTACH PARTITION ... USING TABLE command shown above is
not yet implemented.
As mentioned previously, this uses two system catalogs pg_partitioned_rel
and pg_partition.
This is complete with tuple routing so that no triggers or rules are
required. There is also basic planner support but no support yet to enable
constraint exclusion on partitions (which will be fulfilled shortly by
installing equivalent check constraints on partitions).
There is documentation for the new syntax and catalogs. I feel more
detailed documentation is required so I will keep on improving it in
subsequent versions as we build consensus about the syntax and general
design. There are no regression tests at all in the attached patches which
I will add in the next version along with constraint exclusion support.
pg_dump support will also be added shortly.
The individual patches have commit messages that describe code changes.
This is registered in the upcoming CF. Feedback and review is greatly
welcomed!
Thanks,
Amit
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Attachments:
[text/x-diff] 0001-Add-relkind-RELKIND_PARTITIONED_REL-to-pg_class.h.patch (938B, ../../56C1302D.6070403@lab.ntt.co.jp/2-0001-Add-relkind-RELKIND_PARTITIONED_REL-to-pg_class.h.patch)
download | inline diff:
From 5463d65691bd5037d42280a59120dee008eb0822 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Fri, 8 Jan 2016 09:23:12 +0900
Subject: [PATCH 01/10] Add relkind RELKIND_PARTITIONED_REL to pg_class.h
Partitioned tables to be introduced in subsequent commits will be
marked with this relkind.
---
src/include/catalog/pg_class.h | 1 +
1 files changed, 1 insertions(+), 0 deletions(-)
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index fdc67c2..149f619 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -153,6 +153,7 @@ DESCR("");
#define RELKIND_RELATION 'r' /* ordinary table */
+#define RELKIND_PARTITIONED_REL 'P' /* partitioned table */
#define RELKIND_INDEX 'i' /* secondary index */
#define RELKIND_SEQUENCE 'S' /* sequence object */
#define RELKIND_TOASTVALUE 't' /* for out-of-line values */
--
1.7.1
[text/x-diff] 0002-Add-partitioned-table-system-catalog.patch (11.0K, ../../56C1302D.6070403@lab.ntt.co.jp/3-0002-Add-partitioned-table-system-catalog.patch)
download | inline diff:
From 4bb5a3edee1eaf33b3a6df6892650fc744d50827 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Mon, 8 Feb 2016 15:08:49 +0900
Subject: [PATCH 02/10] Add partitioned table system catalog.
Named pg_partitioned_rel, it contains the following information:
1. Partitioned table oid (pg_class.oid)
2. Partition key level
3. Partition strategy (list, range, etc.)
4. Number of partition attributes
5. Partition key (array of attnum of partition attributes)
6. Partition key opclass info (array of opclass oids, one per key attribute)
7. Partition key expressions (pg_node_tree corresponding to list of expressions)
Add a unique index on (partrelid, partlevel) and associated syscache. There is
also a non-unique index on just partrelid.
---
doc/src/sgml/catalogs.sgml | 109 ++++++++++++++++++++++++++++
src/backend/catalog/Makefile | 2 +-
src/backend/utils/cache/syscache.c | 12 +++
src/include/catalog/indexing.h | 6 ++
src/include/catalog/pg_partitioned_rel.h | 69 ++++++++++++++++++
src/include/utils/syscache.h | 1 +
src/test/regress/expected/sanity_check.out | 1 +
7 files changed, 199 insertions(+), 1 deletions(-)
create mode 100644 src/include/catalog/pg_partitioned_rel.h
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 412c845..db8d0ac 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -219,6 +219,11 @@
</row>
<row>
+ <entry><link linkend="catalog-pg-partitioned-rel"><structname>pg_partitioned_rel</structname></link></entry>
+ <entry>information about partitioned tables, including the partition key</entry>
+ </row>
+
+ <row>
<entry><link linkend="catalog-pg-policy"><structname>pg_policy</structname></link></entry>
<entry>row-security policies</entry>
</row>
@@ -4537,6 +4542,110 @@
</sect1>
+ <sect1 id="catalog-pg-partitioned-rel">
+ <title><structname>pg_partitioned_rel</structname></title>
+
+ <indexterm zone="catalog-pg-partitioned-rel">
+ <primary>pg_partitioned_rel</primary>
+ </indexterm>
+
+ <para>
+ The catalog <structname>pg_partitioned</structname> stores
+ information about the partition key of partitioned tables.
+ </para>
+
+ <table>
+ <title><structname>pg_partitioned_rel</> 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>partlevel</structfield></entry>
+ <entry><type>int2</type></entry>
+ <entry></entry>
+ <entry>
+ Partition key level; It's value is 0 for the top-level key and 1 for
+ the sub-partition key, if any
+ </entry>
+ </row>
+
+ <row>
+ <entry><structfield>partstrat</structfield></entry>
+ <entry><type>char</type></entry>
+ <entry></entry>
+ <entry>
+ Partitioning strategy (or method); <literal>l</> = list partitioned table,
+ <literal>r</> = range partitioned table
+ </entry>
+ </row>
+
+ <row>
+ <entry><structfield>partnatts</structfield></entry>
+ <entry><type>int2</type></entry>
+ <entry></entry>
+ <entry>The number of columns in partition key</entry>
+ </row>
+
+ <row>
+ <entry><structfield>partkey</structfield></entry>
+ <entry><type>int2vector</type></entry>
+ <entry><literal><link linkend="catalog-pg-attribute"><structname>pg_attribute</structname></link>.attnum</literal></entry>
+ <entry>
+ This is an array of <structfield>partnatts</structfield> values that
+ indicate which table columns are used as partition key. For example,
+ a value of <literal>1 3</literal> would mean that the first and the
+ third table columns make up the partition key. A zero in this array
+ indicates that the corresponding partition key column is an expression
+ over the table columns, rather than a simple column reference.
+ </entry>
+ </row>
+
+ <row>
+ <entry><structfield>partclass</structfield></entry>
+ <entry><type>oidvector</type></entry>
+ <entry><literal><link linkend="catalog-pg-opclass"><structname>pg_opclass</structname></link>.oid</literal></entry>
+ <entry>
+ For each column in the partition key, this contains the OID of
+ the operator class to use. See
+ <link linkend="catalog-pg-opclass"><structname>pg_opclass</structname></link> for details.
+ </entry>
+ </row>
+
+ <row>
+ <entry><structfield>partexprs</structfield></entry>
+ <entry><type>pg_node_tree</type></entry>
+ <entry></entry>
+ <entry>
+ Expression trees (in <function>nodeToString()</function>
+ representation) for partition key columns that are not simple column
+ references. This is a list with one element for each zero
+ entry in <structfield>indkey</>. Null if all partition key columns
+ are simple references.
+ </entry>
+ </row>
+
+ </tbody>
+ </tgroup>
+ </table>
+ </sect1>
+
<sect1 id="catalog-pg-policy">
<title><structname>pg_policy</structname></title>
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index 25130ec..df30415 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -41,7 +41,7 @@ POSTGRES_BKI_SRCS = $(addprefix $(top_srcdir)/src/include/catalog/,\
pg_foreign_data_wrapper.h pg_foreign_server.h pg_user_mapping.h \
pg_foreign_table.h pg_policy.h pg_replication_origin.h \
pg_default_acl.h pg_seclabel.h pg_shseclabel.h \
- pg_collation.h pg_range.h pg_transform.h \
+ pg_collation.h pg_range.h pg_transform.h pg_partitioned_rel.h\
toasting.h indexing.h \
)
diff --git a/src/backend/utils/cache/syscache.c b/src/backend/utils/cache/syscache.c
index 65ffe84..f703668 100644
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -48,6 +48,7 @@
#include "catalog/pg_opclass.h"
#include "catalog/pg_operator.h"
#include "catalog/pg_opfamily.h"
+#include "catalog/pg_partitioned_rel.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_range.h"
#include "catalog/pg_rewrite.h"
@@ -568,6 +569,17 @@ static const struct cachedesc cacheinfo[] = {
},
8
},
+ {PartitionedRelRelationId, /* PARTEDRELIDLEVEL */
+ PartitionedRelRelidLevelIndexId,
+ 2,
+ {
+ Anum_pg_partitioned_rel_partrelid,
+ Anum_pg_partitioned_rel_partlevel,
+ 0,
+ 0
+ },
+ 128
+ },
{ProcedureRelationId, /* PROCNAMEARGSNSP */
ProcedureNameArgsNspIndexId,
3,
diff --git a/src/include/catalog/indexing.h b/src/include/catalog/indexing.h
index ab2c1a8..343204b 100644
--- a/src/include/catalog/indexing.h
+++ b/src/include/catalog/indexing.h
@@ -316,6 +316,12 @@ DECLARE_UNIQUE_INDEX(pg_replication_origin_roiident_index, 6001, on pg_replicati
DECLARE_UNIQUE_INDEX(pg_replication_origin_roname_index, 6002, on pg_replication_origin using btree(roname text_pattern_ops));
#define ReplicationOriginNameIndex 6002
+DECLARE_UNIQUE_INDEX(pg_partitioned_rel_partrelid_level_index, 3319, on pg_partitioned_rel using btree(partrelid oid_ops, partlevel int2_ops));
+#define PartitionedRelRelidLevelIndexId 3319
+
+DECLARE_INDEX(pg_partitioned_rel_partrelid_index, 3320, on pg_partitioned_rel using btree(partrelid oid_ops));
+#define PartitionedRelRelidIndexId 3320
+
/* last step of initialization script: build the indexes declared above */
BUILD_INDICES
diff --git a/src/include/catalog/pg_partitioned_rel.h b/src/include/catalog/pg_partitioned_rel.h
new file mode 100644
index 0000000..3f17451
--- /dev/null
+++ b/src/include/catalog/pg_partitioned_rel.h
@@ -0,0 +1,69 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_partitioned_rel.h
+ * definition of the system "partitioned" relation (pg_partitioned_rel)
+ * along with the relation's initial contents.
+ *
+ *
+ * Portions Copyright (c) 1996-2015, PostgreSQL Global Development Group
+ *
+ * $PostgreSQL: pgsql/src/include/catalog/pg_partitioned_rel.h $
+ *
+ * NOTES
+ * the genbki.sh script reads this file and generates .bki
+ * information from the DATA() statements.
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_PARTITIONED_REL_H
+#define PG_PARTITIONED_REL_H
+
+#include "catalog/genbki.h"
+
+/* ----------------
+ * pg_partitioned_rel definition. cpp turns this into
+ * typedef struct FormData_pg_partitioned_rel
+ * ----------------
+ */
+#define PartitionedRelRelationId 3318
+
+CATALOG(pg_partitioned_rel,3318) BKI_WITHOUT_OIDS
+{
+ Oid partrelid; /* partitioned table oid */
+ int16 partlevel; /* partition key level*/
+ char partstrat; /* partition key strategy */
+ int16 partnatts; /* number of partition key columns */
+
+ /* variable-length fields start here, but we allow direct access to indkey */
+ int2vector partkey; /* attribute numbers of partition key
+ * columns */
+
+#ifdef CATALOG_VARLEN
+ oidvector partclass; /* operator class to compare keys */
+ pg_node_tree partexprs; /* expression trees for partition key members
+ * that are not simple column references; one
+ * for each zero entry in partkey[] */
+#endif
+} FormData_pg_partitioned_rel;
+
+/* ----------------
+ * Form_pg_partitioned_rel corresponds to a pointer to a tuple with
+ * the format of pg_partitioned_rel relation.
+ * ----------------
+ */
+typedef FormData_pg_partitioned_rel *Form_pg_partitioned_rel;
+
+/* ----------------
+ * compiler constants for pg_partitioned_rel
+ * ----------------
+ */
+#define Natts_pg_partitioned_rel 7
+#define Anum_pg_partitioned_rel_partrelid 1
+#define Anum_pg_partitioned_rel_partlevel 2
+#define Anum_pg_partitioned_rel_partstrat 3
+#define Anum_pg_partitioned_rel_partnatts 4
+#define Anum_pg_partitioned_rel_partkey 5
+#define Anum_pg_partitioned_rel_partclass 6
+#define Anum_pg_partitioned_rel_partexprs 7
+
+#endif /* PG_PARTITIONED_REL_H */
diff --git a/src/include/utils/syscache.h b/src/include/utils/syscache.h
index 256615b..1c961c7 100644
--- a/src/include/utils/syscache.h
+++ b/src/include/utils/syscache.h
@@ -72,6 +72,7 @@ enum SysCacheIdentifier
OPEROID,
OPFAMILYAMNAMENSP,
OPFAMILYOID,
+ PARTEDRELIDLEVEL,
PROCNAMEARGSNSP,
PROCOID,
RANGETYPE,
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index eb0bc88..19e4e46 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -117,6 +117,7 @@ pg_namespace|t
pg_opclass|t
pg_operator|t
pg_opfamily|t
+pg_partitioned_rel|t
pg_pltemplate|t
pg_policy|t
pg_proc|t
--
1.7.1
[text/x-diff] 0003-Introduce-partition-key-clause-in-CREATE-TABLE.patch (22.0K, ../../56C1302D.6070403@lab.ntt.co.jp/4-0003-Introduce-partition-key-clause-in-CREATE-TABLE.patch)
download | inline diff:
From ad90ca32803d6f9b23e0262e4c558526c5574735 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Mon, 8 Feb 2016 15:21:20 +0900
Subject: [PATCH 03/10] Introduce partition key clause in CREATE TABLE.
This adds the following clause:
PARTITION BY method ON (col [USING opclass] [, ...])
SUBPARTITION BY method ON (col [USING opclass] [, ...])
where 'method' is either RANGE or LIST. 'col' could be either a simple
column reference or a parenthesized expression . 'opclass' is name
of btree operator class to use with the column (compatible with the
type of the column).
Syntax allows specifycing multiple SUBPARTITION BY clauses each of which
specifies the partition key for successively lower level of partition
hierarchy. However, only 2 levels of partitioning will be supported
in upcoming commits that will add support for creating and managing
partitions. So for now, higher level code emits an error if more than one
SUBPARTITION BY clauses are specified. Note that if we find an aggreable
syntax for creating and managing partitions at arbitrary number of levels,
we can remove the aforementioned restriction.
This adds 2 parser nodes viz. PartitionElem and PartitionBy. The former
is similar to IndexElem and the latter encapsulates details of partition
key such as the method and list of PartitionElems. The new member
partitionBy of CreateStmt is simply a list of PartitionBy nodes. The
first member always corresponds to PARTITION BY clause and subsequent
members (if any) to SUBPARTITION BY clause(s).
---
doc/src/sgml/ref/create_table.sgml | 58 ++++++++++++++++
src/backend/nodes/copyfuncs.c | 33 +++++++++
src/backend/nodes/equalfuncs.c | 28 ++++++++
src/backend/nodes/outfuncs.c | 27 +++++++
src/backend/parser/gram.y | 132 ++++++++++++++++++++++++++++++++---
src/backend/parser/parse_utilcmd.c | 35 ++++++++++
src/include/nodes/nodes.h | 2 +
src/include/nodes/parsenodes.h | 35 ++++++++++
src/include/parser/kwlist.h | 2 +
src/include/pg_config_manual.h | 5 ++
10 files changed, 345 insertions(+), 12 deletions(-)
diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml
index cd234db..43725e0 100644
--- a/doc/src/sgml/ref/create_table.sgml
+++ b/doc/src/sgml/ref/create_table.sgml
@@ -28,6 +28,8 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
[, ... ]
] )
[ INHERITS ( <replaceable>parent_table</replaceable> [, ... ] ) ]
+[ PARTITION BY {RANGE | LIST} ON ( { <replaceable class="parameter">column_name</replaceable> | ( <replaceable class="parameter">expression</replaceable> ) } [ <replaceable class="parameter">opclass</replaceable> ] [, ...] )
+ [ SUBPARTITION BY {RANGE | LIST} ON ( { <replaceable class="parameter">column_name</replaceable> | ( <replaceable class="parameter">expression</replaceable> ) } [ <replaceable class="parameter">opclass</replaceable> ] [, ...] ) ] ]
[ WITH ( <replaceable class="PARAMETER">storage_parameter</replaceable> [= <replaceable class="PARAMETER">value</replaceable>] [, ... ] ) | WITH OIDS | WITHOUT OIDS ]
[ ON COMMIT { PRESERVE ROWS | DELETE ROWS | DROP } ]
[ TABLESPACE <replaceable class="PARAMETER">tablespace_name</replaceable> ]
@@ -126,6 +128,21 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
</para>
<para>
+ The optional partition clause specifies the partition key and sub-partition
+ keys (if any). If specified, an empty partitioned table is created. Data
+ rows added to the table are mapped to and stored in partitions (or sub-
+ partitions thereof) which are created using separate commands. The rows
+ are mapped based on values of the columns specified in the key and partition
+ rules associated with individual partitions. Partitions can be specified
+ to be further divided into sub-partitions by specifying the sub-partition
+ key. In fact, there can be multiple sub-partition keys each of which
+ corresponds to division of partitions on successive levels. When partitions
+ are so sub-partitioned, any rows mapped to a given partition are further
+ mapped to one of its own partitions based on values of the columns in
+ the sub-partition key corrsponding to the partition's level.
+ </para>
+
+ <para>
To be able to create a table, you must have <literal>USAGE</literal>
privilege on all column types or the type in the <literal>OF</literal>
clause, respectively.
@@ -314,6 +331,47 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
</varlistentry>
<varlistentry>
+ <term><literal>PARTITION BY {RANGE | LIST} ON ( { <replaceable class="parameter">column_name</replaceable> | ( <replaceable class="parameter">expression</replaceable> ) } [ <replaceable class="parameter">opclass</replaceable> ] [, ...] )
+ [ SUBPARTITION BY {RANGE | LIST} ON ( { <replaceable class="parameter">column_name</replaceable> | ( <replaceable class="parameter">expression</replaceable> ) } [ <replaceable class="parameter">opclass</replaceable> ] [, ...] ) ] </literal></term>
+ <listitem>
+ <para>
+ The optional <literal>PARTITION BY</> clause specifies the partition key
+ of the table. Table thus created is called a partitioned table. The key
+ consists of an ordered list of column names and/or parenthesized
+ expressions when using the <literal>RANGE</> method, whereas only a
+ single column or parenthesized expression can be specified when using
+ the <literal>LIST</> method. The type of a key column or an expresion
+ must have an associated btree operator class or one must be specified
+ along with the column or the expression.
+ </para>
+
+ <para>
+ Partitioned table is divided into sub-tables (called partitions), which
+ in turn, are created using separate commands. The table itself is empty
+ and does not have any storage files. A data row inserted into the table
+ is mapped to and stored in one of its partitions based on the values of
+ columns and/or expressions in the partition key and partition rules
+ associated with the partitions.
+ </para>
+
+ <para>
+ If <literal>SUBPARTITION BY</> clause(s) are specified, any partitions
+ of the table are themselves further divided into partitions using
+ similar rules and so on. Thus a multi-level partition hierarchy can
+ be created. In such a multi-level partitioned table, data is stored
+ in the leaf-level partitions.
+ </para>
+
+ <para>
+ Currently, there are following limitations on definition of partitioned
+ tables: one cannot specify any UNIQUE, PRIMARY KEY, EXCLUDE and/or
+ FOREIGN KEY constraints.
+ </para>
+
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><literal>LIKE <replaceable>source_table</replaceable> [ <replaceable>like_option</replaceable> ... ]</literal></term>
<listitem>
<para>
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index a9e9cc3..6b8eed8 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2995,6 +2995,7 @@ CopyCreateStmtFields(const CreateStmt *from, CreateStmt *newnode)
COPY_NODE_FIELD(relation);
COPY_NODE_FIELD(tableElts);
COPY_NODE_FIELD(inhRelations);
+ COPY_NODE_FIELD(partitionBy);
COPY_NODE_FIELD(ofTypename);
COPY_NODE_FIELD(constraints);
COPY_NODE_FIELD(options);
@@ -4124,6 +4125,32 @@ _copyAlterPolicyStmt(const AlterPolicyStmt *from)
return newnode;
}
+static PartitionBy *
+_copyPartitionBy(const PartitionBy *from)
+{
+
+ PartitionBy *newnode = makeNode(PartitionBy);
+
+ COPY_SCALAR_FIELD(strategy);
+ COPY_NODE_FIELD(partParams);
+ COPY_LOCATION_FIELD(location);
+
+ return newnode;
+}
+
+static PartitionElem *
+_copyPartitionElem(const PartitionElem *from)
+{
+ PartitionElem *newnode = makeNode(PartitionElem);
+
+ COPY_STRING_FIELD(name);
+ COPY_NODE_FIELD(expr);
+ COPY_NODE_FIELD(opclass);
+ COPY_LOCATION_FIELD(location);
+
+ return newnode;
+}
+
/* ****************************************************************
* pg_list.h copy functions
* ****************************************************************
@@ -5011,6 +5038,12 @@ copyObject(const void *from)
case T_RoleSpec:
retval = _copyRoleSpec(from);
break;
+ case T_PartitionBy:
+ retval = _copyPartitionBy(from);
+ break;
+ case T_PartitionElem:
+ retval = _copyPartitionElem(from);
+ break;
default:
elog(ERROR, "unrecognized node type: %d", (int) nodeTag(from));
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index b9c3959..befff43 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1153,6 +1153,7 @@ _equalCreateStmt(const CreateStmt *a, const CreateStmt *b)
COMPARE_NODE_FIELD(relation);
COMPARE_NODE_FIELD(tableElts);
COMPARE_NODE_FIELD(inhRelations);
+ COMPARE_NODE_FIELD(partitionBy);
COMPARE_NODE_FIELD(ofTypename);
COMPARE_NODE_FIELD(constraints);
COMPARE_NODE_FIELD(options);
@@ -2594,6 +2595,27 @@ _equalRoleSpec(const RoleSpec *a, const RoleSpec *b)
return true;
}
+static bool
+_equalPartitionBy(const PartitionBy *a, const PartitionBy *b)
+{
+ COMPARE_SCALAR_FIELD(strategy);
+ COMPARE_NODE_FIELD(partParams);
+ COMPARE_LOCATION_FIELD(location);
+
+ return true;
+}
+
+static bool
+_equalPartitionElem(const PartitionElem *a, const PartitionElem *b)
+{
+ COMPARE_STRING_FIELD(name);
+ COMPARE_NODE_FIELD(expr);
+ COMPARE_NODE_FIELD(opclass);
+ COMPARE_LOCATION_FIELD(location);
+
+ return true;
+}
+
/*
* Stuff from pg_list.h
*/
@@ -3338,6 +3360,12 @@ equal(const void *a, const void *b)
case T_RoleSpec:
retval = _equalRoleSpec(a, b);
break;
+ case T_PartitionBy:
+ retval = _equalPartitionBy(a, b);
+ break;
+ case T_PartitionElem:
+ retval = _equalPartitionElem(a, b);
+ break;
default:
elog(ERROR, "unrecognized node type: %d",
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 28d983c..9baf7cd 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2163,6 +2163,7 @@ _outCreateStmtInfo(StringInfo str, const CreateStmt *node)
WRITE_NODE_FIELD(relation);
WRITE_NODE_FIELD(tableElts);
WRITE_NODE_FIELD(inhRelations);
+ WRITE_NODE_FIELD(partitionBy);
WRITE_NODE_FIELD(ofTypename);
WRITE_NODE_FIELD(constraints);
WRITE_NODE_FIELD(options);
@@ -3011,6 +3012,26 @@ _outConstraint(StringInfo str, const Constraint *node)
}
}
+static void
+_outPartitionBy(StringInfo str, const PartitionBy *node)
+{
+ WRITE_NODE_TYPE("PARTITIONBY");
+
+ WRITE_CHAR_FIELD(strategy);
+ WRITE_NODE_FIELD(partParams);
+ WRITE_LOCATION_FIELD(location);
+}
+
+static void
+_outPartitionElem(StringInfo str, const PartitionElem *node)
+{
+ WRITE_NODE_TYPE("PARTITIONELEM");
+
+ WRITE_STRING_FIELD(name);
+ WRITE_NODE_FIELD(expr);
+ WRITE_NODE_FIELD(opclass);
+ WRITE_LOCATION_FIELD(location);
+}
/*
* _outNode -
@@ -3542,6 +3563,12 @@ _outNode(StringInfo str, const void *obj)
case T_XmlSerialize:
_outXmlSerialize(str, obj);
break;
+ case T_PartitionBy:
+ _outPartitionBy(str, obj);
+ break;
+ case T_PartitionElem:
+ _outPartitionElem(str, obj);
+ break;
default:
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index b307b48..6e3471d 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -226,6 +226,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
struct ImportQual *importqual;
InsertStmt *istmt;
VariableSetStmt *vsetstmt;
+ PartitionElem *pelem;
+ PartitionBy *partby;
}
%type <node> stmt schema_stmt
@@ -537,6 +539,10 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
opt_frame_clause frame_extent frame_bound
%type <str> opt_existing_window_name
%type <boolean> opt_if_not_exists
+%type <list> OptPartitionKey PartitionKey
+%type <partby> PartitionBy SubPartitionBy
+%type <pelem> part_elem
+%type <list> part_params
/*
* Non-keyword token types. These are hard-wired into the "flex" lexer.
@@ -601,7 +607,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
KEY
LABEL LANGUAGE LARGE_P LAST_P LATERAL_P
- LEADING LEAKPROOF LEAST LEFT LEVEL LIKE LIMIT LISTEN LOAD LOCAL
+ LEADING LEAKPROOF LEAST LEFT LEVEL LIKE LIMIT LIST LISTEN LOAD LOCAL
LOCALTIME LOCALTIMESTAMP LOCATION LOCK_P LOCKED LOGGED
MAPPING MATCH MATERIALIZED MAXVALUE MINUTE_P MINVALUE MODE MONTH_P MOVE
@@ -627,8 +633,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
SAVEPOINT SCHEMA SCROLL SEARCH SECOND_P SECURITY SELECT SEQUENCE SEQUENCES
SERIALIZABLE SERVER SESSION SESSION_USER SET SETS SETOF SHARE SHOW
SIMILAR SIMPLE SKIP SMALLINT SNAPSHOT SOME SQL_P STABLE STANDALONE_P START
- STATEMENT STATISTICS STDIN STDOUT STORAGE STRICT_P STRIP_P SUBSTRING
- SYMMETRIC SYSID SYSTEM_P
+ STATEMENT STATISTICS STDIN STDOUT STORAGE STRICT_P STRIP_P SUBPARTITION
+ SUBSTRING SYMMETRIC SYSID SYSTEM_P
TABLE TABLES TABLESAMPLE TABLESPACE TEMP TEMPLATE TEMPORARY TEXT_P THEN
TIME TIMESTAMP TO TRAILING TRANSACTION TRANSFORM TREAT TRIGGER TRIM TRUE_P
@@ -2804,35 +2810,37 @@ copy_generic_opt_arg_list_item:
*****************************************************************************/
CreateStmt: CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
- OptInherit OptWith OnCommitOption OptTableSpace
+ OptInherit OptPartitionKey OptWith OnCommitOption OptTableSpace
{
CreateStmt *n = makeNode(CreateStmt);
$4->relpersistence = $2;
n->relation = $4;
n->tableElts = $6;
n->inhRelations = $8;
+ n->partitionBy = $9;
n->ofTypename = NULL;
n->constraints = NIL;
- n->options = $9;
- n->oncommit = $10;
- n->tablespacename = $11;
+ n->options = $10;
+ n->oncommit = $11;
+ n->tablespacename = $12;
n->if_not_exists = false;
$$ = (Node *)n;
}
| CREATE OptTemp TABLE IF_P NOT EXISTS qualified_name '('
- OptTableElementList ')' OptInherit OptWith OnCommitOption
- OptTableSpace
+ OptTableElementList ')' OptInherit OptPartitionKey OptWith
+ OnCommitOption OptTableSpace
{
CreateStmt *n = makeNode(CreateStmt);
$7->relpersistence = $2;
n->relation = $7;
n->tableElts = $9;
n->inhRelations = $11;
+ n->partitionBy = $12;
n->ofTypename = NULL;
n->constraints = NIL;
- n->options = $12;
- n->oncommit = $13;
- n->tablespacename = $14;
+ n->options = $13;
+ n->oncommit = $14;
+ n->tablespacename = $15;
n->if_not_exists = true;
$$ = (Node *)n;
}
@@ -3411,6 +3419,104 @@ OptInherit: INHERITS '(' qualified_name_list ')' { $$ = $3; }
| /*EMPTY*/ { $$ = NIL; }
;
+/* Optional partition key definition */
+OptPartitionKey: PartitionKey { $$ = $1; }
+ | /*EMPTY*/ { $$ = NULL; }
+ ;
+
+PartitionKey: PartitionBy
+ {
+ List *n = list_make1($1);
+
+ $$ = n;
+ }
+ | PartitionKey SubPartitionBy
+ {
+ List *n = lappend($1, $2);
+
+ $$ = n;
+ }
+ ;
+
+PartitionBy: PARTITION BY RANGE ON '(' part_params ')'
+ {
+ PartitionBy *n = makeNode(PartitionBy);
+
+ n->strategy = PARTITION_STRAT_RANGE;
+ n->partParams = $6;
+ n->location = @1;
+
+ $$ = n;
+ }
+ | PARTITION BY LIST ON '(' part_params ')'
+ {
+ PartitionBy *n = makeNode(PartitionBy);
+
+ n->strategy = PARTITION_STRAT_LIST;
+ n->partParams = $6;
+ n->location = @1;
+
+ $$ = n;
+ }
+ ;
+
+SubPartitionBy: SUBPARTITION BY RANGE ON '(' part_params ')'
+ {
+ PartitionBy *n = makeNode(PartitionBy);
+
+ n->strategy = PARTITION_STRAT_RANGE;
+ n->partParams = $6;
+ n->location = @1;
+
+ $$ = n;
+ }
+ | SUBPARTITION BY LIST ON '(' part_params ')'
+ {
+ PartitionBy *n = makeNode(PartitionBy);
+
+ n->strategy = PARTITION_STRAT_LIST;
+ n->partParams = $6;
+ n->location = @1;
+
+ $$ = n;
+ }
+ ;
+
+part_params: part_elem { $$ = list_make1($1); }
+ | part_params ',' part_elem { $$ = lappend($1, $3); }
+ ;
+
+part_elem: ColId opt_class
+ {
+ PartitionElem *n = makeNode(PartitionElem);
+
+ n->name = $1;
+ n->expr = NULL;
+ n->opclass = $2;
+ n->location = @1;
+ $$ = n;
+ }
+ | func_expr_windowless opt_class
+ {
+ PartitionElem *n = makeNode(PartitionElem);
+
+ n->name = NULL;
+ n->expr = $1;
+ n->opclass = $2;
+ n->location = @1;
+ $$ = n;
+ }
+ | '(' a_expr ')' opt_class
+ {
+ PartitionElem *n = makeNode(PartitionElem);
+
+ n->name = NULL;
+ n->expr = $2;
+ n->opclass = $4;
+ n->location = @1;
+ $$ = n;
+ }
+ ;
/* WITH (options) is preferred, WITH OIDS and WITHOUT OIDS are legacy forms */
OptWith:
WITH reloptions { $$ = $2; }
@@ -13767,6 +13873,7 @@ unreserved_keyword:
| LAST_P
| LEAKPROOF
| LEVEL
+ | LIST
| LISTEN
| LOAD
| LOCAL
@@ -13873,6 +13980,7 @@ unreserved_keyword:
| STORAGE
| STRICT_P
| STRIP_P
+ | SUBPARTITION
| SYSID
| SYSTEM_P
| TABLES
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index dc431c7..54efa35 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -205,6 +205,41 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
&& stmt->relation->relpersistence != RELPERSISTENCE_TEMP)
stmt->relation->schemaname = get_namespace_name(namespaceid);
+ if (stmt->partitionBy)
+ {
+ ListCell *cell;
+ int keyidx = 0;
+
+ if (list_length(stmt->partitionBy) > 2)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("specifying more than one SUBPARTITION BY clause"
+ " is not supported")));
+
+ foreach(cell, stmt->partitionBy)
+ {
+ PartitionBy *partby = (PartitionBy *) lfirst(cell);
+ int partnatts = list_length(partby->partParams);
+
+ if (partnatts > PARTITION_MAX_KEYS)
+ ereport(ERROR,
+ (errcode(ERRCODE_TOO_MANY_COLUMNS),
+ errmsg("cannot use more than %d columns in the %s key",
+ PARTITION_MAX_KEYS,
+ keyidx > 0 ? "sub-partition" : "partition")));
+
+ if (partby->strategy == PARTITION_STRAT_LIST &&
+ partnatts > 1)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("cannot use more than one column in the %s key",
+ keyidx > 0 ? "sub-partition" : "partition"),
+ errdetail("Only one column allowed with list partitioning.")));
+
+ keyidx++;
+ }
+ }
+
/* Set up CreateStmtContext */
cxt.pstate = pstate;
if (IsA(stmt, CreateForeignTableStmt))
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index c407fa2..825f7d9 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -433,6 +433,8 @@ typedef enum NodeTag
T_OnConflictClause,
T_CommonTableExpr,
T_RoleSpec,
+ T_PartitionElem,
+ T_PartitionBy,
/*
* TAGS FOR REPLICATION GRAMMAR PARSE NODES (replnodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 2fd0629..130410c 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -696,6 +696,40 @@ typedef struct XmlSerialize
int location; /* token location, or -1 if unknown */
} XmlSerialize;
+/* Partitioning related definitions */
+
+/*
+ * PartitionElem - a partition key column
+ *
+ * 'name' Name of the table column included in the key
+ * 'expr' Expression node tree of expressional key column
+ * 'opclass' Operator class name associated with the column
+ */
+typedef struct PartitionElem
+{
+ NodeTag type;
+ char *name; /* name of column to partition on, or NULL */
+ Node *expr; /* expression to partition on, or NULL */
+ List *opclass; /* name of desired opclass; NIL = default */
+ int location; /* token location, or -1 if unknown */
+} PartitionElem;
+
+/*
+ * PartitionBy - partition key definition including the strategy
+ *
+ * 'strategy' partition strategy to use (one of the below defined)
+ * 'partParams' List of PartitionElems, one for each key column
+ */
+#define PARTITION_STRAT_LIST 'l'
+#define PARTITION_STRAT_RANGE 'r'
+
+typedef struct PartitionBy
+{
+ NodeTag type;
+ char strategy;
+ List *partParams;
+ int location; /* token location, or -1 if unknown */
+} PartitionBy;
/****************************************************************************
* Nodes for a Query tree
@@ -1749,6 +1783,7 @@ typedef struct CreateStmt
List *tableElts; /* column definitions (list of ColumnDef) */
List *inhRelations; /* relations to inherit from (list of
* inhRelation) */
+ List *partitionBy;
TypeName *ofTypename; /* OF typename */
List *constraints; /* constraints (list of Constraint nodes) */
List *options; /* options from WITH clause */
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 6e1e820..38c7afe 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -226,6 +226,7 @@ PG_KEYWORD("left", LEFT, TYPE_FUNC_NAME_KEYWORD)
PG_KEYWORD("level", LEVEL, UNRESERVED_KEYWORD)
PG_KEYWORD("like", LIKE, TYPE_FUNC_NAME_KEYWORD)
PG_KEYWORD("limit", LIMIT, RESERVED_KEYWORD)
+PG_KEYWORD("list", LIST, UNRESERVED_KEYWORD)
PG_KEYWORD("listen", LISTEN, UNRESERVED_KEYWORD)
PG_KEYWORD("load", LOAD, UNRESERVED_KEYWORD)
PG_KEYWORD("local", LOCAL, UNRESERVED_KEYWORD)
@@ -367,6 +368,7 @@ PG_KEYWORD("stdout", STDOUT, UNRESERVED_KEYWORD)
PG_KEYWORD("storage", STORAGE, UNRESERVED_KEYWORD)
PG_KEYWORD("strict", STRICT_P, UNRESERVED_KEYWORD)
PG_KEYWORD("strip", STRIP_P, UNRESERVED_KEYWORD)
+PG_KEYWORD("subpartition", SUBPARTITION, UNRESERVED_KEYWORD)
PG_KEYWORD("substring", SUBSTRING, COL_NAME_KEYWORD)
PG_KEYWORD("symmetric", SYMMETRIC, RESERVED_KEYWORD)
PG_KEYWORD("sysid", SYSID, UNRESERVED_KEYWORD)
diff --git a/src/include/pg_config_manual.h b/src/include/pg_config_manual.h
index ef89521..ac40571 100644
--- a/src/include/pg_config_manual.h
+++ b/src/include/pg_config_manual.h
@@ -46,6 +46,11 @@
#define INDEX_MAX_KEYS 32
/*
+ * Maximum number of columns in a partition key (XXX - arbitrary)
+ */
+#define PARTITION_MAX_KEYS 32
+
+/*
* Set the upper and lower bounds of sequence values.
*/
#define SEQ_MAXVALUE PG_INT64_MAX
--
1.7.1
[text/x-diff] 0004-Infrastructure-for-creation-of-partitioned-tables.patch (79.9K, ../../56C1302D.6070403@lab.ntt.co.jp/5-0004-Infrastructure-for-creation-of-partitioned-tables.patch)
download | inline diff:
From d16c73fe9df84e8fd5c83ac1c31df2976d29dc97 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Mon, 8 Feb 2016 17:06:39 +0900
Subject: [PATCH 04/10] Infrastructure for creation of partitioned tables.
Primary changes include the code to store and retrieve partition key info
to and from the catalog. Other changes are centered around handling special
case behaviors for RELKIND_PARTITIONED_REL relations based on the fact that
no storage objects are allocated for them. Certain SQL features like unique
primary key, exclusion or foreign key constraints cannot be supported right
away.
There are a few special cases around ALTER TABLE, viz. the following:
* Changing tablespace is handled by simply updating the pg_class.reltablespace
* Changing logging is handled by simply updating pg_class.relpersistence
* Dropping or altering type of columns that are part of the partition key
is prevented
* Inheritance and OF type_name for partitioned tables is prevented
Creating indexes on partitioned tables is disallowed. Also, running commands
reindex, vacuum, analyze, cluster essentially ignores them. These two things
follow from the fact that partitioned tables themselves are just logical
entities without any storage.
Partition key(s) are built during the first relcache build of a partitioned
relation and stored in rd_partkey. They are built in the CacheMemoryContext.
Each partition key is represented as a PartitionKey struct and rd_partkey
points to an ordered list of these structs. Partition key expressions (if any)
are built on demand and cached in appropriate PartitionKey struct.
---
src/backend/access/common/reloptions.c | 2 +
src/backend/catalog/Makefile | 2 +-
src/backend/catalog/aclchk.c | 2 +
src/backend/catalog/heap.c | 37 ++-
src/backend/catalog/index.c | 4 +
src/backend/catalog/objectaddress.c | 10 +-
src/backend/catalog/partition.c | 450 ++++++++++++++++++++++++++
src/backend/commands/analyze.c | 13 +-
src/backend/commands/cluster.c | 4 +
src/backend/commands/copy.c | 6 +
src/backend/commands/indexcmds.c | 9 +-
src/backend/commands/lockcmds.c | 2 +-
src/backend/commands/policy.c | 2 +-
src/backend/commands/seclabel.c | 1 +
src/backend/commands/sequence.c | 1 +
src/backend/commands/tablecmds.c | 544 ++++++++++++++++++++++++++++++--
src/backend/commands/trigger.c | 7 +-
src/backend/commands/vacuum.c | 11 +-
src/backend/parser/parse_agg.c | 6 +
src/backend/parser/parse_expr.c | 5 +
src/backend/parser/parse_utilcmd.c | 22 ++
src/backend/rewrite/rewriteDefine.c | 1 +
src/backend/tcop/utility.c | 53 ++--
src/backend/utils/cache/relcache.c | 95 ++++++
src/bin/psql/describe.c | 42 ++-
src/include/catalog/partition.h | 59 ++++
src/include/commands/defrem.h | 2 +
src/include/parser/parse_node.h | 3 +-
src/include/utils/rel.h | 3 +
src/include/utils/relcache.h | 2 +
30 files changed, 1313 insertions(+), 87 deletions(-)
create mode 100644 src/backend/catalog/partition.c
create mode 100644 src/include/catalog/partition.h
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 86b9ae1..d6388fe 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -915,6 +915,7 @@ extractRelOptions(HeapTuple tuple, TupleDesc tupdesc,
case RELKIND_RELATION:
case RELKIND_TOASTVALUE:
case RELKIND_MATVIEW:
+ case RELKIND_PARTITIONED_REL:
options = heap_reloptions(classForm->relkind, datum, false);
break;
case RELKIND_VIEW:
@@ -1365,6 +1366,7 @@ heap_reloptions(char relkind, Datum reloptions, bool validate)
return (bytea *) rdopts;
case RELKIND_RELATION:
case RELKIND_MATVIEW:
+ case RELKIND_PARTITIONED_REL:
return default_reloptions(reloptions, validate, RELOPT_KIND_HEAP);
default:
/* other relkinds are not supported */
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index df30415..ffec071 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -11,7 +11,7 @@ top_builddir = ../../..
include $(top_builddir)/src/Makefile.global
OBJS = catalog.o dependency.o heap.o index.o indexing.o namespace.o aclchk.o \
- objectaccess.o objectaddress.o pg_aggregate.o pg_collation.o \
+ objectaccess.o objectaddress.o partition.o pg_aggregate.o pg_collation.o \
pg_constraint.o pg_conversion.o \
pg_depend.o pg_enum.o pg_inherits.o pg_largeobject.o pg_namespace.o \
pg_operator.o pg_proc.o pg_range.o pg_db_role_setting.o pg_shdepend.o \
diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
index 0f3bc07..a405205 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -751,6 +751,8 @@ objectsInSchemaToOids(GrantObjectType objtype, List *nspnames)
case ACL_OBJECT_RELATION:
objs = getRelationsInNamespace(namespaceId, RELKIND_RELATION);
objects = list_concat(objects, objs);
+ objs = getRelationsInNamespace(namespaceId, RELKIND_PARTITIONED_REL);
+ objects = list_concat(objects, objs);
objs = getRelationsInNamespace(namespaceId, RELKIND_VIEW);
objects = list_concat(objects, objs);
objs = getRelationsInNamespace(namespaceId, RELKIND_MATVIEW);
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 6a4a9d9..1194611 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -41,6 +41,7 @@
#include "catalog/heap.h"
#include "catalog/index.h"
#include "catalog/objectaccess.h"
+#include "catalog/partition.h"
#include "catalog/pg_attrdef.h"
#include "catalog/pg_collation.h"
#include "catalog/pg_constraint.h"
@@ -295,6 +296,15 @@ heap_create(const char *relname,
*/
reltablespace = InvalidOid;
break;
+ case RELKIND_PARTITIONED_REL:
+ /*
+ * A partitioned relation itself does not have storage, but still
+ * needs to have a valid tablespace ID stored in pg_class. It will
+ * be used as the default tablespace for its individual partitions,
+ * which can be overridden.
+ */
+ create_storage = false;
+ break;
case RELKIND_SEQUENCE:
create_storage = true;
@@ -1101,9 +1111,10 @@ heap_create_with_catalog(const char *relname,
{
/* Use binary-upgrade override for pg_class.oid/relfilenode? */
if (IsBinaryUpgrade &&
- (relkind == RELKIND_RELATION || relkind == RELKIND_SEQUENCE ||
- relkind == RELKIND_VIEW || relkind == RELKIND_MATVIEW ||
- relkind == RELKIND_COMPOSITE_TYPE || relkind == RELKIND_FOREIGN_TABLE))
+ (relkind == RELKIND_RELATION || relkind == RELKIND_PARTITIONED_REL ||
+ relkind == RELKIND_SEQUENCE || relkind == RELKIND_VIEW ||
+ relkind == RELKIND_MATVIEW || relkind == RELKIND_COMPOSITE_TYPE ||
+ relkind == RELKIND_FOREIGN_TABLE))
{
if (!OidIsValid(binary_upgrade_next_heap_pg_class_oid))
ereport(ERROR,
@@ -1134,6 +1145,7 @@ heap_create_with_catalog(const char *relname,
switch (relkind)
{
case RELKIND_RELATION:
+ case RELKIND_PARTITIONED_REL:
case RELKIND_VIEW:
case RELKIND_MATVIEW:
case RELKIND_FOREIGN_TABLE:
@@ -1178,6 +1190,7 @@ heap_create_with_catalog(const char *relname,
* such is an implementation detail: toast tables, sequences and indexes.
*/
if (IsUnderPostmaster && (relkind == RELKIND_RELATION ||
+ relkind == RELKIND_PARTITIONED_REL ||
relkind == RELKIND_VIEW ||
relkind == RELKIND_MATVIEW ||
relkind == RELKIND_FOREIGN_TABLE ||
@@ -1353,8 +1366,13 @@ heap_create_with_catalog(const char *relname,
if (relpersistence == RELPERSISTENCE_UNLOGGED)
{
Assert(relkind == RELKIND_RELATION || relkind == RELKIND_MATVIEW ||
- relkind == RELKIND_TOASTVALUE);
- heap_create_init_fork(new_rel_desc);
+ relkind == RELKIND_TOASTVALUE || relkind == RELKIND_PARTITIONED_REL);
+
+ /*
+ * Do not bother creating the init fork for a partitioned relation.
+ */
+ if (relkind != RELKIND_PARTITIONED_REL)
+ heap_create_init_fork(new_rel_desc);
}
/*
@@ -1800,11 +1818,18 @@ heap_drop_with_catalog(Oid relid)
}
/*
+ * If a partitioned table, delete the pg_partitioned_rel tuples.
+ */
+ if (rel->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+ RemovePartitionKeysByRelId(relid);
+
+ /*
* Schedule unlinking of the relation's physical files at commit.
*/
if (rel->rd_rel->relkind != RELKIND_VIEW &&
rel->rd_rel->relkind != RELKIND_COMPOSITE_TYPE &&
- rel->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
+ rel->rd_rel->relkind != RELKIND_FOREIGN_TABLE &&
+ rel->rd_rel->relkind != RELKIND_PARTITIONED_REL)
{
RelationDropStorage(rel);
}
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index a309c44..e93aad4 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -3498,6 +3498,10 @@ reindex_relation(Oid relid, int flags, int options)
*/
rel = heap_open(relid, ShareLock);
+ if (rel->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+ ereport(ERROR,
+ (errmsg("cannot reindex partitioned tables")));
+
toast_relid = rel->rd_rel->reltoastrelid;
/*
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index d2aaa6d..fd3db14 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -1154,7 +1154,8 @@ get_relation_by_qualified_name(ObjectType objtype, List *objname,
RelationGetRelationName(relation))));
break;
case OBJECT_TABLE:
- if (relation->rd_rel->relkind != RELKIND_RELATION)
+ if (relation->rd_rel->relkind != RELKIND_RELATION &&
+ relation->rd_rel->relkind != RELKIND_PARTITIONED_REL)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not a table",
@@ -3186,6 +3187,10 @@ getRelationDescription(StringInfo buffer, Oid relid)
appendStringInfo(buffer, _("table %s"),
relname);
break;
+ case RELKIND_PARTITIONED_REL:
+ appendStringInfo(buffer, _("partitioned table %s"),
+ relname);
+ break;
case RELKIND_INDEX:
appendStringInfo(buffer, _("index %s"),
relname);
@@ -3638,6 +3643,9 @@ getRelationTypeDescription(StringInfo buffer, Oid relid, int32 objectSubId)
case RELKIND_RELATION:
appendStringInfoString(buffer, "table");
break;
+ case RELKIND_PARTITIONED_REL:
+ appendStringInfoString(buffer, "partitioned table");
+ break;
case RELKIND_INDEX:
appendStringInfoString(buffer, "index");
break;
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
new file mode 100644
index 0000000..125f0a1
--- /dev/null
+++ b/src/backend/catalog/partition.c
@@ -0,0 +1,450 @@
+/*-------------------------------------------------------------------------
+ *
+ * partition.c
+ * Partitioning related utility functions.
+ *
+ * Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ * src/backend/utils/misc/partition.c
+ *
+ *-------------------------------------------------------------------------
+*/
+
+#include "postgres.h"
+
+#include "access/heapam.h"
+#include "access/htup_details.h"
+#include "access/nbtree.h"
+#include "catalog/dependency.h"
+#include "catalog/indexing.h"
+#include "catalog/objectaddress.h"
+#include "catalog/partition.h"
+#include "catalog/pg_collation.h"
+#include "catalog/pg_opclass.h"
+#include "catalog/pg_partitioned_rel.h"
+#include "catalog/pg_type.h"
+#include "executor/executor.h"
+#include "miscadmin.h"
+#include "nodes/nodeFuncs.h"
+#include "storage/lmgr.h"
+#include "utils/array.h"
+#include "utils/builtins.h"
+#include "utils/datum.h"
+#include "utils/memutils.h"
+#include "utils/fmgroids.h"
+#include "utils/inval.h"
+#include "utils/lsyscache.h"
+#include "utils/syscache.h"
+
+/*
+ * StorePartitionKey
+ * Store the partition keys of rel into pg_partitioned_rel catalog
+ */
+void
+StorePartitionKey(Relation rel,
+ int16 partlevel,
+ int16 partnatts,
+ AttrNumber *partattrs,
+ List *partexprs,
+ Oid *partOpClassOids,
+ char strategy)
+{
+ int i;
+ int2vector *partkey;
+ oidvector *partopclass;
+ Datum partexprsDatum;
+ Relation pg_partitioned_rel;
+ HeapTuple tuple;
+ Datum values[Natts_pg_partitioned_rel];
+ bool nulls[Natts_pg_partitioned_rel];
+ ObjectAddress myself;
+ ObjectAddress referenced;
+
+ Assert(rel->rd_rel->relkind == RELKIND_PARTITIONED_REL);
+
+ tuple = SearchSysCache2(PARTEDRELIDLEVEL,
+ ObjectIdGetDatum(RelationGetRelid(rel)),
+ partlevel);
+ /* Cannot already exist */
+ Assert(!HeapTupleIsValid(tuple));
+
+ /*
+ * Copy the partition key, opclass info into arrays (should we
+ * make the caller pass them like this to start with?)
+ */
+ partkey = buildint2vector(partattrs, partnatts);
+ partopclass = buildoidvector(partOpClassOids, partnatts);
+
+ /* Convert the partition key expressions (if any) to a text datum */
+ if (partexprs)
+ {
+ char *exprsString;
+
+ exprsString = nodeToString(partexprs);
+ partexprsDatum = CStringGetTextDatum(exprsString);
+ pfree(exprsString);
+ }
+ else
+ partexprsDatum = (Datum) 0;
+
+ pg_partitioned_rel = heap_open(PartitionedRelRelationId, RowExclusiveLock);
+
+ MemSet(nulls, false, sizeof(nulls));
+
+ /* Only this can ever be NULL */
+ if (!partexprsDatum)
+ nulls[Anum_pg_partitioned_rel_partexprs - 1] = true;
+
+ values[Anum_pg_partitioned_rel_partrelid - 1] = ObjectIdGetDatum(RelationGetRelid(rel));
+ values[Anum_pg_partitioned_rel_partlevel - 1] = Int16GetDatum(partlevel);
+ values[Anum_pg_partitioned_rel_partstrat - 1] = CharGetDatum(strategy);
+ values[Anum_pg_partitioned_rel_partnatts - 1] = Int16GetDatum(partnatts);
+ values[Anum_pg_partitioned_rel_partkey - 1] = PointerGetDatum(partkey);
+ values[Anum_pg_partitioned_rel_partclass - 1] = PointerGetDatum(partopclass);
+ values[Anum_pg_partitioned_rel_partexprs - 1] = partexprsDatum;
+
+ tuple = heap_form_tuple(RelationGetDescr(pg_partitioned_rel), values, nulls);
+
+ simple_heap_insert(pg_partitioned_rel, tuple);
+
+ /* Update the indexes on pg_partitioned_rel */
+ CatalogUpdateIndexes(pg_partitioned_rel, tuple);
+
+ /* Make this relation dependent on a few things: */
+ myself.classId = RelationRelationId;
+ myself.objectId = RelationGetRelid(rel);;
+ myself.objectSubId = 0;
+
+ /* Operator class per key column */
+ for (i = 0; i < partnatts; i++)
+ {
+ referenced.classId = OperatorClassRelationId;
+ referenced.objectId = partOpClassOids[i];
+ referenced.objectSubId = 0;
+
+ recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+ }
+
+ /*
+ * Things inside key expressions
+ *
+ * An ugliness: normal dependencies also created on attribute references
+ * as part of the following.
+ */
+ if (partexprs)
+ recordDependencyOnSingleRelExpr(&myself,
+ (Node *) partexprs,
+ RelationGetRelid(rel),
+ DEPENDENCY_NORMAL,
+ DEPENDENCY_NORMAL);
+ /* Tell world about the key */
+ CacheInvalidateRelcache(rel);
+
+ heap_close(pg_partitioned_rel, RowExclusiveLock);
+ heap_freetuple(tuple);
+}
+
+/*
+ * RemovePartitionKeysByRelId
+ * Remove pg_partitioned_rel entries for a relation
+ */
+void
+RemovePartitionKeysByRelId(Oid relid)
+{
+ Relation pkeyrel;
+ SysScanDesc scan;
+ ScanKeyData key;
+ HeapTuple tuple;
+
+ pkeyrel = heap_open(PartitionedRelRelationId, RowExclusiveLock);
+
+ ScanKeyInit(&key,
+ Anum_pg_partitioned_rel_partrelid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(relid));
+
+ scan = systable_beginscan(pkeyrel, PartitionedRelRelidIndexId, true,
+ NULL, 1, &key);
+
+ while (HeapTupleIsValid(tuple = systable_getnext(scan)))
+ simple_heap_delete(pkeyrel, &tuple->t_self);
+
+ systable_endscan(scan);
+ heap_close(pkeyrel, RowExclusiveLock);
+}
+
+/*
+ * relid_is_partitioned
+ * Returns whether relation with OID relid is partitioned
+ */
+bool
+relid_is_partitioned(Oid relid)
+{
+ HeapTuple tuple;
+ char relkind;
+
+ tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relid));
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "cache lookup failed for relation %u", relid);
+
+ relkind = ((Form_pg_class) GETSTRUCT(tuple))->relkind;
+ ReleaseSysCache(tuple);
+
+ return relkind == RELKIND_PARTITIONED_REL;
+}
+
+/*
+ * RelationBuildPartitionKeys
+ * Build and attach to relcache partition key data of relation
+ *
+ * Note that the partition key data attached to a relcache entry must be
+ * stored CacheMemoryContext to ensure it survives as long as the relcache
+ * entry. But we should be running in a less long-lived working context.
+ * To avoid leaking cache memory if this routine fails partway through,
+ * we build in working memory and then copy the completed structure into
+ * cache memory.
+ */
+void
+RelationBuildPartitionKeys(Relation relation)
+{
+ List *keys = NIL;
+ HeapTuple tuple;
+ bool isnull;
+ int level, i;
+ MemoryContext oldcxt = CurrentMemoryContext;
+
+ /*
+ * Loop through the partition keys for this relation, if any.
+ */
+ for (level = 1; ; ++level)
+ {
+ Relation catalog;
+ Form_pg_partitioned_rel form;
+ PartitionKey key;
+ int2vector *partattrs;
+ oidvector *opclass;
+ Datum datum;
+
+ tuple = SearchSysCache2(PARTEDRELIDLEVEL,
+ ObjectIdGetDatum(RelationGetRelid(relation)),
+ Int16GetDatum(level));
+
+ if (!HeapTupleIsValid(tuple))
+ break;
+
+ form = (Form_pg_partitioned_rel) GETSTRUCT(tuple);
+
+ /* Allocate in the hopefully short-lived working context */
+ key = (PartitionKey) palloc0(sizeof(PartitionKeyData));
+ key->strategy = form->partstrat;
+ key->level = form->partlevel;
+ key->partnatts = form->partnatts;
+
+ /* Open the catalog for its tuple descriptor */
+ catalog = heap_open(PartitionedRelRelationId, AccessShareLock);
+ datum = fastgetattr(tuple, Anum_pg_partitioned_rel_partkey,
+ RelationGetDescr(catalog),
+ &isnull);
+ Assert(!isnull);
+ partattrs = (int2vector *) DatumGetPointer(datum);
+
+ datum = fastgetattr(tuple, Anum_pg_partitioned_rel_partclass,
+ RelationGetDescr(catalog),
+ &isnull);
+ Assert(!isnull);
+ opclass = (oidvector *) DatumGetPointer(datum);
+
+ key->partattrs = (AttrNumber *)
+ palloc0(key->partnatts * sizeof(AttrNumber));
+ key->partopfamily = (Oid *) palloc0(key->partnatts * sizeof(Oid));
+ key->partsupfunc = (FmgrInfo *)
+ palloc0(key->partnatts * sizeof(FmgrInfo));
+
+ /*
+ * Copy partattrs. Further, determine and store the opfamily and
+ * support function per attribute.
+ */
+ for (i = 0; i < key->partnatts; i++)
+ {
+ HeapTuple tuple;
+ Form_pg_opclass form;
+ Oid funcid;
+
+ key->partattrs[i] = partattrs->values[i];
+
+ tuple = SearchSysCache(CLAOID,
+ ObjectIdGetDatum(opclass->values[i]),
+ 0, 0, 0);
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "cache lookup failed for opclass %u",
+ opclass->values[i]);
+
+ form = (Form_pg_opclass) GETSTRUCT(tuple);
+
+ key->partopfamily[i] = form->opcfamily;
+
+ /*
+ * Btree support function is good to cover the list and range
+ * partitionig related needs.
+ */
+ funcid = get_opfamily_proc(form->opcfamily,
+ form->opcintype, form->opcintype,
+ BTORDER_PROC);
+
+ fmgr_info(funcid, &key->partsupfunc[i]);
+ ReleaseSysCache(tuple);
+ }
+
+ /* partexprs are built and copied here as and when necessary */
+ key->partexprs = NIL;
+
+ keys = lappend(keys, key);
+
+ ReleaseSysCache(tuple);
+ heap_close(catalog, AccessShareLock);
+ }
+
+ /* Success --- now copy to the cache memory */
+ oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
+ relation->rd_partkeys = CopyPartitionKeys(keys);
+ MemoryContextSwitchTo(oldcxt);
+
+ FreePartitionKeys(keys);
+}
+
+/*
+ * CopyPartitionKeys
+ *
+ * The copy is allocated in the current memory context.
+ */
+List *
+CopyPartitionKeys(List *keys)
+{
+ ListCell *cell;
+ List *result = NIL;
+
+ foreach (cell, keys)
+ {
+ PartitionKey fromkey = lfirst(cell);
+ PartitionKey newkey = (PartitionKey)
+ palloc0(sizeof(PartitionKeyData));
+
+ newkey->strategy = fromkey->strategy;
+ newkey->level = fromkey->level;
+ newkey->partnatts = fromkey->partnatts;
+
+ newkey->partattrs = (AttrNumber *)
+ palloc(newkey->partnatts * sizeof(AttrNumber));
+ memcpy(newkey->partattrs, fromkey->partattrs,
+ newkey->partnatts * sizeof(AttrNumber));
+
+ newkey->partopfamily = (Oid *)
+ palloc(newkey->partnatts * sizeof(Oid));
+ memcpy(newkey->partopfamily, fromkey->partopfamily,
+ newkey->partnatts * sizeof(Oid));
+
+ newkey->partsupfunc = (FmgrInfo *)
+ palloc(newkey->partnatts * sizeof(FmgrInfo));
+ memcpy(newkey->partsupfunc, fromkey->partsupfunc,
+ newkey->partnatts * sizeof(FmgrInfo));
+
+ result = lappend(result, newkey);
+ }
+
+ return result;
+}
+
+/*
+ * FreePartitionKeys
+ */
+void
+FreePartitionKeys(List *keys)
+{
+ ListCell *cell;
+
+ if (keys == NIL)
+ return;
+
+ foreach (cell, keys)
+ {
+ PartitionKey key = lfirst(cell);
+
+ pfree(key->partattrs);
+ pfree(key->partopfamily);
+ pfree(key->partsupfunc);
+ pfree(key);
+ }
+ pfree(keys);
+}
+
+/*
+ * get_partition_key_type_info
+ *
+ * Returns a PartitionKeyTypeInfo object that includes type info
+ * for individual partition key attributes.
+ *
+ * Separated out to avoid repeating the code.
+ */
+PartitionKeyTypeInfo *
+get_partition_key_type_info(Relation rel, PartitionKey key)
+{
+ int i;
+ List *partexprs;
+ ListCell *partexprs_item;
+ PartitionKeyTypeInfo *result;
+
+ partexprs = RelationGetPartitionExpr(rel, key->level);
+
+ result = (PartitionKeyTypeInfo *) palloc0(sizeof(PartitionKeyTypeInfo));
+ result->typid = (Oid *) palloc0(key->partnatts * sizeof(Oid));
+ result->typmod = (int32 *) palloc0(key->partnatts * sizeof(int32));
+ result->typlen = (int16 *) palloc0(key->partnatts * sizeof(int16));
+ result->typbyval = (bool *) palloc0(key->partnatts * sizeof(bool));
+ result->typalign = (char *) palloc0(key->partnatts * sizeof(bool));
+
+ partexprs_item = list_head(partexprs);
+ for (i = 0; i < key->partnatts; i++)
+ {
+ AttrNumber attno = key->partattrs[i];
+
+ if (attno != InvalidAttrNumber)
+ {
+ result->typid[i] = rel->rd_att->attrs[attno - 1]->atttypid;
+ result->typmod[i] = rel->rd_att->attrs[attno - 1]->atttypmod;
+ }
+ else
+ {
+ result->typid[i] = exprType(lfirst(partexprs_item));
+ result->typmod[i] = exprTypmod(lfirst(partexprs_item));
+ partexprs_item = lnext(partexprs_item);
+ }
+ get_typlenbyvalalign(result->typid[i],
+ &result->typlen[i],
+ &result->typbyval[i],
+ &result->typalign[i]);
+ }
+
+ if (partexprs)
+ pfree(partexprs);
+
+ return result;
+}
+
+/*
+ * free_key_type_info
+ */
+void
+free_partition_key_type_info(PartitionKeyTypeInfo *typinfo)
+{
+ Assert(typinfo != NULL);
+
+ pfree(typinfo->typid);
+ pfree(typinfo->typmod);
+ pfree(typinfo->typlen);
+ pfree(typinfo->typbyval);
+ pfree(typinfo->typalign);
+ pfree(typinfo);
+}
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index 070df29..e412c68 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -237,9 +237,16 @@ analyze_rel(Oid relid, RangeVar *relation, int options,
{
/* No need for a WARNING if we already complained during VACUUM */
if (!(options & VACOPT_VACUUM))
- ereport(WARNING,
- (errmsg("skipping \"%s\" --- cannot analyze non-tables or special system tables",
- RelationGetRelationName(onerel))));
+ {
+ if (onerel->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+ ereport(WARNING,
+ (errmsg("skipping \"%s\" --- cannot analyze partitioned tables",
+ RelationGetRelationName(onerel))));
+ else
+ ereport(WARNING,
+ (errmsg("skipping \"%s\" --- cannot analyze non-tables or special system tables",
+ RelationGetRelationName(onerel))));
+ }
relation_close(onerel, ShareUpdateExclusiveLock);
return;
}
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 5cb28cf..25398ae 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -128,6 +128,10 @@ cluster(ClusterStmt *stmt, bool isTopLevel)
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot cluster temporary tables of other sessions")));
+ if (rel->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+ ereport(ERROR,
+ (errmsg("cannot cluster partitioned tables")));
+
if (stmt->indexname == NULL)
{
ListCell *index;
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 3201476..403738d 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -1718,6 +1718,12 @@ BeginCopyTo(Relation rel,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot copy from sequence \"%s\"",
RelationGetRelationName(rel))));
+ else if (rel->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot copy from partitioned table \"%s\"",
+ RelationGetRelationName(rel)),
+ errhint("Try the COPY (SELECT ...) TO variant.")));
else
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 13b04e6..ca02c91 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -67,8 +67,6 @@ static void ComputeIndexAttrs(IndexInfo *indexInfo,
char *accessMethodName, Oid accessMethodId,
bool amcanorder,
bool isconstraint);
-static Oid GetIndexOpClass(List *opclass, Oid attrType,
- char *accessMethodName, Oid accessMethodId);
static char *ChooseIndexName(const char *tabname, Oid namespaceId,
List *colnames, List *exclusionOpNames,
bool primary, bool isconstraint);
@@ -381,6 +379,11 @@ DefineIndex(Oid relationId,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot create index on foreign table \"%s\"",
RelationGetRelationName(rel))));
+ else if (rel->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot create index on partitioned table \"%s\"",
+ RelationGetRelationName(rel))));
else
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
@@ -1219,7 +1222,7 @@ ComputeIndexAttrs(IndexInfo *indexInfo,
/*
* Resolve possibly-defaulted operator class specification
*/
-static Oid
+Oid
GetIndexOpClass(List *opclass, Oid attrType,
char *accessMethodName, Oid accessMethodId)
{
diff --git a/src/backend/commands/lockcmds.c b/src/backend/commands/lockcmds.c
index 175d1f3..62671a8 100644
--- a/src/backend/commands/lockcmds.c
+++ b/src/backend/commands/lockcmds.c
@@ -88,7 +88,7 @@ RangeVarCallbackForLockTable(const RangeVar *rv, Oid relid, Oid oldrelid,
* check */
/* Currently, we only allow plain tables to be locked */
- if (relkind != RELKIND_RELATION)
+ if (relkind != RELKIND_RELATION && relkind != RELKIND_PARTITIONED_REL)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not a table",
diff --git a/src/backend/commands/policy.c b/src/backend/commands/policy.c
index bb735ac..3f77694 100644
--- a/src/backend/commands/policy.c
+++ b/src/backend/commands/policy.c
@@ -88,7 +88,7 @@ RangeVarCallbackForPolicy(const RangeVar *rv, Oid relid, Oid oldrelid,
rv->relname)));
/* Relation type MUST be a table. */
- if (relkind != RELKIND_RELATION)
+ if (relkind != RELKIND_RELATION && relkind != RELKIND_PARTITIONED_REL)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not a table", rv->relname)));
diff --git a/src/backend/commands/seclabel.c b/src/backend/commands/seclabel.c
index 5bd7e12..8bc8638 100644
--- a/src/backend/commands/seclabel.c
+++ b/src/backend/commands/seclabel.c
@@ -107,6 +107,7 @@ ExecSecLabelStmt(SecLabelStmt *stmt)
* are the only relkinds for which pg_dump will dump labels).
*/
if (relation->rd_rel->relkind != RELKIND_RELATION &&
+ relation->rd_rel->relkind != RELKIND_PARTITIONED_REL &&
relation->rd_rel->relkind != RELKIND_VIEW &&
relation->rd_rel->relkind != RELKIND_MATVIEW &&
relation->rd_rel->relkind != RELKIND_COMPOSITE_TYPE &&
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index c98f981..5dcd4cf 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -1467,6 +1467,7 @@ process_owned_by(Relation seqrel, List *owned_by)
/* Must be a regular or foreign table */
if (!(tablerel->rd_rel->relkind == RELKIND_RELATION ||
+ tablerel->rd_rel->relkind == RELKIND_PARTITIONED_REL ||
tablerel->rd_rel->relkind == RELKIND_FOREIGN_TABLE))
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 96dc923..674fbe5 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -262,6 +262,12 @@ struct DropRelationCallbackState
bool concurrent;
};
+/* for find_attr_reference_walker */
+typedef struct
+{
+ AttrNumber attnum;
+} find_attr_reference_context;
+
/* Alter table target-type flags for ATSimplePermissions */
#define ATT_TABLE 0x0001
#define ATT_VIEW 0x0002
@@ -433,6 +439,20 @@ static void RangeVarCallbackForDropRelation(const RangeVar *rel, Oid relOid,
Oid oldRelOid, void *arg);
static void RangeVarCallbackForAlterRelation(const RangeVar *rv, Oid relid,
Oid oldrelid, void *arg);
+static PartitionBy *transformPartitionBy(Relation rel, PartitionBy *partitionby);
+static void ComputePartitionAttrs(Oid relid,
+ List *partParams,
+ AttrNumber *partattrs,
+ List **partexprs,
+ Oid *partOpClassOids);
+static bool find_attr_reference_walker(Node *node,
+ find_attr_reference_context *context);
+static bool is_partition_attr(Relation rel, AttrNumber attnum, bool *is_expr);
+static void SetRelationSpcFileNode(Relation rel, Oid newTableSpace,
+ Oid newFileNode);
+static void ATExecChangePersistence(List **wqueue, AlteredTableInfo *tab,
+ Relation rel, char persistence, LOCKMODE lockmode);
+static void SetRelationPersistence(Oid relationId, char persistence);
/* ----------------------------------------------------------------
@@ -476,6 +496,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
static char *validnsps[] = HEAP_RELOPT_NAMESPACES;
Oid ofTypeId;
ObjectAddress address;
+ int16 keyindex;
/*
* Truncate relname to appropriate length (probably a waste of time, as
@@ -596,7 +617,8 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
* affect other relkinds, but it would complicate interpretOidsOption().
*/
localHasOids = interpretOidsOption(stmt->options,
- (relkind == RELKIND_RELATION));
+ (relkind == RELKIND_RELATION ||
+ relkind == RELKIND_PARTITIONED_REL));
descriptor->tdhasoid = (localHasOids || parentOidCount > 0);
/*
@@ -710,6 +732,28 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
AddRelationNewConstraints(rel, rawDefaults, stmt->constraints,
true, true, false);
+ /* Store partition key information into the catalog */
+ keyindex = 1;
+ foreach(listptr, stmt->partitionBy)
+ {
+ PartitionBy *partby = lfirst(listptr);
+ int16 partnatts;
+ AttrNumber partattrs[PARTITION_MAX_KEYS];
+ List *partexprs = NIL;
+ Oid partOpClassOids[PARTITION_MAX_KEYS];
+
+ partnatts = list_length(partby->partParams);
+ partby = transformPartitionBy(rel, partby);
+
+ ComputePartitionAttrs(relationId, partby->partParams,
+ partattrs,
+ &partexprs,
+ partOpClassOids);
+
+ StorePartitionKey(rel, keyindex++, partnatts, partattrs,
+ partexprs, partOpClassOids, partby->strategy);
+ }
+
ObjectAddressSet(address, RelationRelationId, relationId);
/*
@@ -955,7 +999,15 @@ RangeVarCallbackForDropRelation(const RangeVar *rel, Oid relOid, Oid oldRelOid,
return; /* concurrently dropped, so nothing to do */
classform = (Form_pg_class) GETSTRUCT(tuple);
- if (classform->relkind != relkind)
+ /*
+ * Both normal and partitioned tables are dropped using DROP TABLE.
+ * RemoveRelations however never passes RELKIND_PARTITIONED_REL as
+ * relkind for OBJECT_TABLE relations. A mismatch below may have
+ * to do with that. So, check.
+ */
+ if (classform->relkind != relkind &&
+ (relkind == RELKIND_RELATION &&
+ classform->relkind != RELKIND_PARTITIONED_REL))
DropErrorMsgWrongType(rel->relname, classform->relkind, relkind);
/* Allow DROP to either table owner or schema owner */
@@ -1521,6 +1573,13 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
*/
relation = heap_openrv(parent, ShareUpdateExclusiveLock);
+ /* Cannot inherit from partitioned tables */
+ if (relation->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot inherit from partitioned table \"%s\"",
+ parent->relname)));
+
if (relation->rd_rel->relkind != RELKIND_RELATION &&
relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
ereport(ERROR,
@@ -2162,6 +2221,7 @@ renameatt_check(Oid myrelid, Form_pg_class classform, bool recursing)
* restriction.
*/
if (relkind != RELKIND_RELATION &&
+ relkind != RELKIND_PARTITIONED_REL &&
relkind != RELKIND_VIEW &&
relkind != RELKIND_MATVIEW &&
relkind != RELKIND_COMPOSITE_TYPE &&
@@ -3577,7 +3637,11 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
ATExecDropCluster(rel, lockmode);
break;
case AT_SetLogged: /* SET LOGGED */
+ ATExecChangePersistence(wqueue, tab, rel,
+ RELPERSISTENCE_PERMANENT, lockmode);
case AT_SetUnLogged: /* SET UNLOGGED */
+ ATExecChangePersistence(wqueue, tab, rel,
+ RELPERSISTENCE_UNLOGGED, lockmode);
break;
case AT_AddOids: /* SET WITH OIDS */
/* Use the ADD COLUMN code, unless prep decided to do nothing */
@@ -3724,8 +3788,10 @@ ATRewriteTables(AlterTableStmt *parsetree, List **wqueue, LOCKMODE lockmode)
{
AlteredTableInfo *tab = (AlteredTableInfo *) lfirst(ltab);
- /* Foreign tables have no storage. */
- if (tab->relkind == RELKIND_FOREIGN_TABLE)
+ /* Foreign tables and partitioned tables have no storage. */
+ if (tab->relkind == RELKIND_FOREIGN_TABLE ||
+ (tab->relkind == RELKIND_PARTITIONED_REL &&
+ tab->newTableSpace == InvalidOid))
continue;
/*
@@ -4291,6 +4357,7 @@ ATSimplePermissions(Relation rel, int allowed_targets)
switch (rel->rd_rel->relkind)
{
case RELKIND_RELATION:
+ case RELKIND_PARTITIONED_REL:
actual_target = ATT_TABLE;
break;
case RELKIND_VIEW:
@@ -4527,6 +4594,7 @@ find_composite_type_dependencies(Oid typeOid, Relation origRelation,
att = rel->rd_att->attrs[pg_depend->objsubid - 1];
if (rel->rd_rel->relkind == RELKIND_RELATION ||
+ rel->rd_rel->relkind == RELKIND_PARTITIONED_REL ||
rel->rd_rel->relkind == RELKIND_MATVIEW)
{
if (origTypeName)
@@ -5417,6 +5485,7 @@ ATPrepSetStatistics(Relation rel, const char *colName, Node *newValue, LOCKMODE
* allowSystemTableMods to be turned on.
*/
if (rel->rd_rel->relkind != RELKIND_RELATION &&
+ rel->rd_rel->relkind != RELKIND_PARTITIONED_REL &&
rel->rd_rel->relkind != RELKIND_MATVIEW &&
rel->rd_rel->relkind != RELKIND_INDEX &&
rel->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
@@ -5691,6 +5760,84 @@ ATPrepDropColumn(List **wqueue, Relation rel, bool recurse, bool recursing,
cmd->subtype = AT_DropColumnRecurse;
}
+/* Checks if a Var node is for a given attnum */
+static bool
+find_attr_reference_walker(Node *node, find_attr_reference_context *context)
+{
+ if (node == NULL)
+ return false;
+
+ if (IsA(node, Var))
+ {
+ Var *variable = (Var *) node;
+ AttrNumber attnum = variable->varattno;
+
+ if (attnum == context->attnum)
+ return true;
+ }
+
+ return expression_tree_walker(node, find_attr_reference_walker, context);
+}
+
+/*
+ * Checks if attnum is a partition attribute for rel
+ *
+ * Sets *is_expr if attnum is found to be referenced in some partition key
+ * expression.
+ */
+static bool
+is_partition_attr(Relation rel, AttrNumber attnum, bool *is_expr)
+{
+ List *keys = rel->rd_partkeys;
+ ListCell *cell;
+
+ if (rel->rd_rel->relkind != RELKIND_PARTITIONED_REL)
+ {
+ *is_expr = false;
+ return false;
+ }
+
+ foreach(cell, keys)
+ {
+ PartitionKey key = lfirst(cell);
+ int16 level = key->level;
+ int partnatts = key->partnatts;
+ AttrNumber *partattrs = key->partattrs;
+ List *partexprs = RelationGetPartitionExpr(rel, level);
+ ListCell *partexpr_item;
+ int i;
+
+ partexpr_item = list_head(partexprs);
+ for (i = 0; i < partnatts; i++)
+ {
+ AttrNumber partatt = partattrs[i];
+
+ if(partatt != 0)
+ {
+ *is_expr = false;
+ if (attnum == partatt)
+ return true;
+ }
+ else
+ {
+ find_attr_reference_context context;
+
+ *is_expr = true;
+ context.attnum = attnum;
+ if (find_attr_reference_walker(lfirst(partexpr_item), &context))
+ return true;
+
+ partexpr_item = lnext(partexpr_item);
+ }
+ }
+
+ if (partexprs)
+ pfree(partexprs);
+ }
+
+ return false;
+}
+
/*
* Return value is the address of the dropped column.
*/
@@ -5705,6 +5852,7 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
AttrNumber attnum;
List *children;
ObjectAddress object;
+ bool is_expr;
/* At top level, permission check was done in ATPrepCmd, else do it */
if (recursing)
@@ -5749,6 +5897,19 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
errmsg("cannot drop inherited column \"%s\"",
colName)));
+ /* Don't drop columns used in partition key */
+ if (is_partition_attr(rel, attnum, &is_expr))
+ {
+ if (!is_expr)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("cannot drop column named in partition key")));
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("cannot drop column referenced in partition key expressions")));
+ }
+
ReleaseSysCache(tuple);
/*
@@ -6267,6 +6428,12 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
* Validity checks (permission checks wait till we have the column
* numbers)
*/
+ if (pkrel->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot reference relation \"%s\"", RelationGetRelationName(pkrel)),
+ errdetail("Referencing partitioned tables in foreign key constraints is not supported.")));
+
if (pkrel->rd_rel->relkind != RELKIND_RELATION)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
@@ -7319,8 +7486,12 @@ validateCheckConstraint(Relation rel, HeapTuple constrtup)
bool isnull;
Snapshot snapshot;
- /* VALIDATE CONSTRAINT is a no-op for foreign tables */
- if (rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
+ /*
+ * VALIDATE CONSTRAINT is a no-op for foreign tables and partitioned
+ * tables.
+ */
+ if (rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE ||
+ rel->rd_rel->relkind == RELKIND_PARTITIONED_REL)
return;
constrForm = (Form_pg_constraint) GETSTRUCT(constrtup);
@@ -7861,6 +8032,7 @@ ATPrepAlterColumnType(List **wqueue,
NewColumnValue *newval;
ParseState *pstate = make_parsestate(NULL);
AclResult aclresult;
+ bool is_expr;
if (rel->rd_rel->reloftype && !recursing)
ereport(ERROR,
@@ -7891,6 +8063,19 @@ ATPrepAlterColumnType(List **wqueue,
errmsg("cannot alter inherited column \"%s\"",
colName)));
+ /* Don't alter columns used in partition key */
+ if (is_partition_attr(rel, attnum, &is_expr))
+ {
+ if (!is_expr)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("cannot drop column named in partition key")));
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("cannot drop column referenced in partition key expressions")));
+ }
+
/* Look up the target type */
typenameTypeIdAndMod(NULL, typeName, &targettype, &targettypmod);
@@ -7906,7 +8091,8 @@ ATPrepAlterColumnType(List **wqueue,
list_make1_oid(rel->rd_rel->reltype),
false);
- if (tab->relkind == RELKIND_RELATION)
+ if (tab->relkind == RELKIND_RELATION ||
+ tab->relkind == RELKIND_PARTITIONED_REL)
{
/*
* Set up an expression to transform the old data value to the new
@@ -8933,6 +9119,7 @@ ATExecChangeOwner(Oid relationOid, Oid newOwnerId, bool recursing, LOCKMODE lock
switch (tuple_class->relkind)
{
case RELKIND_RELATION:
+ case RELKIND_PARTITIONED_REL:
case RELKIND_VIEW:
case RELKIND_MATVIEW:
case RELKIND_FOREIGN_TABLE:
@@ -9395,6 +9582,7 @@ ATExecSetRelOptions(Relation rel, List *defList, AlterTableType operation,
switch (rel->rd_rel->relkind)
{
case RELKIND_RELATION:
+ case RELKIND_PARTITIONED_REL:
case RELKIND_TOASTVALUE:
case RELKIND_MATVIEW:
(void) heap_reloptions(rel->rd_rel->relkind, newOptions, true);
@@ -9554,9 +9742,6 @@ ATExecSetTableSpace(Oid tableOid, Oid newTableSpace, LOCKMODE lockmode)
Oid newrelfilenode;
RelFileNode newrnode;
SMgrRelation dstrel;
- Relation pg_class;
- HeapTuple tuple;
- Form_pg_class rd_rel;
ForkNumber forkNum;
List *reltoastidxids = NIL;
ListCell *lc;
@@ -9605,6 +9790,18 @@ ATExecSetTableSpace(Oid tableOid, Oid newTableSpace, LOCKMODE lockmode)
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot move temporary tables of other sessions")));
+ /* For partitioned tables, just set the new tablespace in pg_class */
+ if(rel->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+ {
+ SetRelationSpcFileNode(rel, newTableSpace, rel->rd_rel->relfilenode);
+ relation_close(rel, lockmode);
+
+ /* Make sure the reltablespace change is visible */
+ CommandCounterIncrement();
+
+ return;
+ }
+
reltoastrelid = rel->rd_rel->reltoastrelid;
/* Fetch the list of indexes on toast relation if necessary */
if (OidIsValid(reltoastrelid))
@@ -9615,14 +9812,6 @@ ATExecSetTableSpace(Oid tableOid, Oid newTableSpace, LOCKMODE lockmode)
relation_close(toastRel, lockmode);
}
- /* Get a modifiable copy of the relation's pg_class row */
- pg_class = heap_open(RelationRelationId, RowExclusiveLock);
-
- tuple = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(tableOid));
- if (!HeapTupleIsValid(tuple))
- elog(ERROR, "cache lookup failed for relation %u", tableOid);
- rd_rel = (Form_pg_class) GETSTRUCT(tuple);
-
/*
* Since we copy the file directly without looking at the shared buffers,
* we'd better first flush out any pages of the source relation that are
@@ -9683,18 +9872,11 @@ ATExecSetTableSpace(Oid tableOid, Oid newTableSpace, LOCKMODE lockmode)
RelationDropStorage(rel);
smgrclose(dstrel);
- /* update the pg_class row */
- rd_rel->reltablespace = (newTableSpace == MyDatabaseTableSpace) ? InvalidOid : newTableSpace;
- rd_rel->relfilenode = newrelfilenode;
- simple_heap_update(pg_class, &tuple->t_self, tuple);
- CatalogUpdateIndexes(pg_class, tuple);
+ /* Now update pg_class.reltablespace */
+ SetRelationSpcFileNode(rel, newTableSpace, newrelfilenode);
InvokeObjectPostAlterHook(RelationRelationId, RelationGetRelid(rel), 0);
- heap_freetuple(tuple);
-
- heap_close(pg_class, RowExclusiveLock);
-
relation_close(rel, NoLock);
/* Make sure the reltablespace change is visible */
@@ -9711,6 +9893,37 @@ ATExecSetTableSpace(Oid tableOid, Oid newTableSpace, LOCKMODE lockmode)
}
/*
+ * SetRelationTablespace - updates rel's reltablespace.
+ */
+static void
+SetRelationSpcFileNode(Relation rel, Oid newTableSpace, Oid newFileNode)
+{
+ Relation pg_class;
+ HeapTuple tuple;
+ Form_pg_class rd_rel;
+
+ /* Get a modifiable copy of the relation's pg_class row */
+ pg_class = heap_open(RelationRelationId, RowExclusiveLock);
+
+ tuple = SearchSysCacheCopy1(RELOID,
+ ObjectIdGetDatum(RelationGetRelid(rel)));
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "cache lookup failed for relation %u",
+ RelationGetRelid(rel));
+ rd_rel = (Form_pg_class) GETSTRUCT(tuple);
+
+ /* update the pg_class row */
+ rd_rel->reltablespace = (newTableSpace == MyDatabaseTableSpace)
+ ? InvalidOid : newTableSpace;
+ rd_rel->relfilenode = newFileNode;
+ simple_heap_update(pg_class, &tuple->t_self, tuple);
+ CatalogUpdateIndexes(pg_class, tuple);
+
+ heap_freetuple(tuple);
+ heap_close(pg_class, RowExclusiveLock);
+}
+
+/*
* Alter Table ALL ... SET TABLESPACE
*
* Allows a user to move all objects of some type in a given tablespace in the
@@ -9817,7 +10030,8 @@ AlterTableMoveAll(AlterTableMoveAllStmt *stmt)
/* Only move the object type requested */
if ((stmt->objtype == OBJECT_TABLE &&
- relForm->relkind != RELKIND_RELATION) ||
+ relForm->relkind != RELKIND_RELATION &&
+ relForm->relkind != RELKIND_PARTITIONED_REL) ||
(stmt->objtype == OBJECT_INDEX &&
relForm->relkind != RELKIND_INDEX) ||
(stmt->objtype == OBJECT_MATVIEW &&
@@ -10016,6 +10230,11 @@ ATPrepAddInherit(Relation child_rel)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot change inheritance of typed table")));
+
+ if (child_rel->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot change inheritance of partitioned table")));
}
/*
@@ -10067,6 +10286,12 @@ ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode)
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot inherit to temporary relation of another session")));
+ /* Prevent partitioned tables from becoming inheritance parents */
+ if (parent_rel->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot inherit from partitioned table \"%s\"", parent->relname)));
+
/*
* Check for duplicates in the list of parents, and determine the highest
* inhseqno already present; we'll use the next one for the new parent.
@@ -10702,6 +10927,11 @@ ATExecAddOf(Relation rel, const TypeName *ofTypename, LOCKMODE lockmode)
typeobj;
HeapTuple classtuple;
+ if (rel->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot attach a partitioned table to composite type")));
+
/* Validate the type. */
typetuple = typenameType(NULL, ofTypename, NULL);
check_of_type(typetuple);
@@ -10971,6 +11201,11 @@ ATExecReplicaIdentity(Relation rel, ReplicaIdentityStmt *stmt, LOCKMODE lockmode
Relation indexRel;
int key;
+ if (rel->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("cannot set replica identity for partitioned tables")));
+
if (stmt->identity_type == REPLICA_IDENTITY_DEFAULT)
{
relation_mark_replica_identity(rel, stmt->identity_type, InvalidOid, true);
@@ -11343,6 +11578,55 @@ ATPrepChangePersistence(Relation rel, bool toLogged)
}
/*
+ * ATExecChangePersistence - change pg_class.relpersistence of rel
+ */
+static void
+ATExecChangePersistence(List **wqueue, AlteredTableInfo *tab, Relation rel,
+ char persistence, LOCKMODE lockmode)
+{
+ /* Phase 3 does not handle partitioned tables, so do this here. */
+ if (rel->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+ SetRelationPersistence(RelationGetRelid(rel), persistence);
+}
+
+/*
+ * SetRelationPersistence - set relpersistence
+ */
+static void
+SetRelationPersistence(Oid relationId, char persistence)
+{
+ Relation relationRelation;
+ HeapTuple tuple;
+ Form_pg_class classtuple;
+
+ /*
+ * Fetch a modifiable copy of the tuple, modify it, update pg_class.
+ */
+ relationRelation = heap_open(RelationRelationId, RowExclusiveLock);
+ tuple = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(relationId));
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "cache lookup failed for relation %u", relationId);
+ classtuple = (Form_pg_class) GETSTRUCT(tuple);
+
+ if (classtuple->relpersistence != persistence)
+ {
+ classtuple->relpersistence = persistence;
+ simple_heap_update(relationRelation, &tuple->t_self, tuple);
+
+ /* keep the catalog indexes up to date */
+ CatalogUpdateIndexes(relationRelation, tuple);
+ }
+ else
+ {
+ /* no need to change tuple, but force relcache rebuild anyway */
+ CacheInvalidateRelcacheByTuple(tuple);
+ }
+
+ heap_freetuple(tuple);
+ heap_close(relationRelation, RowExclusiveLock);
+}
+
+/*
* Execute ALTER TABLE SET SCHEMA
*/
ObjectAddress
@@ -11435,6 +11719,7 @@ AlterTableNamespaceInternal(Relation rel, Oid oldNspOid, Oid nspOid,
/* Fix other dependent stuff */
if (rel->rd_rel->relkind == RELKIND_RELATION ||
+ rel->rd_rel->relkind == RELKIND_PARTITIONED_REL ||
rel->rd_rel->relkind == RELKIND_MATVIEW)
{
AlterIndexNamespaces(classRel, rel, oldNspOid, nspOid, objsMoved);
@@ -11884,7 +12169,7 @@ RangeVarCallbackOwnsTable(const RangeVar *relation,
if (!relkind)
return;
if (relkind != RELKIND_RELATION && relkind != RELKIND_TOASTVALUE &&
- relkind != RELKIND_MATVIEW)
+ relkind != RELKIND_MATVIEW && relkind != RELKIND_PARTITIONED_REL)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not a table or materialized view", relation->relname)));
@@ -12038,6 +12323,7 @@ RangeVarCallbackForAlterRelation(const RangeVar *rv, Oid relid, Oid oldrelid,
*/
if (IsA(stmt, AlterObjectSchemaStmt) &&
relkind != RELKIND_RELATION &&
+ relkind != RELKIND_PARTITIONED_REL &&
relkind != RELKIND_VIEW &&
relkind != RELKIND_MATVIEW &&
relkind != RELKIND_SEQUENCE &&
@@ -12049,3 +12335,203 @@ RangeVarCallbackForAlterRelation(const RangeVar *rv, Oid relid, Oid oldrelid,
ReleaseSysCache(tuple);
}
+
+/*
+ * transformPartitionBy
+ * Transform any expressions present in the partition key
+ */
+static PartitionBy *
+transformPartitionBy(Relation rel, PartitionBy *partitionby)
+{
+ ParseState *pstate = NULL;
+ RangeTblEntry *rte;
+ ListCell *l;
+ PartitionBy *partby = NULL;
+
+ partby = (PartitionBy *) makeNode(PartitionBy);
+
+ partby->strategy = partitionby->strategy;
+ partby->location = partitionby->location;
+ partby->partParams = NIL;
+
+ /*
+ * Create a dummy ParseState and insert the target relation as its sole
+ * rangetable entry. We need a ParseState for transformExpr.
+ */
+ pstate = make_parsestate(NULL);
+ rte = addRangeTableEntryForRelation(pstate,
+ rel,
+ NULL,
+ false,
+ true);
+ addRTEtoQuery(pstate, rte, false, true, true);
+
+ /* take care of any partition expressions */
+ foreach(l, partitionby->partParams)
+ {
+ ListCell *column;
+ PartitionElem *pelem = (PartitionElem *) lfirst(l);
+
+ /* Check for PARTITION BY ... ON (foo, foo) */
+ foreach(column, partby->partParams)
+ {
+ PartitionElem *pparam = (PartitionElem *) lfirst(column);
+ if (pparam->name && strcmp(pelem->name, pparam->name) == 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_COLUMN),
+ errmsg("column \"%s\" appears twice in partition key", pelem->name),
+ parser_errposition(pstate, pelem->location)));
+ }
+
+ if (pelem->expr)
+ {
+ /* Now do parse transformation of the expression */
+ pelem->expr = transformExpr(pstate, pelem->expr,
+ EXPR_KIND_PARTKEY_EXPRESSION);
+
+ /* we have to fix its collations too */
+ assign_expr_collations(pstate, pelem->expr);
+
+ /*
+ * transformExpr() should have already rejected subqueries,
+ * aggregates, and window functions, based on the EXPR_KIND_ for
+ * a partition key expression.
+ *
+ * Also reject expressions returning sets; this is for consistency
+ * DefineRelation() will make more checks.
+ */
+ if (expression_returns_set(pelem->expr))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("partition key expression cannot return a set"),
+ parser_errposition(pstate, pelem->location)));
+ }
+
+ partby->partParams = lappend(partby->partParams, pelem);
+ }
+
+ return partby;
+}
+
+/*
+ * ComputePartitionAttrs
+ * Compute per-partition-column information from partParams
+ */
+static void
+ComputePartitionAttrs(Oid relid,
+ List *partParams,
+ AttrNumber *partattrs,
+ List **partexprs,
+ Oid *partOpClassOids)
+{
+ int attn;
+ ListCell *lc;
+
+ attn = 0;
+ foreach(lc, partParams)
+ {
+ PartitionElem *pelem = (PartitionElem *) lfirst(lc);
+ Oid atttype;
+ Oid opclassOid;
+
+ if (pelem->name != NULL)
+ {
+ HeapTuple atttuple;
+ Form_pg_attribute attform;
+
+ atttuple = SearchSysCacheAttName(relid, pelem->name);
+ if (!HeapTupleIsValid(atttuple))
+ {
+ /* difference in error message spellings is historical */
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_COLUMN),
+ errmsg("column \"%s\" named in partition key does not exist",
+ pelem->name)));
+ }
+ attform = (Form_pg_attribute) GETSTRUCT(atttuple);
+
+ if (attform->attnum <= 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_COLUMN),
+ errmsg("cannot use system column \"%s\" in partition key",
+ pelem->name)));
+
+ partattrs[attn] = attform->attnum;
+ atttype = attform->atttypid;
+ ReleaseSysCache(atttuple);
+ }
+ else
+ {
+ /* Partition key expression */
+ Node *expr = pelem->expr;
+
+ Assert(expr != NULL);
+ atttype = exprType(expr);
+
+ if (IsA(expr, CollateExpr))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("cannot use COLLATE in partition key expression")));
+
+ if (IsA(expr, Var) &&
+ ((Var *) expr)->varattno != InvalidAttrNumber)
+ {
+ /*
+ * User wrote "(column)" or "(column COLLATE something)".
+ * Treat it like simple attribute anyway.
+ */
+ partattrs[attn] = ((Var *) expr)->varattno;
+ }
+ else
+ {
+ partattrs[attn] = 0; /* marks expression */
+ *partexprs = lappend(*partexprs, expr);
+
+ /*
+ * transformExpr() should have already rejected subqueries,
+ * aggregates, and window functions, based on the EXPR_KIND_
+ * for a partition key expression.
+ */
+
+ /*
+ * An expression using mutable functions is probably wrong even
+ * even to use in a partition key
+ */
+ expr = (Node *) expression_planner((Expr *) expr);
+
+ if (IsA(expr, Const))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("cannot use a constant expression as partition key")));
+
+ if (contain_mutable_functions(expr))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("functions in partition key expression must be marked IMMUTABLE")));
+ }
+ }
+
+ /*
+ * Identify the opclass to use. At the moment, we use "btree" operators
+ * that seems enough for list and range partitioning.
+ */
+ if (!pelem->opclass)
+ {
+ opclassOid = GetDefaultOpClass(atttype, BTREE_AM_OID);
+
+ if (!OidIsValid(opclassOid))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("data type %s has no default btree operator class",
+ format_type_be(atttype)),
+ errhint("You must specify an existing btree operator class or define one for the type.")));
+ }
+ else
+ opclassOid = GetIndexOpClass(pelem->opclass,
+ atttype,
+ "btree",
+ BTREE_AM_OID);
+
+ partOpClassOids[attn++] = opclassOid;
+ }
+}
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index cca2e43..26dbac3 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -174,7 +174,8 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
* Triggers must be on tables or views, and there are additional
* relation-type-specific restrictions.
*/
- if (rel->rd_rel->relkind == RELKIND_RELATION)
+ if (rel->rd_rel->relkind == RELKIND_RELATION ||
+ rel->rd_rel->relkind == RELKIND_PARTITIONED_REL)
{
/* Tables can't have INSTEAD OF triggers */
if (stmt->timing != TRIGGER_TYPE_BEFORE &&
@@ -1114,6 +1115,7 @@ RemoveTriggerById(Oid trigOid)
rel = heap_open(relid, AccessExclusiveLock);
if (rel->rd_rel->relkind != RELKIND_RELATION &&
+ rel->rd_rel->relkind != RELKIND_PARTITIONED_REL &&
rel->rd_rel->relkind != RELKIND_VIEW &&
rel->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
ereport(ERROR,
@@ -1220,7 +1222,8 @@ RangeVarCallbackForRenameTrigger(const RangeVar *rv, Oid relid, Oid oldrelid,
/* only tables and views can have triggers */
if (form->relkind != RELKIND_RELATION && form->relkind != RELKIND_VIEW &&
- form->relkind != RELKIND_FOREIGN_TABLE)
+ form->relkind != RELKIND_FOREIGN_TABLE &&
+ form->relkind != RELKIND_PARTITIONED_REL)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not a table, view, or foreign table",
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 4cb4acf..cc21951 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -1300,9 +1300,14 @@ vacuum_rel(Oid relid, RangeVar *relation, int options, VacuumParams *params)
onerel->rd_rel->relkind != RELKIND_MATVIEW &&
onerel->rd_rel->relkind != RELKIND_TOASTVALUE)
{
- ereport(WARNING,
- (errmsg("skipping \"%s\" --- cannot vacuum non-tables or special system tables",
- RelationGetRelationName(onerel))));
+ if (onerel->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+ ereport(WARNING,
+ (errmsg("skipping \"%s\" --- cannot vacuum partitioned tables",
+ RelationGetRelationName(onerel))));
+ else
+ ereport(WARNING,
+ (errmsg("skipping \"%s\" --- cannot vacuum non-tables or special system tables",
+ RelationGetRelationName(onerel))));
relation_close(onerel, lmode);
PopActiveSnapshot();
CommitTransactionCommand();
diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c
index b109ea5..3d465c0 100644
--- a/src/backend/parser/parse_agg.c
+++ b/src/backend/parser/parse_agg.c
@@ -457,6 +457,9 @@ check_agglevels_and_constraints(ParseState *pstate, Node *expr)
err = _("grouping operations are not allowed in index expressions");
break;
+ case EXPR_KIND_PARTKEY_EXPRESSION:
+ err = _("aggregate functions are not allowed in partition key expressions");
+ break;
case EXPR_KIND_INDEX_PREDICATE:
if (isAgg)
err = _("aggregate functions are not allowed in index predicates");
@@ -830,6 +833,9 @@ transformWindowFuncCall(ParseState *pstate, WindowFunc *wfunc,
case EXPR_KIND_INDEX_EXPRESSION:
err = _("window functions are not allowed in index expressions");
break;
+ case EXPR_KIND_PARTKEY_EXPRESSION:
+ err = _("window functions are not allowed in partition key expressions");
+ break;
case EXPR_KIND_INDEX_PREDICATE:
err = _("window functions are not allowed in index predicates");
break;
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index ebda55d..b115f66 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -1703,6 +1703,9 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
case EXPR_KIND_INDEX_EXPRESSION:
err = _("cannot use subquery in index expression");
break;
+ case EXPR_KIND_PARTKEY_EXPRESSION:
+ err = _("cannot use subquery in partition key expressions");
+ break;
case EXPR_KIND_INDEX_PREDICATE:
err = _("cannot use subquery in index predicate");
break;
@@ -3225,6 +3228,8 @@ ParseExprKindName(ParseExprKind exprKind)
return "EXECUTE";
case EXPR_KIND_TRIGGER_WHEN:
return "WHEN";
+ case EXPR_KIND_PARTKEY_EXPRESSION:
+ return "partition key expression";
/*
* There is intentionally no default: case here, so that the
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 54efa35..02655da 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -75,6 +75,7 @@ typedef struct
Relation rel; /* opened/locked rel, if ALTER */
List *inhRelations; /* relations to inherit from */
bool isforeign; /* true if CREATE/ALTER FOREIGN TABLE */
+ bool ispartitioned; /* true if has partition key */
bool isalter; /* true if altering existing table */
bool hasoids; /* does relation have an OID column? */
List *columns; /* ColumnDef items */
@@ -264,6 +265,7 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
cxt.blist = NIL;
cxt.alist = NIL;
cxt.pkey = NULL;
+ cxt.ispartitioned = stmt->partitionBy != NIL;
/*
* Notice that we allow OIDs here only for plain tables, even though
@@ -617,6 +619,12 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
errmsg("primary key constraints are not supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
+ if (cxt->ispartitioned)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("primary key constraints are not supported on partitioned tables"),
+ parser_errposition(cxt->pstate,
+ constraint->location)));
/* FALL THRU */
case CONSTR_UNIQUE:
@@ -626,6 +634,12 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
errmsg("unique constraints are not supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
+ if (cxt->ispartitioned)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("unique constraints are not supported on partititoned tables"),
+ parser_errposition(cxt->pstate,
+ constraint->location)));
if (constraint->keys == NIL)
constraint->keys = list_make1(makeString(column->colname));
cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
@@ -643,6 +657,12 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
errmsg("foreign key constraints are not supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
+ if (cxt->ispartitioned)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("foreign key constraints are not supported on partitioned tables"),
+ parser_errposition(cxt->pstate,
+ constraint->location)));
/*
* Fill in the current attribute's name and throw it into the
@@ -794,6 +814,7 @@ transformTableLikeClause(CreateStmtContext *cxt, TableLikeClause *table_like_cla
relation = relation_openrv(table_like_clause->relation, AccessShareLock);
if (relation->rd_rel->relkind != RELKIND_RELATION &&
+ relation->rd_rel->relkind != RELKIND_PARTITIONED_REL &&
relation->rd_rel->relkind != RELKIND_VIEW &&
relation->rd_rel->relkind != RELKIND_MATVIEW &&
relation->rd_rel->relkind != RELKIND_COMPOSITE_TYPE &&
@@ -2550,6 +2571,7 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
cxt.blist = NIL;
cxt.alist = NIL;
cxt.pkey = NULL;
+ cxt.ispartitioned = relid_is_partitioned(relid);
/*
* The only subtypes that currently require parse transformation handling
diff --git a/src/backend/rewrite/rewriteDefine.c b/src/backend/rewrite/rewriteDefine.c
index 1434a03..b7cda53 100644
--- a/src/backend/rewrite/rewriteDefine.c
+++ b/src/backend/rewrite/rewriteDefine.c
@@ -260,6 +260,7 @@ DefineQueryRewrite(char *rulename,
* blocks them for users. Don't mention them in the error message.
*/
if (event_relation->rd_rel->relkind != RELKIND_RELATION &&
+ event_relation->rd_rel->relkind != RELKIND_PARTITIONED_REL &&
event_relation->rd_rel->relkind != RELKIND_MATVIEW &&
event_relation->rd_rel->relkind != RELKIND_VIEW)
ereport(ERROR,
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 045f7f0..34da4e2 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -961,37 +961,48 @@ ProcessUtilitySlow(Node *parsetree,
{
Datum toast_options;
static char *validnsps[] = HEAP_RELOPT_NAMESPACES;
+ char relkind = ((CreateStmt *) stmt)->partitionBy != NIL
+ ? RELKIND_PARTITIONED_REL
+ : RELKIND_RELATION;
/* Create the table itself */
address = DefineRelation((CreateStmt *) stmt,
- RELKIND_RELATION,
+ relkind,
InvalidOid, NULL);
EventTriggerCollectSimpleCommand(address,
secondaryObject,
stmt);
/*
- * Let NewRelationCreateToastTable decide if this
- * one needs a secondary relation too.
+ * It's not clear whether this is the right place
+ * to determine using the relkind, whether or not
+ * TOAST relation is to be created.
*/
- CommandCounterIncrement();
-
- /*
- * parse and validate reloptions for the toast
- * table
- */
- toast_options = transformRelOptions((Datum) 0,
- ((CreateStmt *) stmt)->options,
- "toast",
- validnsps,
- true,
- false);
- (void) heap_reloptions(RELKIND_TOASTVALUE,
- toast_options,
- true);
+ if (relkind != RELKIND_PARTITIONED_REL)
+ {
+ /*
+ * Let NewRelationCreateToastTable decide if this
+ * one needs a secondary relation too.
+ */
+ CommandCounterIncrement();
- NewRelationCreateToastTable(address.objectId,
- toast_options);
+ /*
+ * parse and validate reloptions for the toast
+ * table
+ */
+ toast_options = transformRelOptions((Datum) 0,
+ ((CreateStmt *) stmt)->options,
+ "toast",
+ validnsps,
+ true,
+ false);
+ (void) heap_reloptions(RELKIND_TOASTVALUE,
+ toast_options,
+ true);
+
+ NewRelationCreateToastTable(address.objectId,
+ toast_options);
+ }
}
else if (IsA(stmt, CreateForeignTableStmt))
{
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 130c06d..ddb0da2 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -49,6 +49,7 @@
#include "catalog/pg_database.h"
#include "catalog/pg_namespace.h"
#include "catalog/pg_opclass.h"
+#include "catalog/pg_partitioned_rel.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_rewrite.h"
#include "catalog/pg_shseclabel.h"
@@ -431,6 +432,7 @@ RelationParseRelOptions(Relation relation, HeapTuple tuple)
switch (relation->rd_rel->relkind)
{
case RELKIND_RELATION:
+ case RELKIND_PARTITIONED_REL:
case RELKIND_TOASTVALUE:
case RELKIND_INDEX:
case RELKIND_VIEW:
@@ -1049,6 +1051,10 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
else
relation->rd_rsdesc = NULL;
+ /* if it's a partitioned table, initialize key info */
+ if (relation->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+ RelationBuildPartitionKeys(relation);
+
/*
* if it's an index, initialize index-related information
*/
@@ -2045,6 +2051,7 @@ RelationDestroyRelation(Relation relation, bool remember_tupdesc)
MemoryContextDelete(relation->rd_rulescxt);
if (relation->rd_rsdesc)
MemoryContextDelete(relation->rd_rsdesc->rscxt);
+ FreePartitionKeys(relation->rd_partkeys);
if (relation->rd_fdwroutine)
pfree(relation->rd_fdwroutine);
pfree(relation);
@@ -4506,6 +4513,93 @@ RelationGetExclusionInfo(Relation indexRelation,
MemoryContextSwitchTo(oldcxt);
}
+/*
+ * RelationGetPartitionExpr -- get the partition key expressions
+ *
+ * The returned node tree is copied into the caller's memory context. (We
+ * don't want to return a pointer to the relcache copy,since it could
+ * disappear due to relcache invalidation.)
+ */
+List *
+RelationGetPartitionExpr(Relation relation, int16 level)
+{
+ ListCell *cell;
+ Relation catalog;
+ HeapTuple tuple;
+ List *result = NIL;
+ Datum exprsDatum;
+ bool isnull;
+ char *exprsString;
+ MemoryContext oldcxt;
+
+ /* Shouldn't happen but play safe! */
+ if (relation->rd_partkeys == NIL)
+ return NIL;
+
+ /* Find the proper key in relcache */
+ foreach(cell, relation->rd_partkeys)
+ {
+ PartitionKey key = lfirst(cell);
+
+ if (key->level != level)
+ continue;
+
+ /* Quick copy if we already computed the result */
+ if (key->partexprs)
+ return (List *) copyObject(key->partexprs);
+
+ /* Need to compute from the tuple in the syscache */
+ tuple = SearchSysCache2(PARTEDRELIDLEVEL,
+ RelationGetRelid(relation),
+ level);
+ Assert(HeapTupleIsValid(tuple));
+
+ /*
+ * We build the tree we intend to return in the caller's context.
+ * After successfully completing the work, we copy it into the
+ * relcache entry. This avoids problems if we get some sort of
+ * error partway through.
+ */
+ catalog = heap_open(PartitionedRelRelationId, AccessShareLock);
+ exprsDatum = heap_getattr(tuple,
+ Anum_pg_partitioned_rel_partexprs,
+ RelationGetDescr(catalog),
+ &isnull);
+
+ /* No expressions in the key at this level */
+ if (isnull)
+ {
+ ReleaseSysCache(tuple);
+ heap_close(catalog, AccessShareLock);
+ return NIL;
+ }
+
+ exprsString = TextDatumGetCString(exprsDatum);
+ result = (List *) stringToNode(exprsString);
+ pfree(exprsString);
+
+ /*
+ * Run the expressions through eval_const_expressions. This is not just an
+ * optimization, but is necessary, because the planner will be comparing
+ * them to similarly-processed qual clauses, and may fail to detect valid
+ * matches without this. We don't bother with canonicalize_qual, however.
+ */
+ result = (List *) eval_const_expressions(NULL, (Node *) result);
+
+ /* May as well fix opfuncids too */
+ fix_opfuncids((Node *) result);
+
+ /* Now save a copy of the completed tree in the relcache entry. */
+ oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
+ key->partexprs = (List *) copyObject(result);
+ MemoryContextSwitchTo(oldcxt);
+
+ ReleaseSysCache(tuple);
+ heap_close(catalog, AccessShareLock);
+ }
+
+ return result;
+}
/*
* Routines to support ereport() reports of relation-related errors
@@ -4898,6 +4992,7 @@ load_relcache_init_file(bool shared)
rel->rd_rulescxt = NULL;
rel->trigdesc = NULL;
rel->rd_rsdesc = NULL;
+ rel->rd_partkeys = NIL;
rel->rd_indexprs = NIL;
rel->rd_indpred = NIL;
rel->rd_exclops = NULL;
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index fd8dc91..aa8c694 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -764,6 +764,7 @@ permissionsList(const char *pattern)
" WHEN 'm' THEN '%s'"
" WHEN 'S' THEN '%s'"
" WHEN 'f' THEN '%s'"
+ " WHEN 'P' THEN '%s'"
" END as \"%s\",\n"
" ",
gettext_noop("Schema"),
@@ -773,6 +774,7 @@ permissionsList(const char *pattern)
gettext_noop("materialized view"),
gettext_noop("sequence"),
gettext_noop("foreign table"),
+ gettext_noop("partitioned table"),
gettext_noop("Type"));
printACLColumn(&buf, "c.relacl");
@@ -819,7 +821,7 @@ permissionsList(const char *pattern)
appendPQExpBufferStr(&buf, "\nFROM pg_catalog.pg_class c\n"
" LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace\n"
- "WHERE c.relkind IN ('r', 'v', 'm', 'S', 'f')\n");
+ "WHERE c.relkind IN ('r', 'v', 'm', 'S', 'f', 'P')\n");
/*
* Unless a schema pattern is specified, we suppress system and temp
@@ -1463,8 +1465,8 @@ describeOneTableDetails(const char *schemaname,
* types, and foreign tables (c.f. CommentObject() in comment.c).
*/
if (tableinfo.relkind == 'r' || tableinfo.relkind == 'v' ||
- tableinfo.relkind == 'm' ||
- tableinfo.relkind == 'f' || tableinfo.relkind == 'c')
+ tableinfo.relkind == 'm' || tableinfo.relkind == 'f' ||
+ tableinfo.relkind == 'c' || tableinfo.relkind == 'P')
appendPQExpBufferStr(&buf, ", pg_catalog.col_description(a.attrelid, a.attnum)");
}
@@ -1529,6 +1531,14 @@ describeOneTableDetails(const char *schemaname,
printfPQExpBuffer(&title, _("Foreign table \"%s.%s\""),
schemaname, relationname);
break;
+ case 'P':
+ if (tableinfo.relpersistence == 'u')
+ printfPQExpBuffer(&title, _("Unlogged partitioned table \"%s.%s\""),
+ schemaname, relationname);
+ else
+ printfPQExpBuffer(&title, _("Partitioned table \"%s.%s\""),
+ schemaname, relationname);
+ break;
default:
/* untranslated unknown relkind */
printfPQExpBuffer(&title, "?%c? \"%s.%s\"",
@@ -1542,8 +1552,8 @@ describeOneTableDetails(const char *schemaname,
cols = 2;
if (tableinfo.relkind == 'r' || tableinfo.relkind == 'v' ||
- tableinfo.relkind == 'm' ||
- tableinfo.relkind == 'f' || tableinfo.relkind == 'c')
+ tableinfo.relkind == 'm' || tableinfo.relkind == 'f' ||
+ tableinfo.relkind == 'c' || tableinfo.relkind == 'P')
{
show_modifiers = true;
headers[cols++] = gettext_noop("Modifiers");
@@ -1563,12 +1573,12 @@ describeOneTableDetails(const char *schemaname,
{
headers[cols++] = gettext_noop("Storage");
if (tableinfo.relkind == 'r' || tableinfo.relkind == 'm' ||
- tableinfo.relkind == 'f')
+ tableinfo.relkind == 'f' || tableinfo.relkind == 'P')
headers[cols++] = gettext_noop("Stats target");
/* Column comments, if the relkind supports this feature. */
if (tableinfo.relkind == 'r' || tableinfo.relkind == 'v' ||
- tableinfo.relkind == 'm' ||
- tableinfo.relkind == 'c' || tableinfo.relkind == 'f')
+ tableinfo.relkind == 'm' || tableinfo.relkind == 'c' ||
+ tableinfo.relkind == 'f' || tableinfo.relkind == 'P')
headers[cols++] = gettext_noop("Description");
}
@@ -1668,7 +1678,7 @@ describeOneTableDetails(const char *schemaname,
/* Statistics target, if the relkind supports this feature */
if (tableinfo.relkind == 'r' || tableinfo.relkind == 'm' ||
- tableinfo.relkind == 'f')
+ tableinfo.relkind == 'f' || tableinfo.relkind == 'P')
{
printTableAddCell(&cont, PQgetvalue(res, i, firstvcol + 1),
false, false);
@@ -1676,8 +1686,8 @@ describeOneTableDetails(const char *schemaname,
/* Column comments, if the relkind supports this feature. */
if (tableinfo.relkind == 'r' || tableinfo.relkind == 'v' ||
- tableinfo.relkind == 'm' ||
- tableinfo.relkind == 'c' || tableinfo.relkind == 'f')
+ tableinfo.relkind == 'm' || tableinfo.relkind == 'c' ||
+ tableinfo.relkind == 'f' || tableinfo.relkind == 'P')
printTableAddCell(&cont, PQgetvalue(res, i, firstvcol + 2),
false, false);
}
@@ -1822,7 +1832,7 @@ describeOneTableDetails(const char *schemaname,
PQclear(result);
}
else if (tableinfo.relkind == 'r' || tableinfo.relkind == 'm' ||
- tableinfo.relkind == 'f')
+ tableinfo.relkind == 'f' || tableinfo.relkind == 'P')
{
/* Footer information about a table */
PGresult *result = NULL;
@@ -2381,7 +2391,7 @@ describeOneTableDetails(const char *schemaname,
* Finish printing the footer information about a table.
*/
if (tableinfo.relkind == 'r' || tableinfo.relkind == 'm' ||
- tableinfo.relkind == 'f')
+ tableinfo.relkind == 'f' || tableinfo.relkind == 'P')
{
PGresult *result;
int tuples;
@@ -2592,7 +2602,7 @@ add_tablespace_footer(printTableContent *const cont, char relkind,
Oid tablespace, const bool newline)
{
/* relkinds for which we support tablespaces */
- if (relkind == 'r' || relkind == 'm' || relkind == 'i')
+ if (relkind == 'r' || relkind == 'm' || relkind == 'i' || relkind == 'P')
{
/*
* We ignore the database default tablespace so that users not using
@@ -2923,6 +2933,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
" WHEN 'S' THEN '%s'"
" WHEN 's' THEN '%s'"
" WHEN 'f' THEN '%s'"
+ " WHEN 'P' THEN '%s'"
" END as \"%s\",\n"
" pg_catalog.pg_get_userbyid(c.relowner) as \"%s\"",
gettext_noop("Schema"),
@@ -2934,6 +2945,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
gettext_noop("sequence"),
gettext_noop("special"),
gettext_noop("foreign table"),
+ gettext_noop("partitioned table"),
gettext_noop("Type"),
gettext_noop("Owner"));
@@ -2972,7 +2984,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
appendPQExpBufferStr(&buf, "\nWHERE c.relkind IN (");
if (showTables)
- appendPQExpBufferStr(&buf, "'r',");
+ appendPQExpBufferStr(&buf, "'r', 'P',");
if (showViews)
appendPQExpBufferStr(&buf, "'v',");
if (showMatViews)
diff --git a/src/include/catalog/partition.h b/src/include/catalog/partition.h
new file mode 100644
index 0000000..44b4c06
--- /dev/null
+++ b/src/include/catalog/partition.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * partition.h
+ * Header file for structures and utility functions related to
+ * partitioning
+ *
+ * Copyright (c) 2007-2016, PostgreSQL Global Development Group
+ *
+ * src/include/utils/partition.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PARTITION_H
+#define PARTITION_H
+
+#include "fmgr.h"
+#include "utils/relcache.h"
+
+/* Partition key information for one level */
+typedef struct PartitionKeyData
+{
+ char strategy; /* partition strategy */
+ int16 level; /* partition level */
+ int16 partnatts; /* number of partition attributes */
+ AttrNumber *partattrs; /* partition attnums */
+ Oid *partopfamily; /* OIDs of opfamily per col */
+ FmgrInfo *partsupfunc; /* lookup info for support funcs */
+ List *partexprs; /* partition key expressions, if any */
+} PartitionKeyData;
+
+typedef struct PartitionKeyData *PartitionKey;
+
+/* Type information for partition key columns */
+typedef struct PartitionKeyTypeInfo
+{
+ Oid *typid;
+ int32 *typmod;
+ int16 *typlen;
+ bool *typbyval;
+ char *typalign;
+} PartitionKeyTypeInfo;
+
+extern void StorePartitionKey(Relation rel,
+ int16 partlevel,
+ int16 partnatts,
+ AttrNumber *partattrs,
+ List *partexprs,
+ Oid *partOpClassOids,
+ char strategy);
+extern void RemovePartitionKeysByRelId(Oid relid);
+extern void RelationBuildPartitionKeys(Relation relation);
+extern List *CopyPartitionKeys(List *keys);
+extern void FreePartitionKeys(List *keys);
+extern PartitionKeyTypeInfo *get_partition_key_type_info(Relation rel,
+ PartitionKey key);
+extern void free_partition_key_type_info(PartitionKeyTypeInfo *typinfo);
+extern bool relid_is_partitioned(Oid relid);
+
+#endif /* PARTITION_H */
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index 54f67e9..1b6270c 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -42,6 +42,8 @@ extern bool CheckIndexCompatible(Oid oldId,
List *attributeList,
List *exclusionOpNames);
extern Oid GetDefaultOpClass(Oid type_id, Oid am_id);
+extern Oid GetIndexOpClass(List *opclass, Oid attrType,
+ char *accessMethodName, Oid accessMethodId);
/* commands/functioncmds.c */
extern ObjectAddress CreateFunction(CreateFunctionStmt *stmt, const char *queryString);
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index e3e359c..f6f3f52 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -64,7 +64,8 @@ typedef enum ParseExprKind
EXPR_KIND_ALTER_COL_TRANSFORM, /* transform expr in ALTER COLUMN TYPE */
EXPR_KIND_EXECUTE_PARAMETER, /* parameter value in EXECUTE */
EXPR_KIND_TRIGGER_WHEN, /* WHEN condition in CREATE TRIGGER */
- EXPR_KIND_POLICY /* USING or WITH CHECK expr in policy */
+ EXPR_KIND_POLICY, /* USING or WITH CHECK expr in policy */
+ EXPR_KIND_PARTKEY_EXPRESSION /* partition key expression */
} ParseExprKind;
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index f2bebf2..4242e84 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -15,6 +15,7 @@
#define REL_H
#include "access/tupdesc.h"
+#include "catalog/partition.h"
#include "catalog/pg_class.h"
#include "catalog/pg_index.h"
#include "fmgr.h"
@@ -89,6 +90,8 @@ typedef struct RelationData
/* use "struct" here to avoid needing to include rowsecurity.h: */
struct RowSecurityDesc *rd_rsdesc; /* row security policies, or NULL */
+ List *rd_partkeys; /* partition keys, or NIL */
+
/* data managed by RelationGetIndexList: */
List *rd_indexlist; /* list of OIDs of indexes on relation */
Oid rd_oidindex; /* OID of unique index on OID, if any */
diff --git a/src/include/utils/relcache.h b/src/include/utils/relcache.h
index 1b48304..0aa2104 100644
--- a/src/include/utils/relcache.h
+++ b/src/include/utils/relcache.h
@@ -42,6 +42,8 @@ extern Oid RelationGetOidIndex(Relation relation);
extern Oid RelationGetReplicaIndex(Relation relation);
extern List *RelationGetIndexExpressions(Relation relation);
extern List *RelationGetIndexPredicate(Relation relation);
+extern List *RelationGetPartitionExpr(Relation relation,
+ int16 level);
typedef enum IndexAttrBitmapKind
{
--
1.7.1
[text/x-diff] 0005-Add-partition-system-catalog.patch (8.3K, ../../56C1302D.6070403@lab.ntt.co.jp/6-0005-Add-partition-system-catalog.patch)
download | inline diff:
From ba39817ac4ccc6182eae720a6f54df0f66584574 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Fri, 22 Jan 2016 18:31:39 +0900
Subject: [PATCH 05/10] Add partition system catalog.
Named pg_partition, it contains the following information:
1. Partition relation oid (pg_class.oid)
2. Partition parent oid
3. Whether partition is valid
4. List partition literal values
5. Range partition maximum value per key column
It has a unique index on partrelid and the related syscache. Also,
an index on partparent.
---
doc/src/sgml/catalogs.sgml | 75 +++++++++++++++++++++++++++++++++++-
src/backend/catalog/Makefile | 2 +-
src/backend/utils/cache/syscache.c | 12 ++++++
src/include/catalog/indexing.h | 5 ++
src/include/catalog/pg_partition.h | 62 +++++++++++++++++++++++++++++
src/include/utils/syscache.h | 1 +
6 files changed, 155 insertions(+), 2 deletions(-)
create mode 100644 src/include/catalog/pg_partition.h
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index db8d0ac..a5fe8a6 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -219,8 +219,13 @@
</row>
<row>
+ <entry><link linkend="catalog-pg-partition"><structname>pg_partition</structname></link></entry>
+ <entry>information about partitions</entry>
+ </row>
+
+ <row>
<entry><link linkend="catalog-pg-partitioned-rel"><structname>pg_partitioned_rel</structname></link></entry>
- <entry>information about partitioned tables, including the partition key</entry>
+ <entry>information about partitioned tables</entry>
</row>
<row>
@@ -4542,6 +4547,74 @@
</sect1>
+ <sect1 id="catalog-pg-partition">
+ <title><structname>pg_partition</structname></title>
+
+ <indexterm zone="catalog-pg-partition">
+ <primary>pg_partition</primary>
+ </indexterm>
+
+ <para>
+ The catalog <structname>pg_partition</structname> stores
+ information about partitions of partitioned tables.
+ </para>
+
+ <table>
+ <title><structname>pg_partition</> Columns</title>
+
+ <tgroup cols="4">
+ <thead>
+ <row>
+ <entry>Name</entry>
+ <entry>Type</entry>
+ <entry>References</entry>
+ <entry>Description</entry>
+ </row>
+ </thead>
+
+ <tbody>
+
+ <row>
+ <entry><structfield>partrelid</structfield></entry>
+ <entry><type>oid</type></entry>
+ <entry><literal><link linkend="catalog-pg-class"><structname>pg_class</structname></link>.oid</literal></entry>
+ <entry>The OID of the <structname>pg_class</> entry for this partition</entry>
+ </row>
+
+ <row>
+ <entry><structfield>partparent</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 partitioned table this partition is part of</entry>
+ </row>
+
+ <row>
+ <entry><structfield>partvalid</structfield></entry>
+ <entry><type>bool</type></entry>
+ <entry></entry>
+ <entry>Partition is valid</entry>
+ </row>
+
+ <row>
+ <entry><structfield>partlistvals</structfield></entry>
+ <entry><type>anyarray</type></entry>
+ <entry></entry>
+ <entry>For partitions of list partitioned table, list of values assigned to this partition</entry>
+ </row>
+
+ <row>
+ <entry><structfield>partrangemaxs</structfield></entry>
+ <entry><type>anyarray</type></entry>
+ <entry></entry>
+ <entry>For partitions of range partitioned table, array of maximum values per partition key column</entry>
+ </row>
+
+ </tbody>
+ </tgroup>
+ </table>
+
+ </sect1>
+
<sect1 id="catalog-pg-partitioned-rel">
<title><structname>pg_partitioned_rel</structname></title>
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index ffec071..fc85518 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -41,7 +41,7 @@ POSTGRES_BKI_SRCS = $(addprefix $(top_srcdir)/src/include/catalog/,\
pg_foreign_data_wrapper.h pg_foreign_server.h pg_user_mapping.h \
pg_foreign_table.h pg_policy.h pg_replication_origin.h \
pg_default_acl.h pg_seclabel.h pg_shseclabel.h \
- pg_collation.h pg_range.h pg_transform.h pg_partitioned_rel.h\
+ pg_collation.h pg_range.h pg_transform.h pg_partitioned_rel.h pg_partition.h \
toasting.h indexing.h \
)
diff --git a/src/backend/utils/cache/syscache.c b/src/backend/utils/cache/syscache.c
index f703668..fe194d5 100644
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -48,6 +48,7 @@
#include "catalog/pg_opclass.h"
#include "catalog/pg_operator.h"
#include "catalog/pg_opfamily.h"
+#include "catalog/pg_partition.h"
#include "catalog/pg_partitioned_rel.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_range.h"
@@ -569,6 +570,17 @@ static const struct cachedesc cacheinfo[] = {
},
8
},
+ {PartitionRelationId, /* PARTRELID */
+ PartitionRelidIndexId,
+ 1,
+ {
+ Anum_pg_partition_partrelid,
+ 0,
+ 0,
+ 0
+ },
+ 128
+ },
{PartitionedRelRelationId, /* PARTEDRELIDLEVEL */
PartitionedRelRelidLevelIndexId,
2,
diff --git a/src/include/catalog/indexing.h b/src/include/catalog/indexing.h
index 343204b..a2a583d 100644
--- a/src/include/catalog/indexing.h
+++ b/src/include/catalog/indexing.h
@@ -322,6 +322,11 @@ DECLARE_UNIQUE_INDEX(pg_partitioned_rel_partrelid_level_index, 3319, on pg_parti
DECLARE_INDEX(pg_partitioned_rel_partrelid_index, 3320, on pg_partitioned_rel using btree(partrelid oid_ops));
#define PartitionedRelRelidIndexId 3320
+DECLARE_UNIQUE_INDEX(pg_partition_partrelid_index, 3322, on pg_partition using btree(partrelid oid_ops));
+#define PartitionRelidIndexId 3322
+DECLARE_INDEX(pg_partition_parent_index, 3323, on pg_partition using btree(partparent oid_ops));
+#define PartitionParentIndexId 3323
+
/* last step of initialization script: build the indexes declared above */
BUILD_INDICES
diff --git a/src/include/catalog/pg_partition.h b/src/include/catalog/pg_partition.h
new file mode 100644
index 0000000..aa89338
--- /dev/null
+++ b/src/include/catalog/pg_partition.h
@@ -0,0 +1,62 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_partition.h
+ * definition of the system "partition" relation (pg_partition)
+ * along with the relation's initial contents.
+ *
+ *
+ * Portions Copyright (c) 1996-2015, PostgreSQL Global Development Group
+ *
+ * $PostgreSQL: pgsql/src/include/catalog/pg_partition.h $
+ *
+ * NOTES
+ * the genbki.sh script reads this file and generates .bki
+ * information from the DATA() statements.
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_PARTITION_H
+#define PG_PARTITION_H
+
+#include "catalog/genbki.h"
+
+/* ----------------
+ * pg_partitioned_rel definition. cpp turns this into
+ * typedef struct FormData_pg_partitioned_rel
+ * ----------------
+ */
+#define PartitionRelationId 3321
+
+CATALOG(pg_partition,3321) BKI_WITHOUT_OIDS
+{
+ Oid partrelid; /* partition oid */
+ Oid partparent; /* parent oid */
+ bool partvalid; /* is partition valid */
+
+#ifdef CATALOG_VARLEN /* variable-length fields start here */
+ anyarray partlistvals; /* array of allowed values for the only
+ * partition column */
+ anyarray partrangemaxs; /* array of rangemax values, one per
+ * key column */
+#endif
+} FormData_pg_partition;
+
+/* ----------------
+ * Form_pg_partition corresponds to a pointer to a tuple with
+ * the format of pg_partition relation.
+ * ----------------
+ */
+typedef FormData_pg_partition *Form_pg_partition;
+
+/* ----------------
+ * compiler constants for pg_partition
+ * ----------------
+ */
+#define Natts_pg_partition 5
+#define Anum_pg_partition_partrelid 1
+#define Anum_pg_partition_partparent 2
+#define Anum_pg_partition_partvalid 3
+#define Anum_pg_partition_partlistvals 4
+#define Anum_pg_partition_partrangemaxs 5
+
+#endif /* PG_PARTITION_H */
diff --git a/src/include/utils/syscache.h b/src/include/utils/syscache.h
index 1c961c7..600047b 100644
--- a/src/include/utils/syscache.h
+++ b/src/include/utils/syscache.h
@@ -72,6 +72,7 @@ enum SysCacheIdentifier
OPEROID,
OPFAMILYAMNAMENSP,
OPFAMILYOID,
+ PARTRELID,
PARTEDRELIDLEVEL,
PROCNAMEARGSNSP,
PROCOID,
--
1.7.1
[text/x-diff] 0006-Add-partition-creation-management-syntax.patch (39.6K, ../../56C1302D.6070403@lab.ntt.co.jp/7-0006-Add-partition-creation-management-syntax.patch)
download | inline diff:
From a136d87b072a40c045d16b74453ec30387d8c783 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Tue, 9 Feb 2016 10:55:48 +0900
Subject: [PATCH 06/10] Add partition creation/management syntax.
This adds following ALTER TABLE commands for adding/dropping/attaching/
detaching partitions and sub-partitions to partitioned tables:
ADD PARTITION name FOR VALUES .. [ WITH (..) ] [ TABLESPACE .. ]
MODIFY PARTITION partname ADD SUBPARTITION FOR VALUES ..
[ WITH (..) ] [ TABLESPACE .. ]
DROP PARTITION partname
DROP SUBPARTITION subpartname
ATTACH PARTITION name FOR VALUES .. USING [ TABLE ] table_name
[ WITH (..) ] [ TABLESPACE .. ]
MODIFY PARTITION partname ATTACH SUBPARTITION name FOR VALUES ..
USING [ TABLE ] table_name [ WITH (..) ] [ TABLESPACE .. ]
DETACH PARTITION partname [ USING [ TABLE ] table_name ]
DROP SUBPARTITION subpartname [ USING [ TABLE ] table_name ]
Two of these commands (ATTACH PARTITION/SUBPARTITION) will not be
implemented right away and will output an "feature not supproted"
error message.
The commands don't yet actually create partitions/sub-partitions; that
will have to wait the next commit which will add infrastructure to add
partition info to the catalog.
Adds LESS, THAN, MODIFY as unreserved keywords and changes PARTITION
and SUBPARTITION from unreserved to reserved keywords.
---
doc/src/sgml/ref/alter_table.sgml | 107 +++++++++++++++++++
src/backend/commands/tablecmds.c | 32 ++++++
src/backend/nodes/copyfuncs.c | 34 ++++++
src/backend/nodes/equalfuncs.c | 30 +++++
src/backend/nodes/outfuncs.c | 1 +
src/backend/parser/gram.y | 188 +++++++++++++++++++++++++++++++--
src/backend/parser/parse_agg.c | 16 +++-
src/backend/parser/parse_expr.c | 11 ++
src/backend/parser/parse_utilcmd.c | 206 ++++++++++++++++++++++++++++++++++++
src/include/nodes/nodes.h | 2 +
src/include/nodes/parsenodes.h | 37 ++++++-
src/include/parser/kwlist.h | 9 ++-
src/include/parser/parse_node.h | 3 +-
13 files changed, 661 insertions(+), 15 deletions(-)
diff --git a/doc/src/sgml/ref/alter_table.sgml b/doc/src/sgml/ref/alter_table.sgml
index aca40f5..e8067cb 100644
--- a/doc/src/sgml/ref/alter_table.sgml
+++ b/doc/src/sgml/ref/alter_table.sgml
@@ -77,12 +77,25 @@ ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable>
NOT OF
OWNER TO { <replaceable class="PARAMETER">new_owner</replaceable> | CURRENT_USER | SESSION_USER }
REPLICA IDENTITY { DEFAULT | USING INDEX <replaceable class="PARAMETER">index_name</replaceable> | FULL | NOTHING }
+ ADD PARTITION <replaceable class="PARAMETER">name</replaceable> FOR VALUES <replaceable class="PARAMETER">partition_bound_spec</replaceable> [ WITH ( <replaceable class="PARAMETER">storage_parameter</replaceable> [= <replaceable class="PARAMETER">value</replaceable>] [, ... ] )] [ TABLESPACE <replaceable class="PARAMETER">tablespace_name</replaceable> ]
+ MODIFY PARTITION <replaceable class="PARAMETER">partition_name</replaceable> ADD SUBPARTITION <replaceable class="PARAMETER">name</replaceable> FOR VALUES <replaceable class="PARAMETER">partition_bound_spec</replaceable> [ WITH ( <replaceable class="PARAMETER">storage_parameter</replaceable> [= <replaceable class="PARAMETER">value</replaceable>] [, ... ] )] [ TABLESPACE <replaceable class="PARAMETER">tablespace_name</replaceable> ]
+ DROP PARTITION <replaceable class="PARAMETER">name</replaceable>
+ DROP SUBPARTITION <replaceable class="PARAMETER">name</replaceable>
+ ATTACH PARTITION <replaceable class="PARAMETER">name</replaceable> FOR VALUES <replaceable class="PARAMETER">partition_bound_spec</replaceable> USING [ TABLE ] <replaceable class="PARAMETER">table_name</replaceable> [ WITH ( <replaceable class="PARAMETER">storage_parameter</replaceable> [= <replaceable class="PARAMETER">value</replaceable>] [, ... ] )] [ TABLESPACE <replaceable class="PARAMETER">tablespace_name</replaceable> ]
+ MODIFY PARTITION <replaceable class="PARAMETER">partition_name</replaceable> ATTACH SUBPARTITION <replaceable class="PARAMETER">name</replaceable> FOR VALUES <replaceable class="PARAMETER">partition_bound_spec</replaceable> USING [ TABLE ] <replaceable class="PARAMETER">table_name</replaceable> [ WITH ( <replaceable class="PARAMETER">storage_parameter</replaceable> [= <replaceable class="PARAMETER">value</replaceable>] [, ... ] )] [ TABLESPACE <replaceable class="PARAMETER">tablespace_name</replaceable> ]
+ DETACH PARTITION <replaceable class="PARAMETER">name</replaceable> [ USING [ TABLE ] <replaceable class="PARAMETER">table_name</replaceable>]
+ DETACH SUBPARTITION <replaceable class="PARAMETER">name</replaceable> [ USING [ TABLE ] <replaceable class="PARAMETER">table_name</replaceable>]
<phrase>and <replaceable class="PARAMETER">table_constraint_using_index</replaceable> is:</phrase>
[ CONSTRAINT <replaceable class="PARAMETER">constraint_name</replaceable> ]
{ UNIQUE | PRIMARY KEY } USING INDEX <replaceable class="PARAMETER">index_name</replaceable>
[ DEFERRABLE | NOT DEFERRABLE ] [ INITIALLY DEFERRED | INITIALLY IMMEDIATE ]
+
+<phrase>and <replaceable class="PARAMETER">partition_bound_spec</replaceable> is:</phrase>
+ FOR VALUES [ IN ] ( <replaceable class="PARAMETER">expression</replaceable> [, ...] )
+ FOR VALUES LESS THAN ( <replaceable class="PARAMETER">expression</replaceable> [, ...] )
+
</synopsis>
</refsynopsisdiv>
@@ -704,6 +717,100 @@ ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable>
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>ADD PARTITION</literal></term>
+ <listitem>
+ <para>
+ This form adds named partition to a partitioned table. The <literal>
+ FOR VALUES </literal> specification must match the partition key
+ of the partitioned table.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><literal>ADD SUBPARTITION</literal></term>
+ <listitem>
+ <para>
+ This form adds named sub-partition to specified partition of a
+ partitioned table. The <literal> FOR VALUES </literal> specification
+ must match the corresponding sub-partition key of the partitioned table.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><literal>DROP PARTITION</literal></term>
+ <listitem>
+ <para>
+ This form drops specified partition of a partitioned table.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><literal>DROP SUBPARTITION</literal></term>
+ <listitem>
+ <para>
+ This form drops specified sub-partition of a partitioned table.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><literal>ATTACH PARTITION USING TABLE</literal></term>
+ <listitem>
+ <para>
+ This form attaches a specified non-partitioned table as named partition
+ of a partitioned table provided it is not sub-partitioned. The <literal>
+ FOR VALUES </literal> specifiation must match the partition key of the
+ partitioned table.
+ </para>
+ <para>
+ There are a number of restrictions on the composition of source table
+ for it to be a candidate for attaching as a partition.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><literal>ATTACH SUBPARTITION USING TABLE</literal></term>
+ <listitem>
+ <para>
+ This form attaches a specified non-partitioned table as named sub-partition
+ of specified partition of a partitioned table provided it is sub-partitioned.
+ The <literal> FOR VALUES </literal> specifiation must match the partition
+ key of the partitioned table.
+ </para>
+ <para>
+ There are a number of restrictions on the composition of source table
+ for it to be a candidate for attaching as a partition.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><literal>DETACH PARTITION</literal></term>
+ <listitem>
+ <para>
+ This form detaches specified partition of a partitioned table.
+ Optionally, one can specifiy target_name to rename the no longer
+ partition to.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><literal>DETACH SUBPARTITION</literal></term>
+ <listitem>
+ <para>
+ This form detaches specified sub-partition of a partitioned table.
+ Optionally, one can specifiy target_name to rename the no longer
+ sub-partition to.
+ </para>
+ </listitem>
+ </varlistentry>
+
</variablelist>
</para>
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 674fbe5..7bdbdf0 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -3117,6 +3117,17 @@ AlterTableGetLockLevel(List *cmds)
cmd_lockmode = AlterTableGetRelOptionsLockLevel((List *) cmd->def);
break;
+ case AT_AddPartition:
+ case AT_AddSubPartition:
+ case AT_DropPartition:
+ case AT_DropSubPartition:
+ case AT_AttachPartition:
+ case AT_AttachSubPartition:
+ case AT_DetachPartition:
+ case AT_DetachSubPartition:
+ cmd_lockmode = AccessExclusiveLock;
+ break;
+
default: /* oops */
elog(ERROR, "unrecognized alter table type: %d",
(int) cmd->subtype);
@@ -3434,6 +3445,18 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
/* No command-specific prep needed */
pass = AT_PASS_MISC;
break;
+ case AT_AddPartition:
+ case AT_AddSubPartition:
+ case AT_DropPartition:
+ case AT_DropSubPartition:
+ case AT_AttachPartition:
+ case AT_AttachSubPartition:
+ case AT_DetachPartition:
+ case AT_DetachSubPartition:
+ ATSimplePermissions(rel, ATT_TABLE);
+ /* No command-specific prep needed */
+ pass = AT_PASS_MISC;
+ break;
default: /* oops */
elog(ERROR, "unrecognized alter table type: %d",
(int) cmd->subtype);
@@ -3757,6 +3780,15 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
case AT_GenericOptions:
ATExecGenericOptions(rel, (List *) cmd->def);
break;
+ case AT_AddPartition:
+ case AT_AddSubPartition:
+ case AT_DropPartition:
+ case AT_DropSubPartition:
+ case AT_AttachPartition:
+ case AT_AttachSubPartition:
+ case AT_DetachPartition:
+ case AT_DetachSubPartition:
+ break;
default: /* oops */
elog(ERROR, "unrecognized alter table type: %d",
(int) cmd->subtype);
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 6b8eed8..aabe108 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3002,6 +3002,7 @@ CopyCreateStmtFields(const CreateStmt *from, CreateStmt *newnode)
COPY_SCALAR_FIELD(oncommit);
COPY_STRING_FIELD(tablespacename);
COPY_SCALAR_FIELD(if_not_exists);
+ COPY_SCALAR_FIELD(is_internal_partition);
}
static CreateStmt *
@@ -4151,6 +4152,33 @@ _copyPartitionElem(const PartitionElem *from)
return newnode;
}
+static PartitionStmt *
+_copyPartitionStmt(const PartitionStmt *from)
+{
+ PartitionStmt *newnode = makeNode(PartitionStmt);
+
+ COPY_NODE_FIELD(name);
+ COPY_NODE_FIELD(parent);
+ COPY_NODE_FIELD(using_table);
+ COPY_NODE_FIELD(values);
+ COPY_NODE_FIELD(options);
+ COPY_STRING_FIELD(tablespacename);
+
+ return newnode;
+}
+
+static PartitionValues *
+_copyPartitionValues(const PartitionValues *from)
+{
+ PartitionValues *newnode = makeNode(PartitionValues);
+
+ COPY_NODE_FIELD(listvalues);
+ COPY_NODE_FIELD(rangemaxs);
+ COPY_LOCATION_FIELD(location);
+
+ return newnode;
+}
+
/* ****************************************************************
* pg_list.h copy functions
* ****************************************************************
@@ -5044,6 +5072,12 @@ copyObject(const void *from)
case T_PartitionElem:
retval = _copyPartitionElem(from);
break;
+ case T_PartitionStmt:
+ retval = _copyPartitionStmt(from);
+ break;
+ case T_PartitionValues:
+ retval = _copyPartitionValues(from);
+ break;
default:
elog(ERROR, "unrecognized node type: %d", (int) nodeTag(from));
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index befff43..cfb5db6 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1160,6 +1160,7 @@ _equalCreateStmt(const CreateStmt *a, const CreateStmt *b)
COMPARE_SCALAR_FIELD(oncommit);
COMPARE_STRING_FIELD(tablespacename);
COMPARE_SCALAR_FIELD(if_not_exists);
+ COMPARE_SCALAR_FIELD(is_internal_partition);
return true;
}
@@ -2616,6 +2617,29 @@ _equalPartitionElem(const PartitionElem *a, const PartitionElem *b)
return true;
}
+static bool
+_equalPartitionStmt(const PartitionStmt *a, const PartitionStmt *b)
+{
+ COMPARE_NODE_FIELD(name);
+ COMPARE_NODE_FIELD(parent);
+ COMPARE_NODE_FIELD(using_table);
+ COMPARE_NODE_FIELD(values);
+ COMPARE_NODE_FIELD(options);
+ COMPARE_STRING_FIELD(tablespacename);
+
+ return true;
+}
+
+static bool
+_equalPartitionValues(const PartitionValues *a, const PartitionValues *b)
+{
+ COMPARE_NODE_FIELD(listvalues);
+ COMPARE_NODE_FIELD(rangemaxs);
+ COMPARE_LOCATION_FIELD(location);
+
+ return true;
+}
+
/*
* Stuff from pg_list.h
*/
@@ -3366,6 +3390,12 @@ equal(const void *a, const void *b)
case T_PartitionElem:
retval = _equalPartitionElem(a, b);
break;
+ case T_PartitionStmt:
+ retval = _equalPartitionStmt(a, b);
+ break;
+ case T_PartitionValues:
+ retval = _equalPartitionValues(a, b);
+ break;
default:
elog(ERROR, "unrecognized node type: %d",
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 9baf7cd..00b449d 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2170,6 +2170,7 @@ _outCreateStmtInfo(StringInfo str, const CreateStmt *node)
WRITE_ENUM_FIELD(oncommit, OnCommitAction);
WRITE_STRING_FIELD(tablespacename);
WRITE_BOOL_FIELD(if_not_exists);
+ WRITE_BOOL_FIELD(is_internal_partition);
}
static void
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 6e3471d..902bd40 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -226,8 +226,9 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
struct ImportQual *importqual;
InsertStmt *istmt;
VariableSetStmt *vsetstmt;
- PartitionElem *pelem;
+ PartitionElem *partelem;
PartitionBy *partby;
+ PartitionValues *partvalues;
}
%type <node> stmt schema_stmt
@@ -541,8 +542,11 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <boolean> opt_if_not_exists
%type <list> OptPartitionKey PartitionKey
%type <partby> PartitionBy SubPartitionBy
-%type <pelem> part_elem
-%type <list> part_params
+%type <partelem> part_elem
+%type <list> part_params
+%type <partvalues> ForValues
+%type <list> ValuesList
+%type <range> using_table opt_using_table
/*
* Non-keyword token types. These are hard-wired into the "flex" lexer.
@@ -568,7 +572,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
/* ordinary key words in alphabetical order */
%token <keyword> ABORT_P ABSOLUTE_P ACCESS ACTION ADD_P ADMIN AFTER
AGGREGATE ALL ALSO ALTER ALWAYS ANALYSE ANALYZE AND ANY ARRAY AS ASC
- ASSERTION ASSIGNMENT ASYMMETRIC AT ATTRIBUTE AUTHORIZATION
+ ASSERTION ASSIGNMENT ASYMMETRIC AT ATTACH ATTRIBUTE AUTHORIZATION
BACKWARD BEFORE BEGIN_P BETWEEN BIGINT BINARY BIT
BOOLEAN_P BOTH BY
@@ -583,7 +587,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
CURRENT_TIME CURRENT_TIMESTAMP CURRENT_USER CURSOR CYCLE
DATA_P DATABASE DAY_P DEALLOCATE DEC DECIMAL_P DECLARE DEFAULT DEFAULTS
- DEFERRABLE DEFERRED DEFINER DELETE_P DELIMITER DELIMITERS DESC
+ DEFERRABLE DEFERRED DEFINER DELETE_P DELIMITER DELIMITERS DESC DETACH
DICTIONARY DISABLE_P DISCARD DISTINCT DO DOCUMENT_P DOMAIN_P DOUBLE_P DROP
EACH ELSE ENABLE_P ENCODING ENCRYPTED END_P ENUM_P ESCAPE EVENT EXCEPT
@@ -607,10 +611,11 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
KEY
LABEL LANGUAGE LARGE_P LAST_P LATERAL_P
- LEADING LEAKPROOF LEAST LEFT LEVEL LIKE LIMIT LIST LISTEN LOAD LOCAL
+ LEADING LEAKPROOF LEAST LEFT LESS LEVEL LIKE LIMIT LIST LISTEN LOAD LOCAL
LOCALTIME LOCALTIMESTAMP LOCATION LOCK_P LOCKED LOGGED
- MAPPING MATCH MATERIALIZED MAXVALUE MINUTE_P MINVALUE MODE MONTH_P MOVE
+ MAPPING MATCH MATERIALIZED MAXVALUE MINUTE_P MINVALUE MODIFY MODE MONTH_P
+ MOVE
NAME_P NAMES NATIONAL NATURAL NCHAR NEXT NO NONE
NOT NOTHING NOTIFY NOTNULL NOWAIT NULL_P NULLIF
@@ -636,7 +641,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
STATEMENT STATISTICS STDIN STDOUT STORAGE STRICT_P STRIP_P SUBPARTITION
SUBSTRING SYMMETRIC SYSID SYSTEM_P
- TABLE TABLES TABLESAMPLE TABLESPACE TEMP TEMPLATE TEMPORARY TEXT_P THEN
+ TABLE TABLES TABLESAMPLE TABLESPACE TEMP TEMPLATE TEMPORARY TEXT_P THAN THEN
TIME TIMESTAMP TO TRAILING TRANSACTION TRANSFORM TREAT TRIGGER TRIM TRUE_P
TRUNCATE TRUSTED TYPE_P TYPES_P
@@ -2370,6 +2375,124 @@ alter_table_cmd:
n->def = (Node *)$1;
$$ = (Node *) n;
}
+ /* ALTER TABLE <name> ADD PARTITION */
+ | ADD_P PARTITION qualified_name ForValues OptWithPartition
+ OptTableSpace
+ {
+ AlterTableCmd *n = makeNode(AlterTableCmd);
+ PartitionStmt *stmt = makeNode(PartitionStmt);
+
+ n->subtype = AT_AddPartition;
+ stmt->name = $3;
+ stmt->values = $4;
+ stmt->options = $5;
+ stmt->tablespacename = $6;
+ n->def = (Node *) stmt;
+ $$ = (Node *) n;
+ }
+ /* ALTER TABLE <name> MODIFY PARTITION ADD SUBPARTITION */
+ | MODIFY PARTITION qualified_name ADD_P SUBPARTITION qualified_name
+ ForValues OptWithPartition OptTableSpace
+ {
+ AlterTableCmd *n = makeNode(AlterTableCmd);
+ PartitionStmt *stmt = makeNode(PartitionStmt);
+
+ n->subtype = AT_AddSubPartition;
+ stmt->name = $6;
+ stmt->parent = $3;
+ stmt->values = $7;
+ stmt->options = $8;
+ stmt->tablespacename = $9;
+ n->def = (Node *) stmt;
+
+ $$ = (Node *) n;
+ }
+ /* ALTER TABLE <name> DROP PARTITION */
+ | DROP PARTITION qualified_name
+ {
+ AlterTableCmd *n = makeNode(AlterTableCmd);
+ n->subtype = AT_DropPartition;
+ n->def = (Node *) $3;
+ $$ = (Node *) n;
+ }
+ /* ALTER TABLE <name> DROP SUBPARTITION */
+ | DROP SUBPARTITION qualified_name
+ {
+ AlterTableCmd *n = makeNode(AlterTableCmd);
+ n->subtype = AT_DropSubPartition;
+ n->def = (Node *) $3;
+ $$ = (Node *) n;
+ }
+ /* ALTER TABLE <name> ATTACH PARTITION USING TABLE */
+ | ATTACH PARTITION qualified_name ForValues using_table OptWithPartition
+ OptTableSpace
+ {
+ AlterTableCmd *n = makeNode(AlterTableCmd);
+ PartitionStmt *stmt = makeNode(PartitionStmt);
+
+ n->subtype = AT_AttachPartition;
+ stmt->name = $3;
+ stmt->values = $4;
+ stmt->using_table = $5;
+ stmt->options = $6;
+ stmt->tablespacename = $7;
+ n->def = (Node *) stmt;
+
+ $$ = (Node *) n;
+ }
+ /* ALTER TABLE <name> MODIFY PARTITION ATTACH SUBPARTITION USING TABLE */
+ | MODIFY PARTITION qualified_name ATTACH SUBPARTITION qualified_name
+ ForValues using_table OptWithPartition OptTableSpace
+ {
+ AlterTableCmd *n = makeNode(AlterTableCmd);
+ PartitionStmt *stmt = makeNode(PartitionStmt);
+
+ n->subtype = AT_AttachSubPartition;
+ stmt->name = $6;
+ stmt->parent = $3;
+ stmt->values = $7;
+ stmt->using_table = $8;
+ stmt->options = $9;
+ stmt->tablespacename = $10;
+ n->def = (Node *) stmt;
+
+ $$ = (Node *) n;
+ }
+ /* ALTER TABLE <name> DETACH PARTITION [ USING TABLE ] */
+ | DETACH PARTITION qualified_name opt_using_table
+ {
+ AlterTableCmd *n = makeNode(AlterTableCmd);
+ PartitionStmt *stmt = makeNode(PartitionStmt);
+
+ n->subtype = AT_DetachPartition;
+ stmt->name = $3;
+ stmt->using_table = $4;
+ n->def = (Node *) stmt;
+
+ $$ = (Node *) n;
+ }
+ /* ALTER TABLE <name> DETACH SUBPARTITION [ USING TABLE ] */
+ | DETACH SUBPARTITION qualified_name opt_using_table
+ {
+ AlterTableCmd *n = makeNode(AlterTableCmd);
+ PartitionStmt *stmt = makeNode(PartitionStmt);
+
+ n->subtype = AT_DetachSubPartition;
+ stmt->name = $3;
+ stmt->using_table = $4;
+ n->def = (Node *) stmt;
+
+ $$ = (Node *) n;
+ }
+ ;
+
+opt_using_table:
+ using_table { $$ = $1; }
+ | /* EMPTY */ { $$ = NULL; }
+ ;
+using_table:
+ /* USING [ TABLE ] qualified_name */
+ USING opt_table qualified_name { $$ = $3; }
;
alter_column_default:
@@ -2465,6 +2588,40 @@ reloption_elem:
}
;
+ForValues:
+ /* LIST partition */
+ FOR VALUES opt_in '(' ValuesList ')'
+ {
+ PartitionValues *n = makeNode(PartitionValues);
+
+ n->listvalues = $5;
+ n->rangemaxs = NIL;
+ n->location = @1;
+
+ $$ = n;
+ }
+
+ /* RANGE partition */
+ | FOR VALUES LESS THAN '(' ValuesList ')'
+ {
+ PartitionValues *n = makeNode(PartitionValues);
+
+ n->listvalues = NIL;
+ n->rangemaxs = $6;
+ n->location = @1;
+
+ $$ = n;
+ }
+ ;
+
+opt_in: IN_P {}
+ | /* EMPTY */ {}
+ ;
+
+ValuesList:
+ a_expr { $$ = list_make1($1); }
+ | ValuesList ',' a_expr { $$ = lappend($1, $3); }
+ ;
/*****************************************************************************
*
@@ -3525,6 +3682,12 @@ OptWith:
| /*EMPTY*/ { $$ = NIL; }
;
+/* WITH (options) only for partitions */
+OptWithPartition:
+ WITH reloptions { $$ = $2; }
+ | /*EMPTY*/ { $$ = NIL; }
+ ;
+
OnCommitOption: ON COMMIT DROP { $$ = ONCOMMIT_DROP; }
| ON COMMIT DELETE_P ROWS { $$ = ONCOMMIT_DELETE_ROWS; }
| ON COMMIT PRESERVE ROWS { $$ = ONCOMMIT_PRESERVE_ROWS; }
@@ -13767,6 +13930,7 @@ unreserved_keyword:
| ASSERTION
| ASSIGNMENT
| AT
+ | ATTACH
| ATTRIBUTE
| BACKWARD
| BEFORE
@@ -13812,6 +13976,7 @@ unreserved_keyword:
| DELETE_P
| DELIMITER
| DELIMITERS
+ | DETACH
| DICTIONARY
| DISABLE_P
| DISCARD
@@ -13872,6 +14037,7 @@ unreserved_keyword:
| LARGE_P
| LAST_P
| LEAKPROOF
+ | LESS
| LEVEL
| LIST
| LISTEN
@@ -13888,6 +14054,7 @@ unreserved_keyword:
| MINUTE_P
| MINVALUE
| MODE
+ | MODIFY
| MONTH_P
| MOVE
| NAME_P
@@ -13912,7 +14079,6 @@ unreserved_keyword:
| PARALLEL
| PARSER
| PARTIAL
- | PARTITION
| PASSING
| PASSWORD
| PLANS
@@ -13980,7 +14146,6 @@ unreserved_keyword:
| STORAGE
| STRICT_P
| STRIP_P
- | SUBPARTITION
| SYSID
| SYSTEM_P
| TABLES
@@ -13989,6 +14154,7 @@ unreserved_keyword:
| TEMPLATE
| TEMPORARY
| TEXT_P
+ | THAN
| TRANSACTION
| TRANSFORM
| TRIGGER
@@ -14185,6 +14351,7 @@ reserved_keyword:
| ONLY
| OR
| ORDER
+ | PARTITION
| PLACING
| PRIMARY
| REFERENCES
@@ -14192,6 +14359,7 @@ reserved_keyword:
| SELECT
| SESSION_USER
| SOME
+ | SUBPARTITION
| SYMMETRIC
| TABLE
| THEN
diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c
index 3d465c0..5420b20 100644
--- a/src/backend/parser/parse_agg.c
+++ b/src/backend/parser/parse_agg.c
@@ -458,7 +458,11 @@ check_agglevels_and_constraints(ParseState *pstate, Node *expr)
break;
case EXPR_KIND_PARTKEY_EXPRESSION:
- err = _("aggregate functions are not allowed in partition key expressions");
+ if (isAgg)
+ err = _("aggregate functions are not allowed in partition key expressions");
+ else
+ err = _("grouping operations are not allowed in partition key expressions");
+
break;
case EXPR_KIND_INDEX_PREDICATE:
if (isAgg)
@@ -488,6 +492,13 @@ check_agglevels_and_constraints(ParseState *pstate, Node *expr)
err = _("grouping operations are not allowed in trigger WHEN conditions");
break;
+ case EXPR_KIND_PARTITION_VALUE:
+ if (isAgg)
+ err = _("aggregate functions are not allowed in partition value expressions");
+ else
+ err = _("grouping operations are not allowed in partition value expressions");
+
+ break;
/*
* There is intentionally no default: case here, so that the
@@ -848,6 +859,9 @@ transformWindowFuncCall(ParseState *pstate, WindowFunc *wfunc,
case EXPR_KIND_TRIGGER_WHEN:
err = _("window functions are not allowed in trigger WHEN conditions");
break;
+ case EXPR_KIND_PARTITION_VALUE:
+ err = _("window functions are not allowed in partition value expressions");
+ break;
/*
* There is intentionally no default: case here, so that the
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index b115f66..49fca98 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -514,6 +514,12 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref)
return node;
}
+ if (pstate->p_expr_kind == EXPR_KIND_PARTITION_VALUE)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot use column reference in partition value expression"),
+ parser_errposition(pstate, cref->location)));
+
/*----------
* The allowed syntaxes are:
*
@@ -1718,6 +1724,9 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
case EXPR_KIND_TRIGGER_WHEN:
err = _("cannot use subquery in trigger WHEN condition");
break;
+ case EXPR_KIND_PARTITION_VALUE:
+ err = _("cannot use subquery in partition value expressions");
+ break;
/*
* There is intentionally no default: case here, so that the
@@ -3230,6 +3239,8 @@ ParseExprKindName(ParseExprKind exprKind)
return "WHEN";
case EXPR_KIND_PARTKEY_EXPRESSION:
return "partition key expression";
+ case EXPR_KIND_PARTITION_VALUE:
+ return "partition value expression";
/*
* There is intentionally no default: case here, so that the
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 02655da..ce8ed33 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -49,6 +49,7 @@
#include "nodes/nodeFuncs.h"
#include "parser/analyze.h"
#include "parser/parse_clause.h"
+#include "parser/parse_coerce.h"
#include "parser/parse_collate.h"
#include "parser/parse_expr.h"
#include "parser/parse_relation.h"
@@ -130,6 +131,10 @@ static void transformConstraintAttrs(CreateStmtContext *cxt,
List *constraintList);
static void transformColumnType(CreateStmtContext *cxt, ColumnDef *column);
static void setSchemaName(char *context_schema, char **stmt_schema_name);
+static void transformAddPartition(CreateStmtContext *cxt, PartitionStmt *stmt);
+static PartitionValues *transformPartitionValues(CreateStmtContext *cxt,
+ PartitionKey key,
+ PartitionValues *values);
/*
@@ -2660,6 +2665,41 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
break;
}
+ case AT_AddPartition:
+ case AT_AddSubPartition:
+ transformAddPartition(&cxt, (PartitionStmt *) cmd->def);
+ newcmds = lappend(newcmds, cmd);
+ break;
+ case AT_DropPartition:
+ case AT_DropSubPartition:
+ break;
+ case AT_AttachPartition:
+ case AT_AttachSubPartition:
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("ATTACH PARTITION/SUBPARTITION USING TABLE not implemented")));
+ break;
+ case AT_DetachPartition:
+ case AT_DetachSubPartition:
+ {
+ PartitionStmt *partstmt = (PartitionStmt *) cmd->def;
+
+ if (partstmt->using_table)
+ {
+ RenameStmt *stmt = makeNode(RenameStmt);
+
+ stmt->renameType = OBJECT_TABLE;
+ stmt->relation = partstmt->name;
+ stmt->newname = partstmt->using_table->relname;
+ stmt->missing_ok = false;
+
+ cxt.alist = lappend(cxt.alist, stmt);
+ }
+
+ newcmds = lappend(newcmds, cmd);
+ break;
+ }
+
default:
newcmds = lappend(newcmds, cmd);
break;
@@ -3024,3 +3064,169 @@ setSchemaName(char *context_schema, char **stmt_schema_name)
"different from the one being created (%s)",
*stmt_schema_name, context_schema)));
}
+
+/*
+ * transformAddPartition
+ * Transforms ADD PARTITION / SUBPARTITION
+ *
+ * Note that cxt->relation corresponds to the root partitioned table.
+ */
+static void
+transformAddPartition(CreateStmtContext *cxt, PartitionStmt *stmt)
+{
+ Relation parentRel;
+ bool is_subpartition = stmt->parent != NULL;
+ PartitionKey key;
+
+ /* Check if the target table is partitioned at all */
+ if (cxt->rel->rd_partkeys == NIL)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("\"%s\" is not partitioned", cxt->relation->relname)));
+
+ /* If adding a sub-partition, check if we can do so */
+ if (is_subpartition && list_length(cxt->rel->rd_partkeys) < 2)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("cannot add sub-partition"),
+ errdetail("\"%s\" is not sub-partitioned", cxt->relation->relname)));
+
+ if (stmt->parent)
+ {
+ parentRel = heap_openrv_extended(stmt->parent, NoLock, true);
+
+ if (parentRel == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("partition \"%s\" of \"%s\" does not exist",
+ stmt->parent->relname, cxt->relation->relname),
+ parser_errposition(cxt->pstate, stmt->parent->location)));
+ }
+ else
+ parentRel = heap_openrv(cxt->relation, NoLock);
+
+ /* tranform FOR VALUES clause */
+ key = !is_subpartition ? linitial(cxt->rel->rd_partkeys)
+ : lsecond(cxt->rel->rd_partkeys);
+ stmt->values = transformPartitionValues(cxt, key, stmt->values);
+
+ heap_close(parentRel, NoLock);
+}
+
+/*
+ * transformPartitionValues
+ *
+ * Transform partition value as returned by the grammar into something
+ * we can evaluate to store into pg_partition.
+ */
+static PartitionValues *
+transformPartitionValues(CreateStmtContext *cxt, PartitionKey key,
+ PartitionValues *values)
+{
+ int i;
+ ListCell *cell;
+ PartitionKeyTypeInfo *typinfo;
+ PartitionValues *result = (PartitionValues *)
+ makeNode(PartitionValues);
+
+ typinfo = get_partition_key_type_info(cxt->rel, key);
+
+ switch (key->strategy)
+ {
+ case PARTITION_STRAT_LIST:
+ if (!values->listvalues)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("invalid FOR VALUES specification for a list partition"),
+ parser_errposition(cxt->pstate, values->location)));
+
+ foreach(cell, values->listvalues)
+ {
+ Node *value = (Node *) lfirst(cell);
+ Node *orig_value = value;
+ Oid valuetype;
+
+ value = transformExpr(cxt->pstate, value,
+ EXPR_KIND_PARTITION_VALUE);
+ valuetype = exprType(value);
+ value = coerce_to_target_type(cxt->pstate,
+ value, valuetype,
+ typinfo->typid[0],
+ typinfo->typmod[0],
+ COERCION_ASSIGNMENT,
+ COERCE_IMPLICIT_CAST,
+ -1);
+ if (value == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("partition key column is of type %s"
+ " but expression is of type %s",
+ format_type_be(typinfo->typid[0]),
+ format_type_be(valuetype)),
+ errhint("You will need to rewrite or cast the expression."),
+ parser_errposition(cxt->pstate, exprLocation(orig_value))));
+
+ result->listvalues = lappend(result->listvalues, value);
+ }
+ break;
+
+ case PARTITION_STRAT_RANGE:
+ if (!values->rangemaxs)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("invalid FOR VALUES specification for a range partition"),
+ parser_errposition(cxt->pstate, values->location)));
+
+ if (list_length(values->rangemaxs) > key->partnatts)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("range partition FOR VALUES specification has more"
+ " expressions than columns in the partition key"),
+ parser_errposition(cxt->pstate,
+ exprLocation(list_nth(values->rangemaxs,
+ key->partnatts)))));
+ else if (list_length(values->rangemaxs) < key->partnatts)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("range partition FOR VALUES specification has fewer"
+ " expressions than columns in the partition key"),
+ parser_errposition(cxt->pstate,
+ exprLocation(list_nth(values->rangemaxs,
+ list_length(values->rangemaxs))))));
+ i = 0;
+ foreach(cell, values->rangemaxs)
+ {
+ Node *value = (Node *) lfirst(cell);
+ Node *orig_value = value;
+ Oid valuetype;
+
+ value = transformExpr(cxt->pstate, value,
+ EXPR_KIND_PARTITION_VALUE);
+ valuetype = exprType(value);
+ value = coerce_to_target_type(NULL,
+ value, valuetype,
+ typinfo->typid[i],
+ typinfo->typmod[i],
+ COERCION_ASSIGNMENT,
+ COERCE_IMPLICIT_CAST,
+ -1);
+ if (value == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("partition key column is of type %s"
+ " but expression is of type %s",
+ format_type_be(typinfo->typid[0]),
+ format_type_be(valuetype)),
+ errhint("You will need to rewrite or cast the expression."),
+ parser_errposition(cxt->pstate, exprLocation(orig_value))));
+
+ result->rangemaxs = lappend(result->rangemaxs, value);
+ ++i;
+ }
+ break;
+ }
+
+ result->location = values->location;
+
+ return result;
+}
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 825f7d9..8032f0d 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -435,6 +435,8 @@ typedef enum NodeTag
T_RoleSpec,
T_PartitionElem,
T_PartitionBy,
+ T_PartitionValues,
+ T_PartitionStmt,
/*
* TAGS FOR REPLICATION GRAMMAR PARSE NODES (replnodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 130410c..8128226 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -731,6 +731,31 @@ typedef struct PartitionBy
int location; /* token location, or -1 if unknown */
} PartitionBy;
+/*
+ * PartitionValues - partition FOR VALUES descriptor
+ */
+typedef struct PartitionValues
+{
+ NodeTag type;
+ List *listvalues;
+ List *rangemaxs;
+ int location; /* token location, or -1 if unknown */
+} PartitionValues;
+
+/*
+ * PartitionStmt - create new partition
+ */
+typedef struct PartitionStmt
+{
+ NodeTag type;
+ RangeVar *name; /* partition name */
+ RangeVar *parent; /* RangeVar node for the parent */
+ RangeVar *using_table; /* USING TABLE <name> */
+ PartitionValues *values; /* FOR VALUES clause descriptor */
+ List *options; /* options from WITH clause */
+ char *tablespacename; /* table space to use, or NULL */
+} PartitionStmt;
+
/****************************************************************************
* Nodes for a Query tree
****************************************************************************/
@@ -1557,7 +1582,16 @@ typedef enum AlterTableType
AT_DisableRowSecurity, /* DISABLE ROW SECURITY */
AT_ForceRowSecurity, /* FORCE ROW SECURITY */
AT_NoForceRowSecurity, /* NO FORCE ROW SECURITY */
- AT_GenericOptions /* OPTIONS (...) */
+ AT_GenericOptions, /* OPTIONS (...) */
+ AT_AddPartition, /* ADD PARTITION ... */
+ AT_AddSubPartition, /* MODIFY PARTITION ... ADD SUBPARTITION */
+ AT_DropPartition, /* DROP PARTITION */
+ AT_DropSubPartition, /* DROP SUBPARTITION */
+ AT_AttachPartition, /* ATTACH PARTITION .. USING TABLE .. */
+ AT_AttachSubPartition, /* MODIFY PARTITION ..
+ * ATTACH SUBPARTITION .. USING TABLE .. */
+ AT_DetachPartition, /* DETACH PARTITION .. USING TABLE .. */
+ AT_DetachSubPartition /* DETACH SUBPARTITION .. USING TABLE .. */
} AlterTableType;
typedef struct ReplicaIdentityStmt
@@ -1790,6 +1824,7 @@ typedef struct CreateStmt
OnCommitAction oncommit; /* what do we do at COMMIT? */
char *tablespacename; /* table space to use, or NULL */
bool if_not_exists; /* just do nothing if it already exists? */
+ bool is_internal_partition; /* relation is internal partition? */
} CreateStmt;
/* ----------
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 38c7afe..f56eaee 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -49,6 +49,7 @@ PG_KEYWORD("assertion", ASSERTION, UNRESERVED_KEYWORD)
PG_KEYWORD("assignment", ASSIGNMENT, UNRESERVED_KEYWORD)
PG_KEYWORD("asymmetric", ASYMMETRIC, RESERVED_KEYWORD)
PG_KEYWORD("at", AT, UNRESERVED_KEYWORD)
+PG_KEYWORD("attach", ATTACH, UNRESERVED_KEYWORD)
PG_KEYWORD("attribute", ATTRIBUTE, UNRESERVED_KEYWORD)
PG_KEYWORD("authorization", AUTHORIZATION, TYPE_FUNC_NAME_KEYWORD)
PG_KEYWORD("backward", BACKWARD, UNRESERVED_KEYWORD)
@@ -126,6 +127,7 @@ PG_KEYWORD("delete", DELETE_P, UNRESERVED_KEYWORD)
PG_KEYWORD("delimiter", DELIMITER, UNRESERVED_KEYWORD)
PG_KEYWORD("delimiters", DELIMITERS, UNRESERVED_KEYWORD)
PG_KEYWORD("desc", DESC, RESERVED_KEYWORD)
+PG_KEYWORD("detach", DETACH, UNRESERVED_KEYWORD)
PG_KEYWORD("dictionary", DICTIONARY, UNRESERVED_KEYWORD)
PG_KEYWORD("disable", DISABLE_P, UNRESERVED_KEYWORD)
PG_KEYWORD("discard", DISCARD, UNRESERVED_KEYWORD)
@@ -223,6 +225,7 @@ PG_KEYWORD("leading", LEADING, RESERVED_KEYWORD)
PG_KEYWORD("leakproof", LEAKPROOF, UNRESERVED_KEYWORD)
PG_KEYWORD("least", LEAST, COL_NAME_KEYWORD)
PG_KEYWORD("left", LEFT, TYPE_FUNC_NAME_KEYWORD)
+PG_KEYWORD("less", LESS, UNRESERVED_KEYWORD)
PG_KEYWORD("level", LEVEL, UNRESERVED_KEYWORD)
PG_KEYWORD("like", LIKE, TYPE_FUNC_NAME_KEYWORD)
PG_KEYWORD("limit", LIMIT, RESERVED_KEYWORD)
@@ -243,6 +246,7 @@ PG_KEYWORD("maxvalue", MAXVALUE, UNRESERVED_KEYWORD)
PG_KEYWORD("minute", MINUTE_P, UNRESERVED_KEYWORD)
PG_KEYWORD("minvalue", MINVALUE, UNRESERVED_KEYWORD)
PG_KEYWORD("mode", MODE, UNRESERVED_KEYWORD)
+PG_KEYWORD("modify", MODIFY, UNRESERVED_KEYWORD)
PG_KEYWORD("month", MONTH_P, UNRESERVED_KEYWORD)
PG_KEYWORD("move", MOVE, UNRESERVED_KEYWORD)
PG_KEYWORD("name", NAME_P, UNRESERVED_KEYWORD)
@@ -285,7 +289,7 @@ PG_KEYWORD("owner", OWNER, UNRESERVED_KEYWORD)
PG_KEYWORD("parallel", PARALLEL, UNRESERVED_KEYWORD)
PG_KEYWORD("parser", PARSER, UNRESERVED_KEYWORD)
PG_KEYWORD("partial", PARTIAL, UNRESERVED_KEYWORD)
-PG_KEYWORD("partition", PARTITION, UNRESERVED_KEYWORD)
+PG_KEYWORD("partition", PARTITION, RESERVED_KEYWORD)
PG_KEYWORD("passing", PASSING, UNRESERVED_KEYWORD)
PG_KEYWORD("password", PASSWORD, UNRESERVED_KEYWORD)
PG_KEYWORD("placing", PLACING, RESERVED_KEYWORD)
@@ -368,7 +372,7 @@ PG_KEYWORD("stdout", STDOUT, UNRESERVED_KEYWORD)
PG_KEYWORD("storage", STORAGE, UNRESERVED_KEYWORD)
PG_KEYWORD("strict", STRICT_P, UNRESERVED_KEYWORD)
PG_KEYWORD("strip", STRIP_P, UNRESERVED_KEYWORD)
-PG_KEYWORD("subpartition", SUBPARTITION, UNRESERVED_KEYWORD)
+PG_KEYWORD("subpartition", SUBPARTITION, RESERVED_KEYWORD)
PG_KEYWORD("substring", SUBSTRING, COL_NAME_KEYWORD)
PG_KEYWORD("symmetric", SYMMETRIC, RESERVED_KEYWORD)
PG_KEYWORD("sysid", SYSID, UNRESERVED_KEYWORD)
@@ -381,6 +385,7 @@ PG_KEYWORD("temp", TEMP, UNRESERVED_KEYWORD)
PG_KEYWORD("template", TEMPLATE, UNRESERVED_KEYWORD)
PG_KEYWORD("temporary", TEMPORARY, UNRESERVED_KEYWORD)
PG_KEYWORD("text", TEXT_P, UNRESERVED_KEYWORD)
+PG_KEYWORD("than", THAN, UNRESERVED_KEYWORD)
PG_KEYWORD("then", THEN, RESERVED_KEYWORD)
PG_KEYWORD("time", TIME, COL_NAME_KEYWORD)
PG_KEYWORD("timestamp", TIMESTAMP, COL_NAME_KEYWORD)
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index f6f3f52..054cd37 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -65,7 +65,8 @@ typedef enum ParseExprKind
EXPR_KIND_EXECUTE_PARAMETER, /* parameter value in EXECUTE */
EXPR_KIND_TRIGGER_WHEN, /* WHEN condition in CREATE TRIGGER */
EXPR_KIND_POLICY, /* USING or WITH CHECK expr in policy */
- EXPR_KIND_PARTKEY_EXPRESSION /* partition key expression */
+ EXPR_KIND_PARTKEY_EXPRESSION, /* partition key expression */
+ EXPR_KIND_PARTITION_VALUE
} ParseExprKind;
--
1.7.1
[text/x-diff] 0007-Add-infrastructure-to-actually-create-and-manage-par.patch (81.0K, ../../56C1302D.6070403@lab.ntt.co.jp/8-0007-Add-infrastructure-to-actually-create-and-manage-par.patch)
download | inline diff:
From 6c56eb3caf49ac07795809e2e169753cd1388a41 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Wed, 10 Feb 2016 16:36:16 +0900
Subject: [PATCH 07/10] Add infrastructure to actually create and manage partitions.
This primarily extends catalog/partition.h API to include functions for
storing and removing pg_partition entries. It further includes inquiry
functions for perusing information stored in pg_partition.
catalog/heap.c now knows a couple of new things: to drop partitions along
with a partitioned heap, and to distinguish RELKIND_RELATION relations
that are "internal partitions" from those that are not.
tablecmds.c now implements AT commands for which syntax was added in the
last commit like adding, dropping, detaching partitions/sub-partitions. When
adding a partition, a pg_class relation with matching tuple descriptor
(including dropped columns) is first created followed by creating necessary
pg_partition entry. Dropping has a similar story. Detach simply drops the
pg_partition entry so that it is no longer returned as a partition for
its parent. Attaching table as a partition is not implemented.
Certain AT commands are outright disallowed for partitions. Those include
add/drop/rename column, set/drop not null, alter column type, add check
constraint. Further, all of these commands, except the last, when perfomed
on a partitioned table are recursively applied to all of its partitions.
OTOH, when a check constraint is added or a not valid constraint validated,
it is "checked" for rows in its leaf partitions.
COPY FROM, INSERT into and UPDATE on partitions is disallowed.
Finally, one can request a PartitionDesc on a partitioned table (top-level
or internal) which consists of C array of values of its partitions.
If list partitioned, values are pointers to C arrays of literal values per
partition, and if range partitioned, values are individual rangemax values
themselves (sorted in ascending order). In the range partitioned case, there
are as many arrays as number of key columns. It also contains an array of
partition OIDs (laid out in order of values). The resulting structure is
cached in rd_partdesc. Currently, the only user of this data structure is
add partition command which uses it to verify that new partition does not
overlap with existing partitions.
---
src/backend/access/common/tupdesc.c | 6 +
src/backend/bootstrap/bootparse.y | 6 +-
src/backend/catalog/heap.c | 28 +-
src/backend/catalog/index.c | 5 +-
src/backend/catalog/partition.c | 999 +++++++++++++++++++++++++++-
src/backend/catalog/toasting.c | 3 +-
src/backend/commands/cluster.c | 5 +-
src/backend/commands/copy.c | 16 +-
src/backend/commands/tablecmds.c | 694 +++++++++++++++++++-
src/backend/executor/execMain.c | 25 +
src/backend/nodes/copyfuncs.c | 2 +
src/backend/nodes/equalfuncs.c | 2 +
src/backend/nodes/outfuncs.c | 2 +
src/backend/parser/gram.y | 1 +
src/backend/parser/parse_utilcmd.c | 109 +++-
src/include/catalog/heap.h | 6 +-
src/include/catalog/partition.h | 55 ++
src/include/nodes/parsenodes.h | 5 +
src/include/utils/rel.h | 2 +
src/test/regress/expected/sanity_check.out | 1 +
20 files changed, 1919 insertions(+), 53 deletions(-)
diff --git a/src/backend/access/common/tupdesc.c b/src/backend/access/common/tupdesc.c
index b56d0e3..af79a5a 100644
--- a/src/backend/access/common/tupdesc.c
+++ b/src/backend/access/common/tupdesc.c
@@ -648,6 +648,12 @@ BuildDescForRelation(List *schema)
has_not_null |= entry->is_not_null;
desc->attrs[attnum - 1]->attislocal = entry->is_local;
desc->attrs[attnum - 1]->attinhcount = entry->inhcount;
+
+ if (entry->is_dropped_copy)
+ {
+ Assert(entry->is_for_partition);
+ desc->attrs[attnum - 1]->attisdropped = true;
+ }
}
if (has_not_null)
diff --git a/src/backend/bootstrap/bootparse.y b/src/backend/bootstrap/bootparse.y
index 41d2fd4..f2c2ebb 100644
--- a/src/backend/bootstrap/bootparse.y
+++ b/src/backend/bootstrap/bootparse.y
@@ -224,7 +224,8 @@ Boot_CreateStmt:
RELPERSISTENCE_PERMANENT,
shared_relation,
mapped_relation,
- true);
+ true,
+ false);
elog(DEBUG4, "bootstrap relation created");
}
else
@@ -251,7 +252,8 @@ Boot_CreateStmt:
false,
true,
false,
- NULL);
+ NULL,
+ false);
elog(DEBUG4, "relation created with OID %u", id);
}
do_end();
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 1194611..e4e69f0 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -252,7 +252,8 @@ heap_create(const char *relname,
char relpersistence,
bool shared_relation,
bool mapped_relation,
- bool allow_system_table_mods)
+ bool allow_system_table_mods,
+ bool is_internal_partition)
{
bool create_storage;
Relation rel;
@@ -285,6 +286,9 @@ heap_create(const char *relname,
*/
switch (relkind)
{
+ case RELKIND_RELATION:
+ create_storage = !is_internal_partition;
+ break;
case RELKIND_VIEW:
case RELKIND_COMPOSITE_TYPE:
case RELKIND_FOREIGN_TABLE:
@@ -1045,7 +1049,8 @@ heap_create_with_catalog(const char *relname,
bool use_user_acl,
bool allow_system_table_mods,
bool is_internal,
- ObjectAddress *typaddress)
+ ObjectAddress *typaddress,
+ bool is_internal_partition)
{
Relation pg_class_desc;
Relation new_rel_desc;
@@ -1179,7 +1184,8 @@ heap_create_with_catalog(const char *relname,
relpersistence,
shared_relation,
mapped_relation,
- allow_system_table_mods);
+ allow_system_table_mods,
+ is_internal_partition);
Assert(relid == RelationGetRelid(new_rel_desc));
@@ -1824,12 +1830,26 @@ heap_drop_with_catalog(Oid relid)
RemovePartitionKeysByRelId(relid);
/*
+ * If a partition, delete the pg_partition tuple.
+ */
+ if (relid_is_partition(relid))
+ RemovePartitionEntryByRelId(relid);
+
+ /*
+ * If partitioned (either a RELKIND_PARTITIONED_REL relation or an internal
+ * partition), drop partitions.
+ */
+ if (relid_is_partitioned(relid))
+ RelationDropPartitions(relid);
+
+ /*
* Schedule unlinking of the relation's physical files at commit.
*/
if (rel->rd_rel->relkind != RELKIND_VIEW &&
rel->rd_rel->relkind != RELKIND_COMPOSITE_TYPE &&
rel->rd_rel->relkind != RELKIND_FOREIGN_TABLE &&
- rel->rd_rel->relkind != RELKIND_PARTITIONED_REL)
+ rel->rd_rel->relkind != RELKIND_PARTITIONED_REL &&
+ !relid_is_internal_partition(relid))
{
RelationDropStorage(rel);
}
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index e93aad4..e21a7a4 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -844,7 +844,8 @@ index_create(Relation heapRelation,
relpersistence,
shared_relation,
mapped_relation,
- allow_system_table_mods);
+ allow_system_table_mods,
+ false);
Assert(indexRelationId == RelationGetRelid(indexRelation));
@@ -3498,7 +3499,7 @@ reindex_relation(Oid relid, int flags, int options)
*/
rel = heap_open(relid, ShareLock);
- if (rel->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+ if (relid_is_partitioned(relid))
ereport(ERROR,
(errmsg("cannot reindex partitioned tables")));
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index 125f0a1..33a16eb 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -24,6 +24,7 @@
#include "catalog/partition.h"
#include "catalog/pg_collation.h"
#include "catalog/pg_opclass.h"
+#include "catalog/pg_partition.h"
#include "catalog/pg_partitioned_rel.h"
#include "catalog/pg_type.h"
#include "executor/executor.h"
@@ -39,6 +40,42 @@
#include "utils/lsyscache.h"
#include "utils/syscache.h"
+#define compare_using_func(func, val1, val2)\
+ DatumGetInt32(FunctionCall2Coll(&(func),\
+ DEFAULT_COLLATION_OID,\
+ (val1), (val2)))
+
+/*
+ * Information about a single partition
+ */
+typedef struct PartitionInfo
+{
+ Oid oid; /* Partition relation OID */
+ bool valid; /* is partition valid? */
+ int partnatts; /* qsort_arg's callback function depends on this */
+ int listnvalues; /* How many values does listvalues below hold */
+ Datum *listvalues; /* List partition values */
+ Datum *rangemaxs; /* Range partition max bound - one datum per
+ key column */
+} PartitionInfo;
+
+static int32 range_partition_cmp_max(const void *a, const void *b,
+ void *arg);
+static PartitionDesc CopyPartitionDesc(PartitionDesc src, int partnatts,
+ char strategy, PartitionKeyTypeInfo *typinfo);
+static PartitionInfo **GetRelPartitions(Relation rel, PartitionKey key,
+ PartitionKeyTypeInfo *typinfo, bool only_valid,
+ int *numparts);
+static void free_partitions(PartitionInfo **p, int num);
+static int oid_cmp(const void *p1, const void *p2);
+static List *get_leaf_partitions_recurse(Oid relid, int lockmode,
+ int nlevels, int levels_down);
+static int get_partition_level_internal(Oid relid, int level);
+static int32 rangemaxs_cmp_datum(const Datum *vals,
+ const Datum *rangemaxs,
+ int partnatts,
+ FmgrInfo *partsupfunc);
+
/*
* StorePartitionKey
* Store the partition keys of rel into pg_partitioned_rel catalog
@@ -193,7 +230,8 @@ relid_is_partitioned(Oid relid)
relkind = ((Form_pg_class) GETSTRUCT(tuple))->relkind;
ReleaseSysCache(tuple);
- return relkind == RELKIND_PARTITIONED_REL;
+ return relkind == RELKIND_PARTITIONED_REL ||
+ relid_is_internal_partition(relid);
}
/*
@@ -382,11 +420,11 @@ FreePartitionKeys(List *keys)
/*
* get_partition_key_type_info
+ * Returns a PartitionKeyTypeInfo object that includes type info
+ * for individual partition key attributes.
*
- * Returns a PartitionKeyTypeInfo object that includes type info
- * for individual partition key attributes.
- *
- * Separated out to avoid repeating the code.
+ * Note to callers: make sure to call this on root parents (that is,
+ * RELKIND_PARTITIONED_REL relations)
*/
PartitionKeyTypeInfo *
get_partition_key_type_info(Relation rel, PartitionKey key)
@@ -396,6 +434,8 @@ get_partition_key_type_info(Relation rel, PartitionKey key)
ListCell *partexprs_item;
PartitionKeyTypeInfo *result;
+ Assert(rel->rd_rel->relkind == RELKIND_PARTITIONED_REL);
+
partexprs = RelationGetPartitionExpr(rel, key->level);
result = (PartitionKeyTypeInfo *) palloc0(sizeof(PartitionKeyTypeInfo));
@@ -448,3 +488,952 @@ free_partition_key_type_info(PartitionKeyTypeInfo *typinfo)
pfree(typinfo->typalign);
pfree(typinfo);
}
+
+/*
+ * StorePartition
+ * Store information of new partition of rel into pg_partition
+ *
+ * Note that 'rel' always corresponds to the root partitioned table while
+ * 'parentOid' is OID of the actual parent of 'partition'.
+ */
+void
+StorePartition(Relation rel, PartitionKey key, PartitionKeyTypeInfo *typinfo,
+ Oid partitionOid,
+ Oid parentOid,
+ bool valid,
+ int listnvalues,
+ Datum *boundDatums)
+{
+ Relation pg_partition;
+ HeapTuple tuple;
+ ArrayType *arr_listvalues = NULL;
+ ArrayType *arr_rangemaxs = NULL;
+ Datum values[Natts_pg_partition];
+ bool nulls[Natts_pg_partition];
+
+ pg_partition = heap_open(PartitionRelationId, RowExclusiveLock);
+
+ /* Build the tuple */
+ MemSet(nulls, false, sizeof(nulls));
+
+ switch (key->strategy)
+ {
+ case PARTITION_STRAT_LIST:
+ Assert(listnvalues > 0);
+ arr_listvalues = construct_array(boundDatums,
+ listnvalues,
+ typinfo->typid[0],
+ typinfo->typlen[0],
+ typinfo->typbyval[0],
+ typinfo->typalign[0]);
+ /* No range bounds */
+ nulls[Anum_pg_partition_partrangemaxs - 1] = true;
+ break;
+
+ case PARTITION_STRAT_RANGE:
+ arr_rangemaxs = construct_array(boundDatums, key->partnatts,
+ ANYARRAYOID, -1, false, 'd');
+
+ /* No list values */
+ nulls[Anum_pg_partition_partlistvals - 1] = true;
+ break;
+ }
+
+ values[Anum_pg_partition_partrelid - 1] = ObjectIdGetDatum(partitionOid);
+ values[Anum_pg_partition_partparent - 1] = ObjectIdGetDatum(parentOid);
+ values[Anum_pg_partition_partvalid - 1] = BoolGetDatum(valid);
+ values[Anum_pg_partition_partlistvals - 1] = PointerGetDatum(arr_listvalues);
+ values[Anum_pg_partition_partrangemaxs - 1] = PointerGetDatum(arr_rangemaxs);
+
+ tuple = heap_form_tuple(RelationGetDescr(pg_partition), values, nulls);
+
+ simple_heap_insert(pg_partition, tuple);
+
+ /* Update the indexes on pg_partition */
+ CatalogUpdateIndexes(pg_partition, tuple);
+
+ heap_close(pg_partition, RowExclusiveLock);
+}
+
+/*
+ * RemovePartitionByRelId
+ * Remove a pg_partition entry for a partition.
+ */
+void
+RemovePartitionEntryByRelId(Oid relid)
+{
+ Relation partrel;
+ HeapTuple tuple;
+
+ partrel = heap_open(PartitionRelationId, RowExclusiveLock);
+ tuple = SearchSysCache1(PARTRELID, ObjectIdGetDatum(relid));
+
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "cache lookup failed for partition %u", relid);
+
+ simple_heap_delete(partrel, &tuple->t_self);
+
+ ReleaseSysCache(tuple);
+ heap_close(partrel, RowExclusiveLock);
+}
+
+/*
+ * RelationDropPartitions
+ * Find and drop all partitions of relation with OID 'relid'
+ */
+void
+RelationDropPartitions(Oid relid)
+{
+ List *partitionOids;
+ ListCell *cell;
+
+ partitionOids = get_partitions(relid, AccessExclusiveLock);
+
+ foreach (cell, partitionOids)
+ {
+ Oid partitionOid = lfirst_oid(cell);
+ ObjectAddress obj;
+
+ obj.classId = RelationRelationId;
+ obj.objectId = partitionOid;
+ obj.objectSubId = 0;
+
+ performDeletion(&obj, DROP_CASCADE, 0);
+ }
+}
+
+/*
+ * RelationGetPartitionDesc
+ * Get the PartitionDesc of a partitioned table
+ *
+ * The returned struct consists of array of OIDs of partitions, array of
+ * datums corresponding to the values of partition bounds and related info.
+ * Memory for the struct is allocated in the caller's memory context while
+ * a copy is cached in rd_partdesc so that we don't compute the result
+ * unnecessarily (We don't want to return a pointer to the relcache copy
+ * though, since it could disappear due to relcache invalidation.)
+ */
+PartitionDesc
+RelationGetPartitionDesc(Relation relation, PartitionKey key,
+ PartitionKeyTypeInfo *typinfo, bool include_all)
+{
+ int i,
+ j;
+ int numparts = 0;
+ MemoryContext oldcxt;
+ PartitionDesc result;
+ PartitionInfo **partitions;
+
+ /* Quick copy and exit if already computed the descriptor */
+ if (relation->rd_partdesc != NULL)
+ return CopyPartitionDesc(relation->rd_partdesc,
+ key->partnatts,
+ key->strategy,
+ typinfo);
+
+ result = (PartitionDesc) palloc0(sizeof(PartitionDescData));
+
+ partitions = GetRelPartitions(relation, key, typinfo, !include_all,
+ &numparts);
+ result->numparts = numparts;
+
+ if (partitions)
+ {
+ result->oids = (Oid *) palloc0(numparts * sizeof(Oid));
+ switch (key->strategy)
+ {
+ case PARTITION_STRAT_LIST:
+ result->listnvalues = (int *) palloc0(numparts * sizeof(int));
+ result->listvalues = (Datum **)
+ palloc0(numparts * sizeof(Datum *));
+ break;
+ case PARTITION_STRAT_RANGE:
+ for (i = 0; i < key->partnatts; i++)
+ result->rangemaxs[i] = (Datum *)
+ palloc0(numparts * sizeof(Datum));
+ break;
+ }
+ }
+
+ for (i = 0; i < numparts; i++)
+ {
+ result->oids[i] = partitions[i]->oid;
+
+ switch (key->strategy)
+ {
+ case PARTITION_STRAT_LIST:
+ /* Simply copy the list value datums for each partition */
+ result->listnvalues[i] = partitions[i]->listnvalues;
+ result->listvalues[i] = (Datum *)
+ palloc0(partitions[i]->listnvalues * sizeof(Datum));
+ for (j = 0; j < result->listnvalues[i]; j++)
+ {
+ Datum *from = &partitions[i]->listvalues[j];
+ Datum *into = &result->listvalues[i][j];
+
+ if (!typinfo->typbyval[0])
+ {
+ if (typinfo->typlen[0] == -1)
+ *into = PointerGetDatum(PG_DETOAST_DATUM_COPY(*from));
+ else
+ *into = datumCopy(*from, false, typinfo->typlen[0]);
+ }
+ else
+ *into = *from;
+ }
+ break;
+
+ case PARTITION_STRAT_RANGE:
+ /* Sort on rangemaxs before copying */
+ qsort_arg(partitions, numparts, sizeof(PartitionInfo *),
+ range_partition_cmp_max,
+ key->partsupfunc);
+
+ for (j = 0; j < key->partnatts; j++)
+ {
+ Datum *from = &partitions[i]->rangemaxs[j];
+ Datum *into = &result->rangemaxs[j][i];
+
+ if (!typinfo->typbyval[j])
+ {
+ if (typinfo->typlen[j] == -1)
+ *into = PointerGetDatum(PG_DETOAST_DATUM_COPY(*from));
+ else
+ *into = datumCopy(*from, false, typinfo->typlen[j]);
+ }
+ else
+ *into = *from;
+ }
+ break;
+ }
+ }
+
+ /* Clean up after yourself */
+ if (partitions)
+ free_partitions(partitions, numparts);
+
+ /* Save a copy in the relcache */
+ relation->rd_pdcxt = AllocSetContextCreate(CacheMemoryContext,
+ RelationGetRelationName(relation),
+ ALLOCSET_DEFAULT_MINSIZE,
+ ALLOCSET_DEFAULT_INITSIZE,
+ ALLOCSET_DEFAULT_MAXSIZE);
+
+ relation->rd_partdesc = MemoryContextAllocZero(relation->rd_pdcxt,
+ sizeof(PartitionDescData));
+ oldcxt = MemoryContextSwitchTo(relation->rd_pdcxt);
+ relation->rd_partdesc = CopyPartitionDesc(result,
+ key->partnatts,
+ key->strategy,
+ typinfo);
+ MemoryContextSwitchTo(oldcxt);
+
+ return result;
+}
+
+/*
+ * range_partition_cmp_max
+ * Compare two PartitionBoundInfos based on rangemaxs
+ *
+ * Used as a callback to qsort_arg for sorting range partitions in
+ * ascending order of their rangemaxs values.
+ */
+static int32
+range_partition_cmp_max(const void *a, const void *b, void *arg)
+{
+ int i;
+ int32 result;
+ FmgrInfo *cmpfn = (FmgrInfo *) arg;
+ int natts = (*(const PartitionInfo **) a)->partnatts;
+ const PartitionInfo *arg1 = *(const PartitionInfo **) a;
+ const PartitionInfo *arg2 = *(const PartitionInfo **) b;
+
+ for (i = 0; i < natts; i++)
+ {
+ result = DatumGetInt32(FunctionCall2Coll(&cmpfn[i],
+ DEFAULT_COLLATION_OID,
+ arg1->rangemaxs[i], arg2->rangemaxs[i]));
+
+ /* consider multicolumn range partitions */
+ if (!result)
+ continue;
+ else
+ return result;
+ }
+
+ return 0;
+}
+
+/*
+ * CopyPartitionDesc
+ * Copy PartitionDesc of a partitioned relation into caller's context
+ */
+static PartitionDesc
+CopyPartitionDesc(PartitionDesc src, int partnatts, char strategy,
+ PartitionKeyTypeInfo *typinfo)
+{
+ PartitionDesc result;
+ int i, j;
+
+ result = (PartitionDesc) palloc0(sizeof(PartitionDescData));
+ result->numparts = src->numparts;
+
+ /* If there are partitions, prepare to copy bound arrays */
+ if (result->numparts > 0)
+ {
+ result->oids = (Oid *) palloc0(result->numparts * sizeof(Oid));
+ switch (strategy)
+ {
+ case PARTITION_STRAT_LIST:
+ result->listnvalues = (int *)
+ palloc0(result->numparts * sizeof(int));
+ result->listvalues = (Datum **)
+ palloc0(result->numparts * sizeof(Datum *));
+ break;
+ case PARTITION_STRAT_RANGE:
+ for (i = 0; i < partnatts; i++)
+ result->rangemaxs[i] = (Datum *)
+ palloc0(result->numparts * sizeof(Datum));
+ break;
+ }
+ }
+
+ for (i = 0; i < result->numparts; i++)
+ {
+ result->oids[i] = src->oids[i];
+
+ switch (strategy)
+ {
+ case PARTITION_STRAT_LIST:
+ result->listnvalues[i] = src->listnvalues[i];
+ result->listvalues[i] = (Datum *)
+ palloc0(result->listnvalues[i] * sizeof(Datum));
+
+ for (j = 0; j < result->listnvalues[i]; j++)
+ {
+ Datum *from = &src->listvalues[i][j];
+ Datum *into = &result->listvalues[i][j];
+
+ if (!typinfo->typbyval[0])
+ {
+ if (typinfo->typlen[0] == -1)
+ *into = PointerGetDatum(PG_DETOAST_DATUM_COPY(*from));
+ else
+ *into = datumCopy(*from, false, typinfo->typlen[0]);
+ }
+ else
+ *into = *from;
+ }
+ break;
+
+ case PARTITION_STRAT_RANGE:
+ for (j = 0; j < partnatts; j++)
+ {
+ Datum *from = &src->rangemaxs[j][i];
+ Datum *into = &result->rangemaxs[j][i];
+
+ if (!typinfo->typbyval[j])
+ {
+ if (typinfo->typlen[j] == -1)
+ *into = PointerGetDatum(PG_DETOAST_DATUM_COPY(*from));
+ else
+ *into = datumCopy(*from, false, typinfo->typlen[j]);
+ }
+ else
+ *into = *from;
+ }
+ break;
+ }
+ }
+
+ return result;
+}
+
+/*
+ * GetRelPartitions
+ * Return information of partitions of rel
+ *
+ * If only_valid is 'true', only return partitions that are marked valid
+ * in pg_partition.
+ *
+ * Returns number of partitions in *numparts.
+ */
+static PartitionInfo **
+GetRelPartitions(Relation rel, PartitionKey key, PartitionKeyTypeInfo *typinfo,
+ bool only_valid, int *numparts)
+{
+ List *partoids;
+ int i;
+ ListCell *cell;
+ PartitionInfo **partitions;
+
+ partoids = get_partitions(RelationGetRelid(rel), NoLock);
+
+ if ((*numparts = list_length(partoids)) == 0)
+ return NULL;
+
+ partitions = (PartitionInfo **) palloc0(*numparts *
+ sizeof(PartitionInfo *));
+ i = 0;
+ foreach(cell, partoids)
+ {
+ HeapTuple tuple;
+ Form_pg_partition form;
+ int j;
+ int rangenmaxs;
+ Datum *rangemaxs;
+ Datum datum;
+ bool isnull;
+ Oid partrelid;
+ PartitionInfo *result = NULL;
+
+ partrelid = lfirst_oid(cell);
+
+ tuple = SearchSysCache1(PARTRELID, partrelid);
+ Assert(HeapTupleIsValid(tuple));
+
+ form = (Form_pg_partition) GETSTRUCT(tuple);
+
+ if (only_valid && !form->partvalid)
+ continue;
+
+ result = (PartitionInfo *) palloc0(sizeof(PartitionInfo));
+ result->oid = partrelid;
+ result->partnatts = key->partnatts;
+
+ datum = SysCacheGetAttr(PARTRELID, tuple,
+ Anum_pg_partition_partlistvals, &isnull);
+ if (!isnull)
+ {
+ deconstruct_array(DatumGetArrayTypeP(datum),
+ typinfo->typid[0],
+ typinfo->typlen[0],
+ typinfo->typbyval[0],
+ typinfo->typalign[0],
+ &result->listvalues, NULL, &result->listnvalues);
+
+ ReleaseSysCache(tuple);
+ partitions[i++] = result;
+ continue;
+ }
+
+ /* Must be a range partition */
+ datum = SysCacheGetAttr(PARTRELID, tuple,
+ Anum_pg_partition_partrangemaxs, &isnull);
+ Assert(!isnull);
+
+ /*
+ * Each element of rangemaxs (any) array is itself an array
+ * containing rangemax value for a given key column.
+ */
+ deconstruct_array(DatumGetArrayTypeP(datum),
+ ANYARRAYOID, -1, false, 'd',
+ &rangemaxs, NULL, &rangenmaxs);
+ /* Paranoia */
+ Assert(rangenmaxs = result->partnatts);
+
+ result->rangemaxs = (Datum *) palloc0(key->partnatts * sizeof(Datum));
+ for (j = 0; j < result->partnatts; j++)
+ {
+ ArrayType *arr = DatumGetArrayTypeP(rangemaxs[j]);
+ Datum *datum;
+ bool *nulls;
+ int dummy;
+
+ deconstruct_array(arr,
+ typinfo->typid[j],
+ typinfo->typlen[j],
+ typinfo->typbyval[j],
+ typinfo->typalign[j],
+ &datum, &nulls, &dummy);
+ Assert(!nulls[0]);
+
+ result->rangemaxs[j] = datum[0];
+ }
+ ReleaseSysCache(tuple);
+
+ partitions[i++] = result;
+ }
+
+ return partitions;
+}
+
+/*
+ * free_partitions
+ * Free the PartitionInfo array (presumably the one that was built
+ * using GetRelPartitions)
+ */
+static void
+free_partitions(PartitionInfo **p, int num)
+{
+ int i;
+
+ for (i = 0; i < num; i++)
+ {
+ if (p[i]->rangemaxs)
+ pfree(p[i]->rangemaxs);
+ if (p[i]->listvalues)
+ pfree(p[i]->listvalues);
+ pfree(p[i]);
+ }
+
+ pfree(p);
+}
+
+/*
+ * get_partitions
+ * Returns a list of OIDs of partitions of relation with OID relid
+ *
+ * Partitions are locked in an order that follows the sorted order of
+ * their OIDs.
+ */
+List *
+get_partitions(Oid relid, int lockmode)
+{
+ List *list = NIL;
+ Relation partitionRel;
+ SysScanDesc scan;
+ ScanKeyData skey[1];
+ HeapTuple tuple;
+ Oid partrelid;
+ Oid *oidarr;
+ int maxoids,
+ numoids,
+ i;
+
+ /* Scan pg_partition and build a working array of partition OIDs */
+ maxoids = 64;
+ oidarr = (Oid *) palloc(maxoids * sizeof(Oid));
+ numoids = 0;
+
+ partitionRel = heap_open(PartitionRelationId, AccessShareLock);
+ ScanKeyInit(&skey[0],
+ Anum_pg_partition_partparent,
+ BTEqualStrategyNumber, F_OIDEQ,
+ relid);
+
+ scan = systable_beginscan(partitionRel, PartitionParentIndexId, true,
+ NULL, 1, skey);
+
+ while ((tuple = systable_getnext(scan)) != NULL)
+ {
+ partrelid = ((Form_pg_partition) GETSTRUCT(tuple))->partrelid;
+ if (numoids >= maxoids)
+ {
+ maxoids *= 2;
+ oidarr = (Oid *) repalloc(oidarr, maxoids * sizeof(Oid));
+ }
+ oidarr[numoids++] = partrelid;
+ }
+
+ systable_endscan(scan);
+
+ heap_close(partitionRel, AccessShareLock);
+
+ /*
+ * If we found more than one partition, sort them by OID. This is important
+ * since we need to be sure all backends lock partitions in the same order
+ * to avoid needless deadlocks.
+ */
+ if (numoids > 1)
+ qsort(oidarr, numoids, sizeof(Oid), oid_cmp);
+
+ /* Acquire locks and build the result list. */
+ for (i = 0; i < numoids; i++)
+ {
+ partrelid = oidarr[i];
+
+ if (lockmode != NoLock)
+ {
+ /* Get the lock to synchronize against concurrent drop */
+ LockRelationOid(partrelid, lockmode);
+
+ /*
+ * Now that we have the lock, double-check to see if the relation
+ * really exists or not. If not, assume it was dropped while we
+ * waited to acquire lock, and ignore it.
+ */
+ if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(partrelid)))
+ {
+ /* Release useless lock */
+ UnlockRelationOid(partrelid, lockmode);
+ /* And ignore this relation */
+ continue;
+ }
+ }
+
+ list = lappend_oid(list, partrelid);
+ }
+
+ pfree(oidarr);
+
+ return list;
+}
+
+/* qsort comparison function */
+static int
+oid_cmp(const void *p1, const void *p2)
+{
+ Oid v1 = *((const Oid *) p1);
+ Oid v2 = *((const Oid *) p2);
+
+ if (v1 < v2)
+ return -1;
+ if (v1 > v2)
+ return 1;
+ return 0;
+}
+
+/*
+ * get_all_partitions
+ * Returns a list of OIDs of partitions of relation with OID relid
+ * including any partitions of their own and so on
+ *
+ * Note: should be called only on RELKIND_PARTITIONED_REL relations.
+ */
+List *
+get_all_partitions(Oid relid, int lockmode)
+{
+ List *list;
+ ListCell *cell;
+
+ /* Initialize the list with all partitions at level 1 */
+ list = get_partitions(relid, lockmode);
+
+ /*
+ * Recursively add partitions of further levels in what is effectively
+ * a breadth-first manner.
+ */
+ foreach (cell, list)
+ {
+ Oid partOid = lfirst_oid(cell);
+
+ list = list_concat(list, get_partitions(partOid, lockmode));
+ }
+
+ return list;
+}
+
+/*
+ * get_leaf_partitions
+ * Returns a list of OIDs of leaf level partitions of relation with
+ * OID 'relid'
+ *
+ * Note to callers: make sure to call this on RELKIND_PARTITIONED_REL
+ * relations or internal partitions;
+ */
+List *
+get_leaf_partitions(Oid relid, int lockmode)
+{
+ Relation rel;
+ int nlevels;
+
+ rel = heap_open(relid, AccessShareLock);
+
+ if (rel->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+ nlevels = list_length(rel->rd_partkeys);
+ else
+ {
+ Oid rootParent;
+ Relation rootParentRel;
+
+ Assert(relid_is_internal_partition(relid));
+
+ rootParent = get_partition_root_parent(relid);
+ rootParentRel = heap_open(rootParent, AccessShareLock);
+
+ nlevels = list_length(rootParentRel->rd_partkeys) -
+ get_partition_level(relid);
+
+ heap_close(rootParentRel, AccessShareLock);
+ }
+ heap_close(rel, AccessShareLock);
+
+ return get_leaf_partitions_recurse(relid, lockmode, nlevels, 0);
+}
+
+/*
+ * get_leaf_partitions_recurse
+ * Returns leaf level partitions of relation with OID 'relid'
+ */
+static List *
+get_leaf_partitions_recurse(Oid relid, int lockmode, int nlevels,
+ int levels_down)
+{
+ List *partitions,
+ *result = NIL;
+ ListCell *cell;
+
+ /*
+ * If we got to level just 1 above the leaf level, return our partitions
+ * as leaf partitions.
+ */
+ if (levels_down == nlevels - 1)
+ return get_partitions(relid, lockmode);
+
+ /* Not there yet, so move one-level down */
+ partitions = get_partitions(relid, lockmode);
+ foreach (cell, partitions)
+ {
+ Oid partOid = lfirst_oid(cell);
+
+ result = list_concat(result, get_leaf_partitions_recurse(partOid,
+ lockmode, nlevels,
+ levels_down + 1));
+ }
+
+ return result;
+}
+
+/*
+ * get_partition_parent
+ * Get OID of the parent of partition with OID 'relid'
+ */
+Oid
+get_partition_parent(Oid relid)
+{
+ HeapTuple tuple;
+ Oid result;
+
+ tuple = SearchSysCache1(PARTRELID, ObjectIdGetDatum(relid));
+
+ if (!HeapTupleIsValid(tuple))
+ return InvalidOid;
+
+ result = ((Form_pg_partition) GETSTRUCT(tuple))->partparent;
+ ReleaseSysCache(tuple);
+
+ return result;
+}
+
+/*
+ * get_partition_root_parent
+ * Get OID of the root parent of partition with OID 'relid'
+ */
+Oid
+get_partition_root_parent(Oid relid)
+{
+ Oid parentOid;
+
+ parentOid = get_partition_parent(relid);
+
+ if (!OidIsValid(parentOid))
+ return relid;
+
+ return get_partition_root_parent(parentOid);
+}
+
+/*
+ * get_partition_level
+ * Returns the level of partition with OID 'relid'
+ *
+ * Special case: returns 0 for the root of a partitioning tree.
+ */
+int get_partition_level(Oid relid)
+{
+ return get_partition_level_internal(relid, 0);
+}
+
+/*
+ * get_partition_level_internal
+ * Workhorse for public function get_partition_level
+ */
+static int
+get_partition_level_internal(Oid relid, int level)
+{
+ Oid parentOid;
+
+ parentOid = get_partition_parent(relid);
+
+ if (!OidIsValid(parentOid))
+ return level;
+
+ return get_partition_level_internal(parentOid, level + 1);
+}
+
+/*
+ * relid_is_partition
+ * Returns whether relation with OID relid is a partition
+ */
+bool
+relid_is_partition(Oid relid)
+{
+ return SearchSysCacheExists1(PARTRELID, ObjectIdGetDatum(relid));
+}
+
+/*
+ * relid_is_internal_partition
+ * Returns whether relation with OID relid is an internal partition
+ */
+bool
+relid_is_internal_partition(Oid relid)
+{
+ Relation rel;
+ Relation partitionRel;
+ Relation parentRel;
+ SysScanDesc scan;
+ ScanKeyData key[1];
+
+ /*
+ * Cannot be an internal partition if not a partition in the first
+ * place
+ */
+ if (!relid_is_partition(relid))
+ return false;
+
+ /*
+ * A non-NULL rd_partdesc means it has partitions, so it's not a
+ * leaf partiion.
+ */
+ rel = heap_open(relid, AccessShareLock);
+ if (rel->rd_partdesc != NULL)
+ {
+ heap_close(rel, AccessShareLock);
+ return true;
+ }
+ heap_close(rel, AccessShareLock);
+
+ /*
+ * rd_partdesc is NULL but if there exists at least one pg_partition
+ * tuple with 'relid' in its partparent column, then it's not a leaf
+ * partition.
+ */
+ ScanKeyInit(&key[0],
+ Anum_pg_partition_partparent,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(relid));
+
+ partitionRel = heap_open(PartitionRelationId, AccessShareLock);
+ scan = systable_beginscan(partitionRel,
+ PartitionParentIndexId,
+ true, NULL, 1, key);
+
+ if (HeapTupleIsValid(systable_getnext(scan)))
+ {
+ systable_endscan(scan);
+ heap_close(partitionRel, AccessShareLock);
+ return true;
+ }
+ systable_endscan(scan);
+ heap_close(partitionRel, AccessShareLock);
+
+ /*
+ * Finally, there are yet no (sub-) partitions created but there
+ * is a partition key for partitions of its level
+ */
+ parentRel = heap_open(get_partition_root_parent(relid), AccessShareLock);
+ if (list_length(parentRel->rd_partkeys) > get_partition_level(relid))
+ {
+ heap_close(parentRel, AccessShareLock);
+ return true;
+ }
+ heap_close(parentRel, AccessShareLock);
+
+ return false;
+}
+
+/*
+ * list_partition_overlaps
+ * Check whether list partition overlaps with existing partitions of rel
+ *
+ * If it does overlap, return the oid of the partition it overlaps with.
+ */
+bool
+list_partition_overlaps(PartitionDesc pdesc, PartitionKey key,
+ int listnvalues, Datum *listvalues,
+ Oid *overlapsWith)
+{
+ int i, j, k;
+ int32 result;
+ FmgrInfo *partsupfunc = key->partsupfunc;
+
+ for (i = 0; i < pdesc->numparts; i++)
+ {
+ int ilistnvalues = pdesc->listnvalues[i];
+ Datum *ilistvalues = pdesc->listvalues[i];
+ Oid iOid = pdesc->oids[i];
+
+ for (j = 0; j < listnvalues; j++)
+ {
+ Datum newDatum = listvalues[j];
+
+ for (k = 0; k < ilistnvalues; k++)
+ {
+ result = compare_using_func(partsupfunc[0],
+ newDatum,
+ ilistvalues[k]);
+ if (!result)
+ {
+ *overlapsWith = iOid;
+ return true;
+ }
+ }
+ }
+ }
+
+ return false;
+}
+
+/*
+ * range_partition_overlaps
+ * Check whether range partition overlaps with some existing partition
+ * of rel
+ *
+ * This amounts to comparing the new rangemaxs with that of the last partition.
+ * A new range partition should always be defined to have a rangemax that is
+ * greater than that of the last partition.
+ */
+bool
+range_partition_overlaps(PartitionDesc pdesc, PartitionKey key, Datum *rangemaxs)
+{
+ int i;
+ int32 cmpval;
+ Datum *rel_rangemaxs;
+ int partnatts = key->partnatts;
+ FmgrInfo *partsupfunc = key->partsupfunc;
+
+ if (pdesc->numparts == 0)
+ return false;
+
+ /* Get ahold of rangemaxs of the last partition of rel */
+ rel_rangemaxs = (Datum *) palloc0(partnatts * sizeof(Datum));
+
+ for (i = 0; i < partnatts; i++)
+ rel_rangemaxs[i] = pdesc->rangemaxs[i][pdesc->numparts - 1];
+
+ cmpval = rangemaxs_cmp_datum(rangemaxs,
+ rel_rangemaxs,
+ partnatts,
+ partsupfunc);
+
+ pfree(rel_rangemaxs);
+ return cmpval <= 0;
+}
+
+/*
+ * rangemaxs_cmp_datum
+ * Compare datum against rangemaxs of a range partition
+ *
+ * vals and rangemaxs are each 'natts' datums long. partsupfunc is an array
+ * of pointers to FmgrInfo structs, one per partition key column.
+ */
+static int32
+rangemaxs_cmp_datum(const Datum *vals, const Datum *rangemaxs,
+ int partnatts, FmgrInfo *partsupfunc)
+{
+ int i;
+ int32 result;
+
+ for (i = 0; i < partnatts; i++)
+ {
+ result = compare_using_func(partsupfunc[i], vals[i], rangemaxs[i]);
+
+ /* consider a multi-column key */
+ if (!result)
+ continue;
+ else
+ return result;
+ }
+
+ return 0;
+}
diff --git a/src/backend/catalog/toasting.c b/src/backend/catalog/toasting.c
index f40a005..91e08de 100644
--- a/src/backend/catalog/toasting.c
+++ b/src/backend/catalog/toasting.c
@@ -291,7 +291,8 @@ create_toast_table(Relation rel, Oid toastOid, Oid toastIndexOid,
false,
true,
true,
- NULL);
+ NULL,
+ false);
Assert(toast_relid != InvalidOid);
/* make the toast relation visible, else heap_open will fail */
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 25398ae..4c47d54 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -128,7 +128,7 @@ cluster(ClusterStmt *stmt, bool isTopLevel)
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot cluster temporary tables of other sessions")));
- if (rel->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+ if (relid_is_partitioned(tableOid))
ereport(ERROR,
(errmsg("cannot cluster partitioned tables")));
@@ -684,7 +684,8 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, char relpersistence,
false,
true,
true,
- NULL);
+ NULL,
+ false);
Assert(OIDNewHeap != InvalidOid);
ReleaseSysCache(tuple);
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 403738d..62b650f 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -1731,6 +1731,13 @@ BeginCopyTo(Relation rel,
RelationGetRelationName(rel))));
}
+ if (rel != NULL && relid_is_internal_partition(RelationGetRelid(rel)))
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot copy from internal partition \"%s\"",
+ RelationGetRelationName(rel)),
+ errhint("Try the COPY (SELECT ...) TO variant.")));
+
cstate = BeginCopy(false, rel, query, queryString, queryRelId, attnamelist,
options);
oldcontext = MemoryContextSwitchTo(cstate->copycontext);
@@ -2236,7 +2243,8 @@ CopyFrom(CopyState cstate)
Assert(cstate->rel);
- if (cstate->rel->rd_rel->relkind != RELKIND_RELATION)
+ if (cstate->rel->rd_rel->relkind != RELKIND_RELATION &&
+ cstate->rel->rd_rel->relkind != RELKIND_PARTITIONED_REL)
{
if (cstate->rel->rd_rel->relkind == RELKIND_VIEW)
ereport(ERROR,
@@ -2265,6 +2273,12 @@ CopyFrom(CopyState cstate)
RelationGetRelationName(cstate->rel))));
}
+ if (relid_is_partition(RelationGetRelid(cstate->rel)))
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot copy to partition \"%s\"",
+ RelationGetRelationName(cstate->rel))));
+
tupDesc = RelationGetDescr(cstate->rel);
/*----------
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 7bdbdf0..adfaa2c 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -39,6 +39,7 @@
#include "catalog/pg_inherits_fn.h"
#include "catalog/pg_namespace.h"
#include "catalog/pg_opclass.h"
+#include "catalog/partition.h"
#include "catalog/pg_tablespace.h"
#include "catalog/pg_trigger.h"
#include "catalog/pg_type.h"
@@ -85,6 +86,7 @@
#include "storage/smgr.h"
#include "utils/acl.h"
#include "utils/builtins.h"
+#include "utils/datum.h"
#include "utils/fmgroids.h"
#include "utils/inval.h"
#include "utils/lsyscache.h"
@@ -345,9 +347,10 @@ static void add_column_datatype_dependency(Oid relid, int32 attnum, Oid typid);
static void add_column_collation_dependency(Oid relid, int32 attnum, Oid collid);
static void ATPrepAddOids(List **wqueue, Relation rel, bool recurse,
AlterTableCmd *cmd, LOCKMODE lockmode);
-static ObjectAddress ATExecDropNotNull(Relation rel, const char *colName, LOCKMODE lockmode);
-static ObjectAddress ATExecSetNotNull(AlteredTableInfo *tab, Relation rel,
- const char *colName, LOCKMODE lockmode);
+static ObjectAddress ATExecDropNotNull(Relation rel, const char *colName, bool recursing,
+ LOCKMODE lockmode);
+static ObjectAddress ATExecSetNotNull(List **wqueue, AlteredTableInfo *tab, Relation rel,
+ const char *colName, bool recursing, LOCKMODE lockmode);
static ObjectAddress ATExecColumnDefault(Relation rel, const char *colName,
Node *newDefault, LOCKMODE lockmode);
static void ATPrepSetStatistics(Relation rel, const char *colName,
@@ -388,8 +391,9 @@ static void ATPrepAlterColumnType(List **wqueue,
bool recurse, bool recursing,
AlterTableCmd *cmd, LOCKMODE lockmode);
static bool ATColumnChangeRequiresRewrite(Node *expr, AttrNumber varattno);
-static ObjectAddress ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
- AlterTableCmd *cmd, LOCKMODE lockmode);
+static ObjectAddress ATExecAlterColumnType(List **wqueue, AlteredTableInfo *tab,
+ Relation rel, AlterTableCmd *cmd, bool recursing,
+ LOCKMODE lockmode);
static ObjectAddress ATExecAlterColumnGenericOptions(Relation rel, const char *colName,
List *options, LOCKMODE lockmode);
static void ATPostAlterTypeCleanup(List **wqueue, AlteredTableInfo *tab,
@@ -453,6 +457,17 @@ static void SetRelationSpcFileNode(Relation rel, Oid newTableSpace,
static void ATExecChangePersistence(List **wqueue, AlteredTableInfo *tab,
Relation rel, char persistence, LOCKMODE lockmode);
static void SetRelationPersistence(Oid relationId, char persistence);
+static Datum *EvalPartitionBound(Relation rootParent, Relation parent,
+ PartitionKey key, PartitionKeyTypeInfo *typinfo,
+ PartitionValues *values, int *listnvalues);
+static Datum *evalPartitionListBound(List *values, PartitionKeyTypeInfo *typinfo,
+ int nvalues);
+static Datum *evalPartitionRangeBound(List *values, PartitionKeyTypeInfo *typinfo,
+ int partnatts);
+static void ATExecAddPartition(Relation rootParent, RangeVar *name, RangeVar *parent,
+ PartitionValues *values);
+static void ATExecRemovePartition(Relation rootParent, RangeVar *name, bool is_detach,
+ bool is_subpart);
/* ----------------------------------------------------------------
@@ -700,7 +715,8 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
true,
allowSystemTableMods,
false,
- typaddress);
+ typaddress,
+ stmt->is_internal_partition);
/* Store inheritance information for new rel. */
StoreCatalogInheritance(relationId, inheritOids);
@@ -994,6 +1010,17 @@ RangeVarCallbackForDropRelation(const RangeVar *rel, Oid relOid, Oid oldRelOid,
if (!OidIsValid(relOid))
return;
+ if (relid_is_partition(relOid) && !relid_is_internal_partition(relOid))
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("\"%s\" is a sub-partition", rel->relname),
+ errhint("Use ALTER TABLE DROP SUBPARTITION to drop it.")));
+ else if (relid_is_partition(relOid))
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("\"%s\" is a partition", rel->relname),
+ errhint("Use ALTER TABLE DROP PARTITION to drop it.")));
+
tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relOid));
if (!HeapTupleIsValid(tuple))
return; /* concurrently dropped, so nothing to do */
@@ -1106,6 +1133,25 @@ ExecuteTruncate(TruncateStmt *stmt)
relids = lappend_oid(relids, childrelid);
}
}
+
+ if (relid_is_partitioned(myrelid))
+ {
+ ListCell *partition;
+ List *partitions;
+
+ partitions = get_leaf_partitions(myrelid, AccessExclusiveLock);
+
+ foreach(partition, partitions)
+ {
+ Oid partOid = lfirst_oid(partition);
+
+ /* get_leaf_partitions already got lock */
+ rel = heap_open(partOid, NoLock);
+ truncate_check_rel(rel);
+ rels = lappend(rels, rel);
+ relids = lappend_oid(relids, partOid);
+ }
+ }
}
/*
@@ -1234,6 +1280,9 @@ ExecuteTruncate(TruncateStmt *stmt)
{
Relation rel = (Relation) lfirst(cell);
+ if (relid_is_partitioned(RelationGetRelid(rel)))
+ continue;
+
/*
* Normally, we need a transaction-safe truncation here. However, if
* the table was either created in the current (sub)transaction or has
@@ -1345,7 +1394,8 @@ truncate_check_rel(Relation rel)
AclResult aclresult;
/* Only allow truncate on regular tables */
- if (rel->rd_rel->relkind != RELKIND_RELATION)
+ if (rel->rd_rel->relkind != RELKIND_RELATION &&
+ rel->rd_rel->relkind != RELKIND_PARTITIONED_REL)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not a table",
@@ -2213,6 +2263,11 @@ renameatt_check(Oid myrelid, Form_pg_class classform, bool recursing)
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot rename column of typed table")));
+ if (relid_is_partition(myrelid) && !recursing)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot rename column of a partition")));
+
/*
* Renaming the columns of sequences or toast tables doesn't actually
* break anything from the system's point of view, since internal
@@ -2327,6 +2382,21 @@ renameatt_internal(Oid myrelid,
oldattname)));
}
+ /* Propagate to all partitions */
+ if (targetrelation->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+ {
+ List *partitions;
+ ListCell *partition;
+
+ partitions = get_all_partitions(RelationGetRelid(targetrelation), NoLock);
+ foreach(partition, partitions)
+ {
+ Oid partrelid = lfirst_oid(partition);
+
+ renameatt_internal(partrelid, oldattname, newattname, false, true, 0, behavior);
+ }
+ }
+
/* rename attributes in typed tables of composite type */
if (targetrelation->rd_rel->relkind == RELKIND_COMPOSITE_TYPE)
{
@@ -3560,10 +3630,10 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
address = ATExecColumnDefault(rel, cmd->name, cmd->def, lockmode);
break;
case AT_DropNotNull: /* ALTER COLUMN DROP NOT NULL */
- address = ATExecDropNotNull(rel, cmd->name, lockmode);
+ address = ATExecDropNotNull(rel, cmd->name, false, lockmode);
break;
case AT_SetNotNull: /* ALTER COLUMN SET NOT NULL */
- address = ATExecSetNotNull(tab, rel, cmd->name, lockmode);
+ address = ATExecSetNotNull(wqueue, tab, rel, cmd->name, false, lockmode);
break;
case AT_SetStatistics: /* ALTER COLUMN SET STATISTICS */
address = ATExecSetStatistics(rel, cmd->name, cmd->def, lockmode);
@@ -3641,7 +3711,7 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
cmd->missing_ok, lockmode);
break;
case AT_AlterColumnType: /* ALTER COLUMN TYPE */
- address = ATExecAlterColumnType(tab, rel, cmd, lockmode);
+ address = ATExecAlterColumnType(wqueue, tab, rel, cmd, false, lockmode);
break;
case AT_AlterColumnGenericOptions: /* ALTER COLUMN OPTIONS */
address =
@@ -3781,13 +3851,25 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
ATExecGenericOptions(rel, (List *) cmd->def);
break;
case AT_AddPartition:
+ ATExecAddPartition(rel, ((PartitionStmt *) cmd->def)->name, NULL,
+ ((PartitionStmt *) cmd->def)->values);
+ break;
case AT_AddSubPartition:
+ ATExecAddPartition(rel, ((PartitionStmt *) cmd->def)->name,
+ ((PartitionStmt *) cmd->def)->parent,
+ ((PartitionStmt *) cmd->def)->values);
+ break;
case AT_DropPartition:
+ ATExecRemovePartition(rel, (RangeVar *) cmd->def, false, false);
+ break;
case AT_DropSubPartition:
- case AT_AttachPartition:
- case AT_AttachSubPartition:
+ ATExecRemovePartition(rel, (RangeVar *) cmd->def, false, true);
+ break;
case AT_DetachPartition:
+ ATExecRemovePartition(rel, (RangeVar *) cmd->def, true, false);
+ break;
case AT_DetachSubPartition:
+ ATExecRemovePartition(rel, (RangeVar *) cmd->def, true, true);
break;
default: /* oops */
elog(ERROR, "unrecognized alter table type: %d",
@@ -3822,8 +3904,8 @@ ATRewriteTables(AlterTableStmt *parsetree, List **wqueue, LOCKMODE lockmode)
/* Foreign tables and partitioned tables have no storage. */
if (tab->relkind == RELKIND_FOREIGN_TABLE ||
- (tab->relkind == RELKIND_PARTITIONED_REL &&
- tab->newTableSpace == InvalidOid))
+ (relid_is_partitioned(tab->relid) &&
+ !OidIsValid(tab->newTableSpace) && tab->constraints == NIL))
continue;
/*
@@ -4065,6 +4147,43 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
oldTupDesc = tab->oldDesc;
newTupDesc = RelationGetDescr(oldrel); /* includes all mods */
+ /*
+ * Arrange to verify any new CHECK constraint on leaf partitions. Note
+ * that NOT NULL constraint is handled by recursion in phase 2.
+ */
+ if (oldrel->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+ {
+ List *partitions;
+ ListCell *partition;
+
+ partitions = get_leaf_partitions(tab->relid, NoLock);
+ foreach(partition, partitions)
+ {
+ Oid partrelid = lfirst_oid(partition);
+ Relation partrel;
+ AlteredTableInfo *parttab;
+
+ parttab = (AlteredTableInfo *)
+ palloc0(sizeof(AlteredTableInfo));
+
+ partrel = heap_open(partrelid, NoLock);
+
+ parttab->relid = partrelid;
+ parttab->relkind = partrel->rd_rel->relkind;
+ parttab->oldDesc = tab->oldDesc;
+ parttab->constraints = tab->constraints;
+
+ ATRewriteTable(parttab, InvalidOid, NoLock);
+
+ pfree(parttab);
+ heap_close(partrel, NoLock);
+ }
+
+ /* Nothing more to do here for the original tab */
+ heap_close(oldrel, NoLock);
+ return;
+ }
+
if (OidIsValid(OIDNewHeap))
newrel = heap_open(OIDNewHeap, lockmode);
else
@@ -4828,6 +4947,11 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
if (recursing)
ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
+ if (relid_is_partition(myrelid) && !recursing)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot add column to a partition")));
+
attrdesc = heap_open(AttributeRelationId, RowExclusiveLock);
/*
@@ -5096,6 +5220,38 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
add_column_datatype_dependency(myrelid, newattnum, attribute.atttypid);
add_column_collation_dependency(myrelid, newattnum, attribute.attcollation);
+ /* Propagate to all partitions */
+ if (rel->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+ {
+ List *partitions;
+ ListCell *partition;
+
+ partitions = get_all_partitions(RelationGetRelid(rel), lockmode);
+ foreach(partition, partitions)
+ {
+ Oid partrelid = lfirst_oid(partition);
+ Relation partrel;
+ AlteredTableInfo *parttab;
+ ColumnDef *partColDef = copyObject(colDef);
+
+ /* find_partitions already got lock */
+ partrel = heap_open(partrelid, NoLock);
+ CheckTableNotInUse(partrel, "ALTER TABLE");
+
+ /* Find or create work queue entry for this table */
+ parttab = ATGetQueueEntry(wqueue, partrel);
+
+ partColDef->is_for_partition = true;
+
+ /* Recurse to partition; return value is ignored */
+ ATExecAddColumn(wqueue, parttab, partrel, partColDef, isOid,
+ false, true, if_not_exists, lockmode);
+
+ pfree(partColDef);
+ heap_close(partrel, NoLock);
+ }
+ }
+
/*
* Propagate to children as appropriate. Unlike most other ALTER
* routines, we have to do this one level of recursion at a time; we can't
@@ -5275,7 +5431,8 @@ ATPrepAddOids(List **wqueue, Relation rel, bool recurse, AlterTableCmd *cmd, LOC
* nullable, InvalidObjectAddress is returned.
*/
static ObjectAddress
-ATExecDropNotNull(Relation rel, const char *colName, LOCKMODE lockmode)
+ATExecDropNotNull(Relation rel, const char *colName, bool recursing,
+ LOCKMODE lockmode)
{
HeapTuple tuple;
AttrNumber attnum;
@@ -5284,6 +5441,11 @@ ATExecDropNotNull(Relation rel, const char *colName, LOCKMODE lockmode)
ListCell *indexoidscan;
ObjectAddress address;
+ if (relid_is_partition(RelationGetRelid(rel)) && !recursing)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot alter column of a partition")));
+
/*
* lookup the attribute
*/
@@ -5367,6 +5529,29 @@ ATExecDropNotNull(Relation rel, const char *colName, LOCKMODE lockmode)
else
address = InvalidObjectAddress;
+ /* Propagate to partitions */
+ if (rel->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+ {
+ List *partitions;
+ ListCell *partition;
+
+ partitions = get_all_partitions(RelationGetRelid(rel), lockmode);
+ foreach(partition, partitions)
+ {
+ Oid partrelid = lfirst_oid(partition);
+ Relation partrel;
+
+ /* find_partitions already got lock */
+ partrel = heap_open(partrelid, NoLock);
+ CheckTableNotInUse(partrel, "ALTER TABLE");
+
+ /* Recurse to partition; return value is ignored */
+ ATExecDropNotNull(partrel, colName, true, lockmode);
+
+ heap_close(partrel, NoLock);
+ }
+ }
+
InvokeObjectPostAlterHook(RelationRelationId,
RelationGetRelid(rel), attnum);
@@ -5382,14 +5567,19 @@ ATExecDropNotNull(Relation rel, const char *colName, LOCKMODE lockmode)
* NULL, InvalidObjectAddress is returned.
*/
static ObjectAddress
-ATExecSetNotNull(AlteredTableInfo *tab, Relation rel,
- const char *colName, LOCKMODE lockmode)
+ATExecSetNotNull(List **wqueue, AlteredTableInfo *tab, Relation rel,
+ const char *colName, bool recursing, LOCKMODE lockmode)
{
HeapTuple tuple;
AttrNumber attnum;
Relation attr_rel;
ObjectAddress address;
+ if (relid_is_partition(RelationGetRelid(rel)) && !recursing)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot alter column of a partition")));
+
/*
* lookup the attribute
*/
@@ -5433,6 +5623,34 @@ ATExecSetNotNull(AlteredTableInfo *tab, Relation rel,
else
address = InvalidObjectAddress;
+ /* Propagate to partitions */
+ if (rel->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+ {
+ List *partitions;
+ ListCell *partition;
+
+ partitions = get_all_partitions(RelationGetRelid(rel), lockmode);
+ foreach(partition, partitions)
+ {
+ Oid partrelid = lfirst_oid(partition);
+ Relation partrel;
+ AlteredTableInfo *parttab;
+
+ /* get_all_partitions already got lock */
+ partrel = heap_open(partrelid, NoLock);
+ CheckTableNotInUse(partrel, "ALTER TABLE");
+
+ /* Find or create work queue entry for this table */
+ parttab = ATGetQueueEntry(wqueue, partrel);
+
+ /* Recurse to partition; return value is ignored */
+ ATExecSetNotNull(wqueue, parttab, partrel, colName, true,
+ lockmode);
+
+ heap_close(partrel, NoLock);
+ }
+ }
+
InvokeObjectPostAlterHook(RelationRelationId,
RelationGetRelid(rel), attnum);
@@ -5890,6 +6108,11 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
if (recursing)
ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
+ if (relid_is_partition(RelationGetRelid(rel)) && !recursing)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot drop column from a partition")));
+
/*
* get the number of the attribute
*/
@@ -6076,6 +6299,30 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
tab->rewrite |= AT_REWRITE_ALTER_OID;
}
+ /* Propagate to partitions */
+ if (rel->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+ {
+ List *partitions;
+ ListCell *partition;
+
+ partitions = get_all_partitions(RelationGetRelid(rel), lockmode);
+ foreach(partition, partitions)
+ {
+ Oid partrelid = lfirst_oid(partition);
+ Relation partrel;
+
+ /* find_partitions already got lock */
+ partrel = heap_open(partrelid, NoLock);
+ CheckTableNotInUse(partrel, "ALTER TABLE");
+
+ /* Recurse to partition; return value is ignored */
+ ATExecDropColumn(wqueue, partrel, colName, behavior,
+ false, true, missing_ok, lockmode);
+
+ heap_close(partrel, NoLock);
+ }
+ }
+
return object;
}
@@ -6308,6 +6555,11 @@ ATAddCheckConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
ListCell *child;
ObjectAddress address = InvalidObjectAddress;
+ if (relid_is_partition(RelationGetRelid(rel)))
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot add CHECK constraints to a partition")));
+
/* At top level, permission check was done in ATPrepCmd, else do it */
if (recursing)
ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
@@ -7075,6 +7327,11 @@ ATExecValidateConstraint(Relation rel, char *constrName, bool recurse,
* Foreign keys do not inherit, so we purposely ignore the
* recursion bit here
*/
+
+ /*
+ * Foreign keys are not currently supported on partitioned tables,
+ * so ignore recursion for now.
+ */
}
else if (con->contype == CONSTRAINT_CHECK)
{
@@ -7122,6 +7379,26 @@ ATExecValidateConstraint(Relation rel, char *constrName, bool recurse,
heap_close(childrel, NoLock);
}
+ /* Recurse to partitions */
+ if (rel->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+ {
+ List *partitions;
+ ListCell *partition;
+
+ partitions = get_leaf_partitions(RelationGetRelid(rel),
+ lockmode);
+
+ foreach(partition, partitions)
+ {
+ Oid partOID = lfirst_oid(partition);
+ Relation partrel;
+
+ partrel = heap_open(partOID, lockmode);
+ validateCheckConstraint(partrel, tuple);
+ heap_close(partrel, NoLock);
+ }
+ }
+
validateCheckConstraint(rel, tuple);
/*
@@ -7523,7 +7800,7 @@ validateCheckConstraint(Relation rel, HeapTuple constrtup)
* tables.
*/
if (rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE ||
- rel->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+ relid_is_partitioned(RelationGetRelid(rel)))
return;
constrForm = (Form_pg_constraint) GETSTRUCT(constrtup);
@@ -8266,8 +8543,8 @@ ATColumnChangeRequiresRewrite(Node *expr, AttrNumber varattno)
* Return the address of the modified column.
*/
static ObjectAddress
-ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
- AlterTableCmd *cmd, LOCKMODE lockmode)
+ATExecAlterColumnType(List **wqueue, AlteredTableInfo *tab, Relation rel,
+ AlterTableCmd *cmd, bool recursing, LOCKMODE lockmode)
{
char *colName = cmd->name;
ColumnDef *def = (ColumnDef *) cmd->def;
@@ -8288,6 +8565,11 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
HeapTuple depTup;
ObjectAddress address;
+ if (relid_is_partition(RelationGetRelid(rel)) && !recursing)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot alter column of a partition")));
+
attrelation = heap_open(AttributeRelationId, RowExclusiveLock);
/* Look up the target column */
@@ -8658,6 +8940,37 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
StoreAttrDefault(rel, attnum, defaultexpr, true);
}
+ /* Propagate to partitions */
+ if (rel->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+ {
+ List *partitions;
+ ListCell *partition;
+
+ partitions = get_all_partitions(RelationGetRelid(rel), lockmode);
+ foreach(partition, partitions)
+ {
+ Oid partrelid = lfirst_oid(partition);
+ Relation partrel;
+ AlteredTableInfo *parttab;
+
+ /* find_partitions already got lock */
+ partrel = heap_open(partrelid, NoLock);
+ CheckTableNotInUse(partrel, "ALTER TABLE");
+
+ /* Find or create work queue entry for this table */
+ parttab = ATGetQueueEntry(wqueue, partrel);
+
+ parttab->rewrite |= tab->rewrite;
+ if (parttab->rewrite)
+ parttab->newvals = tab->newvals;
+
+ /* Recurse to partition; return value is ignored */
+ ATExecAlterColumnType(wqueue, parttab, partrel, cmd, true, lockmode);
+
+ heap_close(partrel, NoLock);
+ }
+ }
+
ObjectAddressSubSet(address, RelationRelationId,
RelationGetRelid(rel), attnum);
@@ -9343,6 +9656,29 @@ ATExecChangeOwner(Oid relationOid, Oid newOwnerId, bool recursing, LOCKMODE lock
}
}
+ if (relid_is_partitioned(relationOid))
+ {
+ List *partitions;
+ ListCell *partition;
+
+ /* Propagate to partitions */
+ partitions = get_partitions(relationOid, lockmode);
+ foreach(partition, partitions)
+ {
+ Oid partrelid = lfirst_oid(partition);
+ Relation partrel;
+
+ /* find_partitions already got lock */
+ partrel = heap_open(partrelid, NoLock);
+ CheckTableNotInUse(partrel, "ALTER TABLE");
+
+ /* Recurse to partition; return value is ignored */
+ ATExecChangeOwner(partrelid, newOwnerId, false, lockmode);
+
+ heap_close(partrel, NoLock);
+ }
+ }
+
InvokeObjectPostAlterHook(RelationRelationId, relationOid, 0);
ReleaseSysCache(tuple);
@@ -9823,7 +10159,7 @@ ATExecSetTableSpace(Oid tableOid, Oid newTableSpace, LOCKMODE lockmode)
errmsg("cannot move temporary tables of other sessions")));
/* For partitioned tables, just set the new tablespace in pg_class */
- if(rel->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+ if (relid_is_partitioned(tableOid))
{
SetRelationSpcFileNode(rel, newTableSpace, rel->rd_rel->relfilenode);
relation_close(rel, lockmode);
@@ -12567,3 +12903,319 @@ ComputePartitionAttrs(Oid relid,
partOpClassOids[attn++] = opclassOid;
}
}
+
+/*
+ * ATExecAddPartition - ADD PARTITION
+ */
+static void
+ATExecAddPartition(Relation rootParent, RangeVar *name, RangeVar *parent,
+ PartitionValues *values)
+{
+ bool is_subpart;
+ PartitionKey partKey;
+ Relation partRel,
+ parentRel;
+ Datum *boundDatums;
+ int listnvalues;
+ int parent_level;
+ PartitionKeyTypeInfo *typinfo;
+
+ partRel = heap_openrv(name, NoLock);
+
+ if (parent != NULL)
+ {
+ parentRel = heap_openrv(parent, AccessExclusiveLock);
+ parent_level = get_partition_level(RelationGetRelid(parentRel));
+ is_subpart = true;
+ }
+ else
+ parentRel = rootParent;
+
+ partKey = is_subpart ? list_nth(rootParent->rd_partkeys, parent_level) :
+ linitial(rootParent->rd_partkeys);
+
+ typinfo = get_partition_key_type_info(rootParent, partKey);
+
+ /* Evaluate and validate new partition's bound */
+ boundDatums = EvalPartitionBound(rootParent, parentRel, partKey, typinfo,
+ values, &listnvalues);
+
+ /* Update the catalog */
+ StorePartition(rootParent, partKey, typinfo,
+ RelationGetRelid(partRel),
+ RelationGetRelid(parentRel),
+ true,
+ listnvalues,
+ boundDatums);
+
+ /* Tell world about the new partition of parent. */
+ CacheInvalidateRelcache(parentRel);
+
+ free_partition_key_type_info(typinfo);
+ heap_close(partRel, NoLock);
+ if (parentRel != rootParent)
+ heap_close(parentRel, AccessExclusiveLock);
+}
+
+/*
+ * EvalPartitionBound
+ * Evalulate FOR VALUES specification of a partition
+ *
+ * Returns partition bound value Datums to store in pg_partition catalog
+ * unless the new partition's definition happens to overlap with some
+ * existing partition's, in which case an error is thrown.
+ */
+static Datum *
+EvalPartitionBound(Relation rootParent, Relation parent, PartitionKey key,
+ PartitionKeyTypeInfo *typinfo, PartitionValues *values,
+ int *listnvalues)
+{
+ PartitionDesc pdesc;
+ Datum *result;
+ Datum *rangemaxs;
+ Oid overlapsWith;
+ int i;
+
+ pdesc = RelationGetPartitionDesc(parent, key, typinfo, true);
+
+ /* result->oid is set by the caller */
+
+ *listnvalues = list_length(values->listvalues);
+ switch (key->strategy)
+ {
+ case PARTITION_STRAT_LIST:
+ Assert(key->partnatts == 1);
+ Assert(list_length(values->listvalues) >= 1);
+
+ result = (Datum *) palloc0(*listnvalues * sizeof(Datum));
+ result = evalPartitionListBound(values->listvalues, typinfo,
+ *listnvalues);
+
+ /* TODO: uniqueify(result->listvalues) */
+
+ if (list_partition_overlaps(pdesc, key, *listnvalues, result,
+ &overlapsWith))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("invalid list partition definition"),
+ errdetail("New partition's definition overlaps with partition \"%s\".",
+ get_rel_name(overlapsWith))));
+
+ break;
+
+ case PARTITION_STRAT_RANGE:
+ Assert(list_length(values->rangemaxs) == key->partnatts);
+ rangemaxs = evalPartitionRangeBound(values->rangemaxs, typinfo, key->partnatts);
+
+ if (range_partition_overlaps(pdesc, key, rangemaxs))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("invalid range partition definition"),
+ errhint("Specify a bound value that is greater than that of the last partition")));
+
+ /*
+ * Final result consists of Datum array where each datum is an
+ * an array that contains a rangemax value for a key column.
+ *
+ * The reason to have to do this weird thing is to get around the
+ * the fact about anyarray (which is the type of partrangemaxs
+ * field of pg_partition system catalog) that it requires each
+ * element to be of the same type.
+ */
+ result = (Datum *) palloc0(key->partnatts * sizeof(Datum));
+ for (i = 0; i < key->partnatts; i++)
+ {
+ Datum datums[1];
+ bool nulls[1];
+ int dims[1];
+ int lbs[1];
+ ArrayType *rangemax;
+
+ datums[0] = rangemaxs[i];
+ nulls[0] = false;
+ dims[0] = 1;
+ lbs[0] = 1;
+
+ rangemax = construct_md_array(datums, nulls, 1, dims, lbs,
+ typinfo->typid[i], typinfo->typlen[i],
+ typinfo->typbyval[i], typinfo->typalign[i]);
+
+ result[i] = PointerGetDatum(rangemax);
+ }
+ }
+
+ return result;
+}
+
+/*
+ * evalPartitionListBound
+ * Evaluate a list of expressions appearing in FOR VALUES IN (..) clause
+ * for a list partition
+ */
+static Datum *
+evalPartitionListBound(List *values, PartitionKeyTypeInfo *typinfo, int nvalues)
+{
+ ListCell *cell;
+ ParseState *pstate;
+ EState *estate;
+ ExprContext *ecxt;
+ int i;
+ Datum *datum;
+
+ if (!values)
+ return NULL;
+
+ datum = (Datum *) palloc(nvalues * sizeof(Datum));
+ pstate = make_parsestate(NULL);
+ estate = CreateExecutorState();
+ ecxt = GetPerTupleExprContext(estate);
+
+ i = 0;
+ foreach(cell, values)
+ {
+ Node *value = (Node *) lfirst(cell);
+ bool isnull;
+ ExprState *expr;
+ MemoryContext oldcxt;
+
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
+ expr = ExecPrepareExpr((Expr *) value, estate);
+ datum[i] = ExecEvalExpr(expr, ecxt, &isnull, NULL);
+
+ if (isnull)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("a partition bound value must not be NULL")));
+
+ MemoryContextSwitchTo(oldcxt);
+
+ if (!typinfo->typbyval[0])
+ {
+ if (typinfo->typlen[0] == -1)
+ datum[i] = PointerGetDatum(PG_DETOAST_DATUM_COPY(datum[i]));
+ else
+ datum[i] = datumCopy(datum[i], false, typinfo->typlen[0]);
+ }
+
+ ResetPerTupleExprContext(estate);
+ i++;
+ }
+
+ return datum;
+}
+
+/*
+ * evalPartitionRangeBound
+ * Evaluate a list of expressions appearing in FOR VALUES LESS THAN (..)
+ * clause for a range partition
+ */
+static Datum *
+evalPartitionRangeBound(List *values, PartitionKeyTypeInfo *typinfo, int partnatts)
+{
+ ListCell *cell;
+ ParseState *pstate;
+ EState *estate;
+ ExprContext *ecxt;
+ int i;
+ Datum *datum;
+
+ if (!values)
+ return NULL;
+
+ datum = (Datum *) palloc(partnatts * sizeof(Datum));
+ pstate = make_parsestate(NULL);
+ estate = CreateExecutorState();
+ ecxt = GetPerTupleExprContext(estate);
+
+ i = 0;
+ foreach(cell, values)
+ {
+ Node *value = (Node *) lfirst(cell);
+ bool isnull;
+ ExprState *expr;
+ MemoryContext oldcxt;
+
+ oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
+ expr = ExecPrepareExpr((Expr *) value, estate);
+ datum[i] = ExecEvalExpr(expr, ecxt, &isnull, NULL);
+
+ if (isnull)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("a partition value must not be NULL")));
+
+ MemoryContextSwitchTo(oldcxt);
+
+ if (!typinfo->typbyval[i])
+ {
+ if (typinfo->typlen[i] == -1)
+ datum[i] = PointerGetDatum(PG_DETOAST_DATUM_COPY(datum[i]));
+ else
+ datum[i] = datumCopy(datum[i], false, typinfo->typlen[i]);
+ }
+
+ ResetPerTupleExprContext(estate);
+ i++;
+ }
+
+ return datum;
+}
+
+/*
+ * ATExecRemovePartition - DROP/DETACH PARTITION / SUBPARTITION
+ */
+static void
+ATExecRemovePartition(Relation rootParent, RangeVar *name, bool is_detach,
+ bool is_subpart)
+{
+ Relation partRel,
+ parentRel;
+ Oid partitionOid,
+ parentOid;
+ ObjectAddress obj;
+
+ partRel = heap_openrv(name, AccessExclusiveLock);
+ partitionOid = RelationGetRelid(partRel);
+ parentOid = get_partition_parent(partitionOid);
+ heap_close(partRel, AccessExclusiveLock);
+
+ if (!OidIsValid(parentOid))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("%s \"%s\" of \"%s\" does not exist",
+ !is_subpart ? "partition" : "sub-partition",
+ name->relname, RelationGetRelationName(rootParent))));
+
+ if (is_subpart && parentOid == RelationGetRelid(rootParent))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("sub-partition \"%s\" of \"%s\" does not exist",
+ name->relname, RelationGetRelationName(rootParent))));
+ else if (!is_subpart && parentOid != RelationGetRelid(rootParent))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("partition \"%s\" of \"%s\" does not exist",
+ name->relname, RelationGetRelationName(rootParent))));
+
+ if (is_detach && relid_is_internal_partition(partitionOid))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("\"%s\" is an internal partition", name->relname),
+ errdetail("Detaching internal partitions is not supported")));
+
+ if (is_detach)
+ RemovePartitionEntryByRelId(partitionOid);
+ else
+ {
+ obj.classId = RelationRelationId;
+ obj.objectId = partitionOid;
+ obj.objectSubId = 0;
+ performDeletion(&obj, DROP_CASCADE, 0);
+ }
+
+ CommandCounterIncrement();
+
+ parentRel = heap_open(parentOid, AccessShareLock);
+ CacheInvalidateRelcache(parentRel);
+ heap_close(parentRel, AccessShareLock);
+}
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 76f7297..0f11922 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -1019,6 +1019,31 @@ CheckValidResultRel(Relation resultRel, CmdType operation)
switch (resultRel->rd_rel->relkind)
{
case RELKIND_RELATION:
+ if (relid_is_partition(RelationGetRelid(resultRel)))
+ {
+ switch (operation)
+ {
+ case CMD_INSERT:
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("cannot insert into partition \"%s\"",
+ RelationGetRelationName(resultRel)),
+ errhint("Insert into the root parent instead.")));
+ break;
+ case CMD_UPDATE:
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("cannot update partition \"%s\"",
+ RelationGetRelationName(resultRel)),
+ errhint("Update the root parent instead.")));
+ break;
+ default:
+ elog(ERROR, "unrecognized CmdType: %d", (int) operation);
+ break;
+ }
+ }
+ break;
+ case RELKIND_PARTITIONED_REL:
/* OK */
break;
case RELKIND_SEQUENCE:
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index aabe108..bf3a162 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2600,6 +2600,8 @@ _copyColumnDef(const ColumnDef *from)
COPY_SCALAR_FIELD(is_local);
COPY_SCALAR_FIELD(is_not_null);
COPY_SCALAR_FIELD(is_from_type);
+ COPY_SCALAR_FIELD(is_for_partition);
+ COPY_SCALAR_FIELD(is_dropped_copy);
COPY_SCALAR_FIELD(storage);
COPY_NODE_FIELD(raw_default);
COPY_NODE_FIELD(cooked_default);
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index cfb5db6..2e538ef 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -2339,6 +2339,8 @@ _equalColumnDef(const ColumnDef *a, const ColumnDef *b)
COMPARE_SCALAR_FIELD(is_local);
COMPARE_SCALAR_FIELD(is_not_null);
COMPARE_SCALAR_FIELD(is_from_type);
+ COMPARE_SCALAR_FIELD(is_for_partition);
+ COMPARE_SCALAR_FIELD(is_dropped_copy);
COMPARE_SCALAR_FIELD(storage);
COMPARE_NODE_FIELD(raw_default);
COMPARE_NODE_FIELD(cooked_default);
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 00b449d..cd8f653 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2344,6 +2344,8 @@ _outColumnDef(StringInfo str, const ColumnDef *node)
WRITE_BOOL_FIELD(is_local);
WRITE_BOOL_FIELD(is_not_null);
WRITE_BOOL_FIELD(is_from_type);
+ WRITE_BOOL_FIELD(is_for_partition);
+ WRITE_BOOL_FIELD(is_dropped_copy);
WRITE_CHAR_FIELD(storage);
WRITE_NODE_FIELD(raw_default);
WRITE_NODE_FIELD(cooked_default);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 902bd40..5219d5f 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -544,6 +544,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <partby> PartitionBy SubPartitionBy
%type <partelem> part_elem
%type <list> part_params
+%type <list> OptWithPartition
%type <partvalues> ForValues
%type <list> ValuesList
%type <range> using_table opt_using_table
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index ce8ed33..94731ed 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -2670,9 +2670,6 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
transformAddPartition(&cxt, (PartitionStmt *) cmd->def);
newcmds = lappend(newcmds, cmd);
break;
- case AT_DropPartition:
- case AT_DropSubPartition:
- break;
case AT_AttachPartition:
case AT_AttachSubPartition:
ereport(ERROR,
@@ -2684,6 +2681,10 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
{
PartitionStmt *partstmt = (PartitionStmt *) cmd->def;
+ /*
+ * Add a RenameStmt to rename the partition after being
+ * detached
+ */
if (partstmt->using_table)
{
RenameStmt *stmt = makeNode(RenameStmt);
@@ -2695,6 +2696,7 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
cxt.alist = lappend(cxt.alist, stmt);
}
+ cmd->def = (Node *) partstmt->name;
newcmds = lappend(newcmds, cmd);
break;
@@ -3072,43 +3074,124 @@ setSchemaName(char *context_schema, char **stmt_schema_name)
* Note that cxt->relation corresponds to the root partitioned table.
*/
static void
-transformAddPartition(CreateStmtContext *cxt, PartitionStmt *stmt)
+transformAddPartition(CreateStmtContext *cxt, PartitionStmt *partstmt)
{
+ CreateStmt *stmt;
Relation parentRel;
- bool is_subpartition = stmt->parent != NULL;
+ List *partitionElts;
+ TupleDesc tupdesc;
+ TupleConstr *constr;
+ int16 attno;
+ bool is_subpartition = partstmt->parent != NULL;
PartitionKey key;
/* Check if the target table is partitioned at all */
if (cxt->rel->rd_partkeys == NIL)
ereport(ERROR,
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
- errmsg("\"%s\" is not partitioned", cxt->relation->relname)));
+ errmsg("\"%s\" is not partitioned",
+ cxt->relation->relname)));
/* If adding a sub-partition, check if we can do so */
if (is_subpartition && list_length(cxt->rel->rd_partkeys) < 2)
ereport(ERROR,
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
errmsg("cannot add sub-partition"),
- errdetail("\"%s\" is not sub-partitioned", cxt->relation->relname)));
+ errdetail("\"%s\" is not sub-partitioned",
+ cxt->relation->relname)));
- if (stmt->parent)
+ if (partstmt->parent)
{
- parentRel = heap_openrv_extended(stmt->parent, NoLock, true);
+ parentRel = heap_openrv_extended(partstmt->parent, NoLock, true);
if (parentRel == NULL)
ereport(ERROR,
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
errmsg("partition \"%s\" of \"%s\" does not exist",
- stmt->parent->relname, cxt->relation->relname),
- parser_errposition(cxt->pstate, stmt->parent->location)));
+ partstmt->parent->relname, cxt->relation->relname),
+ parser_errposition(cxt->pstate,
+ partstmt->parent->location)));
}
else
parentRel = heap_openrv(cxt->relation, NoLock);
+ /*
+ * Gin up column definition list for the partition by reading individual
+ * attributes off the root parent's tuple descriptor. Do not miss dropped
+ * columns.
+ */
+ tupdesc = RelationGetDescr(cxt->rel);
+ constr = tupdesc->constr;
+ partitionElts = NIL;
+ for (attno = 1; attno <= tupdesc->natts; attno++)
+ {
+ Form_pg_attribute attr = tupdesc->attrs[attno - 1];
+ ColumnDef *n;
+
+ n = makeNode(ColumnDef);
+ n->colname = pstrdup(NameStr(attr->attname));
+
+ /* A hack... */
+ if (attr->attisdropped)
+ {
+ /* ... is hack! This will have to go away eventually */
+ n->typeName = makeTypeNameFromNameList(list_make1(makeString("int2")));
+ n->is_dropped_copy = true;
+ }
+ else
+ n->typeName = makeTypeNameFromOid(attr->atttypid, attr->atttypmod);
+
+ n->inhcount = 0;
+ n->is_local = true;
+ n->is_not_null = attr->attnotnull;
+ n->is_from_type = false;
+ n->is_for_partition = true;
+ n->storage = attr->attstorage;
+ n->cooked_default = NULL;
+ n->raw_default = NULL;
+ n->collClause = NULL;
+ n->collOid = attr->attcollation;
+ n->constraints = NIL;
+ n->location = -1;
+
+ partitionElts = lappend(partitionElts, n);
+ }
+
+ /* Make a CreateStmt for the partition relation */
+ stmt = makeNode(CreateStmt);
+
+ partstmt->name->relpersistence = parentRel->rd_rel->relpersistence;
+ stmt->relation = partstmt->name;
+ stmt->tableElts = partitionElts;
+
+ /* Use own options if defined, otherwise immediate parent's */
+ if (partstmt->options)
+ stmt->options = partstmt->options;
+ else
+ stmt->options = untransformRelOptions(PointerGetDatum(parentRel->rd_options));
+
+ if (parentRel->rd_rel->relhasoids)
+ stmt->options = lcons(makeDefElem("oids",
+ (Node *) makeInteger(true)), partstmt->options);
+
+ /* Use own tablespace if defined, otherwise immediate parent's */
+ if (partstmt->tablespacename)
+ stmt->tablespacename = partstmt->tablespacename;
+ else if (parentRel->rd_rel->reltablespace)
+ stmt->tablespacename = get_tablespace_name(parentRel->rd_rel->reltablespace);
+ else
+ stmt->tablespacename = NULL;
+
+ stmt->is_internal_partition = !is_subpartition &&
+ list_length(cxt->rel->rd_partkeys) > 1;
+
+ /* Arrange the partition relation to be created */
+ cxt->blist = lappend(cxt->blist, stmt);
+
/* tranform FOR VALUES clause */
key = !is_subpartition ? linitial(cxt->rel->rd_partkeys)
: lsecond(cxt->rel->rd_partkeys);
- stmt->values = transformPartitionValues(cxt, key, stmt->values);
+ partstmt->values = transformPartitionValues(cxt, key, partstmt->values);
heap_close(parentRel, NoLock);
}
@@ -3225,8 +3308,8 @@ transformPartitionValues(CreateStmtContext *cxt, PartitionKey key,
}
break;
}
-
result->location = values->location;
+ free_partition_key_type_info(typinfo);
return result;
}
diff --git a/src/include/catalog/heap.h b/src/include/catalog/heap.h
index b80d8d8..46edef2 100644
--- a/src/include/catalog/heap.h
+++ b/src/include/catalog/heap.h
@@ -49,7 +49,8 @@ extern Relation heap_create(const char *relname,
char relpersistence,
bool shared_relation,
bool mapped_relation,
- bool allow_system_table_mods);
+ bool allow_system_table_mods,
+ bool is_internal_partition);
extern Oid heap_create_with_catalog(const char *relname,
Oid relnamespace,
@@ -71,7 +72,8 @@ extern Oid heap_create_with_catalog(const char *relname,
bool use_user_acl,
bool allow_system_table_mods,
bool is_internal,
- ObjectAddress *typaddress);
+ ObjectAddress *typaddress,
+ bool is_internal_partition);
extern void heap_create_init_fork(Relation rel);
diff --git a/src/include/catalog/partition.h b/src/include/catalog/partition.h
index 44b4c06..7f1576a 100644
--- a/src/include/catalog/partition.h
+++ b/src/include/catalog/partition.h
@@ -40,6 +40,31 @@ typedef struct PartitionKeyTypeInfo
char *typalign;
} PartitionKeyTypeInfo;
+/*
+ * Information about partitions of a partitioned table or sub-partitions
+ * of an internal partition.
+ *
+ * 'numparts' Length of each of the following C arrays
+ * 'oids' Partition OIDs
+ * 'listnvalues' Numbers of value datums in corresponding arrays in
+ * 'listvalues' below
+ * 'listvalues' Pointers to arrays of value datums for all list
+ * partitions
+ * 'rangemaxs' Pointers to arrays of rangemax datums for all range
+ * partitions (note that there are PARTITION_MAX_KEYS
+ * members per array)
+ */
+typedef struct PartitionDescData
+{
+ int numparts;
+ Oid *oids;
+ int *listnvalues;
+ Datum **listvalues;
+ Datum *rangemaxs[PARTITION_MAX_KEYS];
+} PartitionDescData;
+
+typedef struct PartitionDescData *PartitionDesc;
+
extern void StorePartitionKey(Relation rel,
int16 partlevel,
int16 partnatts,
@@ -56,4 +81,34 @@ extern PartitionKeyTypeInfo *get_partition_key_type_info(Relation rel,
extern void free_partition_key_type_info(PartitionKeyTypeInfo *typinfo);
extern bool relid_is_partitioned(Oid relid);
+extern void StorePartition(Relation rel, PartitionKey key,
+ PartitionKeyTypeInfo *typinfo,
+ Oid partitionOid,
+ Oid parentOid,
+ bool valid,
+ int listnvalues,
+ Datum *boundDatums);
+extern void RemovePartitionEntryByRelId(Oid relid);
+extern void RelationDropPartitions(Oid relid);
+extern PartitionDesc RelationGetPartitionDesc(Relation relation,
+ PartitionKey key,
+ PartitionKeyTypeInfo *typinfo,
+ bool include_all);
+extern List *get_partitions(Oid relid, int lockmode);
+extern List *get_all_partitions(Oid relid, int lockmode);
+extern List *get_leaf_partitions(Oid relid, int lockmode);
+extern Oid get_partition_parent(Oid relid);
+extern Oid get_partition_root_parent(Oid relid);
+extern int get_partition_level(Oid relid);
+extern bool relid_is_partition(Oid relid);
+extern bool relid_is_internal_partition(Oid relid);
+
+extern bool list_partition_overlaps(PartitionDesc pdesc,
+ PartitionKey key,
+ int listnvalues,
+ Datum *listvalues,
+ Oid *overlapsWith);
+extern bool range_partition_overlaps(PartitionDesc pdesc,
+ PartitionKey key,
+ Datum *rangemaxs);
#endif /* PARTITION_H */
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 8128226..bf07577 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -591,6 +591,11 @@ typedef struct ColumnDef
bool is_local; /* column has local (non-inherited) def'n */
bool is_not_null; /* NOT NULL constraint specified? */
bool is_from_type; /* column definition came from table type */
+ bool is_for_partition; /* column definition is for a partition
+ * derived from root parent's column */
+ bool is_dropped_copy; /* column definition is a dummy one
+ * corresponding to some dropped column
+ * in the root parent */
char storage; /* attstorage setting, or 0 for default */
Node *raw_default; /* default value (untransformed parse tree) */
Node *cooked_default; /* default value (transformed expr tree) */
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 4242e84..1d339b6 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -91,6 +91,8 @@ typedef struct RelationData
struct RowSecurityDesc *rd_rsdesc; /* row security policies, or NULL */
List *rd_partkeys; /* partition keys, or NIL */
+ MemoryContext rd_pdcxt; /* private context for partdesc */
+ PartitionDesc rd_partdesc; /* partitions, or NULL */
/* data managed by RelationGetIndexList: */
List *rd_indexlist; /* list of OIDs of indexes on relation */
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index 19e4e46..29abb9e 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -117,6 +117,7 @@ pg_namespace|t
pg_opclass|t
pg_operator|t
pg_opfamily|t
+pg_partition|t
pg_partitioned_rel|t
pg_pltemplate|t
pg_policy|t
--
1.7.1
[text/x-diff] 0008-Rework-code-manipulating-the-PartitionKey-data-struc.patch (34.0K, ../../56C1302D.6070403@lab.ntt.co.jp/9-0008-Rework-code-manipulating-the-PartitionKey-data-struc.patch)
download | inline diff:
From 490d89a55bb0a680885704a8b6ddb96b85e160de Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Wed, 10 Feb 2016 19:28:22 +0900
Subject: [PATCH 08/10] Rework code manipulating the PartitionKey data structure.
Previously, type information about partition key columns was handled
separately. So, there were public interface functions to get partition
key type information wherever required. But that needed access to the
root parent relation at all times (to get to partition expressions) and
resulted in needing to pass around that rel and type information which
became unbearable by this point.
Instead, integrate type information into PartitionKey structure and build
it when building partition key itself. That means, partition expressions
are also built which wasn't the case previously. So, no more need for
RelationGetPartitionExpr() which had a weird interface anyway.
---
src/backend/catalog/partition.c | 298 +++++++++++++++++++-----------------
src/backend/commands/tablecmds.c | 60 +++----
src/backend/parser/parse_utilcmd.c | 17 +--
src/backend/utils/cache/relcache.c | 88 -----------
src/include/catalog/partition.h | 41 +++---
src/include/utils/relcache.h | 2 -
6 files changed, 204 insertions(+), 302 deletions(-)
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index 33a16eb..cf48bf4 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -30,6 +30,8 @@
#include "executor/executor.h"
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
+#include "optimizer/clauses.h"
+#include "optimizer/planmain.h"
#include "storage/lmgr.h"
#include "utils/array.h"
#include "utils/builtins.h"
@@ -61,11 +63,9 @@ typedef struct PartitionInfo
static int32 range_partition_cmp_max(const void *a, const void *b,
void *arg);
-static PartitionDesc CopyPartitionDesc(PartitionDesc src, int partnatts,
- char strategy, PartitionKeyTypeInfo *typinfo);
+static PartitionDesc CopyPartitionDesc(PartitionDesc src, PartitionKey key);
static PartitionInfo **GetRelPartitions(Relation rel, PartitionKey key,
- PartitionKeyTypeInfo *typinfo, bool only_valid,
- int *numparts);
+ bool only_valid, int *numparts);
static void free_partitions(PartitionInfo **p, int num);
static int oid_cmp(const void *p1, const void *p2);
static List *get_leaf_partitions_recurse(Oid relid, int lockmode,
@@ -248,11 +248,9 @@ relid_is_partitioned(Oid relid)
void
RelationBuildPartitionKeys(Relation relation)
{
- List *keys = NIL;
- HeapTuple tuple;
- bool isnull;
- int level, i;
- MemoryContext oldcxt = CurrentMemoryContext;
+ List *keys = NIL;
+ int level;
+ MemoryContext oldcxt = CurrentMemoryContext;
/*
* Loop through the partition keys for this relation, if any.
@@ -261,10 +259,15 @@ RelationBuildPartitionKeys(Relation relation)
{
Relation catalog;
Form_pg_partitioned_rel form;
- PartitionKey key;
+ HeapTuple tuple;
+ Datum datum;
+ bool isnull;
int2vector *partattrs;
+ ListCell *partexprs_item;
oidvector *opclass;
- Datum datum;
+ int i;
+ PartitionKey key;
+ KeyTypeInfo *typinfo;
tuple = SearchSysCache2(PARTEDRELIDLEVEL,
ObjectIdGetDatum(RelationGetRelid(relation)),
@@ -295,33 +298,83 @@ RelationBuildPartitionKeys(Relation relation)
Assert(!isnull);
opclass = (oidvector *) DatumGetPointer(datum);
- key->partattrs = (AttrNumber *)
- palloc0(key->partnatts * sizeof(AttrNumber));
+ datum = heap_getattr(tuple,
+ Anum_pg_partitioned_rel_partexprs,
+ RelationGetDescr(catalog),
+ &isnull);
+ if (!isnull)
+ {
+ char *exprsString;
+ Node *exprs;
+
+ exprsString = TextDatumGetCString(datum);
+ exprs = stringToNode(exprsString);
+ pfree(exprsString);
+
+ /*
+ * Run the expressions through eval_const_expressions. This is
+ * not just an optimization, but is necessary, because the
+ * planner will be comparing them to similarly-processed qual
+ * clauses, and may fail to detect valid matches without this.
+ * We don't bother with canonicalize_qual, however.
+ */
+ exprs = eval_const_expressions(NULL, (Node *) exprs);
+
+ /* May as well fix opfuncids too */
+ fix_opfuncids((Node *) exprs);
+ key->partexprs = (List *) exprs;
+ }
+
+ key->partattrs = (AttrNumber *) palloc0(key->partnatts * sizeof(AttrNumber));
key->partopfamily = (Oid *) palloc0(key->partnatts * sizeof(Oid));
- key->partsupfunc = (FmgrInfo *)
- palloc0(key->partnatts * sizeof(FmgrInfo));
+ key->partsupfunc = (FmgrInfo *) palloc0(key->partnatts * sizeof(FmgrInfo));
+
+ /* Gather type info as well */
+ key->typinfo = typinfo = (KeyTypeInfo *) palloc0(sizeof(KeyTypeInfo));
+ typinfo->typid = (Oid *) palloc0(key->partnatts * sizeof(Oid));
+ typinfo->typmod = (int32 *) palloc0(key->partnatts * sizeof(int32));
+ typinfo->typlen = (int16 *) palloc0(key->partnatts * sizeof(int16));
+ typinfo->typbyval = (bool *) palloc0(key->partnatts * sizeof(bool));
+ typinfo->typalign = (char *) palloc0(key->partnatts * sizeof(bool));
/*
* Copy partattrs. Further, determine and store the opfamily and
* support function per attribute.
*/
+ partexprs_item = list_head(key->partexprs);
for (i = 0; i < key->partnatts; i++)
{
- HeapTuple tuple;
- Form_pg_opclass form;
- Oid funcid;
+ HeapTuple tuple;
+ AttrNumber attno;
+ Form_pg_opclass form;
+ Oid funcid;
- key->partattrs[i] = partattrs->values[i];
+ key->partattrs[i] = attno = partattrs->values[i];
- tuple = SearchSysCache(CLAOID,
- ObjectIdGetDatum(opclass->values[i]),
- 0, 0, 0);
+ /* Collect type information */
+ if (attno != InvalidAttrNumber)
+ {
+ typinfo->typid[i] = relation->rd_att->attrs[attno - 1]->atttypid;
+ typinfo->typmod[i] = relation->rd_att->attrs[attno - 1]->atttypmod;
+ }
+ else
+ {
+ typinfo->typid[i] = exprType(lfirst(partexprs_item));
+ typinfo->typmod[i] = exprTypmod(lfirst(partexprs_item));
+ partexprs_item = lnext(partexprs_item);
+ }
+ get_typlenbyvalalign(typinfo->typid[i],
+ &typinfo->typlen[i],
+ &typinfo->typbyval[i],
+ &typinfo->typalign[i]);
+
+ /* Determine operator family */
+ tuple = SearchSysCache1(CLAOID, ObjectIdGetDatum(opclass->values[i]));
if (!HeapTupleIsValid(tuple))
elog(ERROR, "cache lookup failed for opclass %u",
opclass->values[i]);
form = (Form_pg_opclass) GETSTRUCT(tuple);
-
key->partopfamily[i] = form->opcfamily;
/*
@@ -336,9 +389,6 @@ RelationBuildPartitionKeys(Relation relation)
ReleaseSysCache(tuple);
}
- /* partexprs are built and copied here as and when necessary */
- key->partexprs = NIL;
-
keys = lappend(keys, key);
ReleaseSysCache(tuple);
@@ -353,6 +403,25 @@ RelationBuildPartitionKeys(Relation relation)
FreePartitionKeys(keys);
}
+static KeyTypeInfo *
+copy_key_type_info(int n, KeyTypeInfo *typinfo)
+{
+ KeyTypeInfo *result = (KeyTypeInfo *) palloc0(sizeof(KeyTypeInfo));
+
+ result->typid = (Oid *) palloc0(n * sizeof(Oid));
+ memcpy(result->typid, typinfo->typid, n * sizeof(Oid));
+ result->typmod = (int32 *) palloc0(n * sizeof(int32));
+ memcpy(result->typmod, typinfo->typmod, n * sizeof(int32));
+ result->typlen = (int16 *) palloc0(n * sizeof(int16));
+ memcpy(result->typlen, typinfo->typlen, n * sizeof(int16));
+ result->typbyval = (bool *) palloc0(n * sizeof(bool));
+ memcpy(result->typbyval, typinfo->typbyval, n * sizeof(bool));
+ result->typalign = (char *) palloc0(n * sizeof(bool));
+ memcpy(result->typalign, typinfo->typalign, n * sizeof(bool));
+
+ return result;
+}
+
/*
* CopyPartitionKeys
*
@@ -389,6 +458,10 @@ CopyPartitionKeys(List *keys)
memcpy(newkey->partsupfunc, fromkey->partsupfunc,
newkey->partnatts * sizeof(FmgrInfo));
+ newkey->partexprs = copyObject(fromkey->partexprs);
+ newkey->typinfo = copy_key_type_info(fromkey->partnatts,
+ fromkey->typinfo);
+
result = lappend(result, newkey);
}
@@ -396,6 +469,22 @@ CopyPartitionKeys(List *keys)
}
/*
+ * free_key_type_info
+ */
+static void
+free_key_type_info(KeyTypeInfo *typinfo)
+{
+ Assert(typinfo != NULL);
+
+ pfree(typinfo->typid);
+ pfree(typinfo->typmod);
+ pfree(typinfo->typlen);
+ pfree(typinfo->typbyval);
+ pfree(typinfo->typalign);
+ pfree(typinfo);
+}
+
+/*
* FreePartitionKeys
*/
void
@@ -413,83 +502,13 @@ FreePartitionKeys(List *keys)
pfree(key->partattrs);
pfree(key->partopfamily);
pfree(key->partsupfunc);
+ free_key_type_info(key->typinfo);
pfree(key);
}
pfree(keys);
}
/*
- * get_partition_key_type_info
- * Returns a PartitionKeyTypeInfo object that includes type info
- * for individual partition key attributes.
- *
- * Note to callers: make sure to call this on root parents (that is,
- * RELKIND_PARTITIONED_REL relations)
- */
-PartitionKeyTypeInfo *
-get_partition_key_type_info(Relation rel, PartitionKey key)
-{
- int i;
- List *partexprs;
- ListCell *partexprs_item;
- PartitionKeyTypeInfo *result;
-
- Assert(rel->rd_rel->relkind == RELKIND_PARTITIONED_REL);
-
- partexprs = RelationGetPartitionExpr(rel, key->level);
-
- result = (PartitionKeyTypeInfo *) palloc0(sizeof(PartitionKeyTypeInfo));
- result->typid = (Oid *) palloc0(key->partnatts * sizeof(Oid));
- result->typmod = (int32 *) palloc0(key->partnatts * sizeof(int32));
- result->typlen = (int16 *) palloc0(key->partnatts * sizeof(int16));
- result->typbyval = (bool *) palloc0(key->partnatts * sizeof(bool));
- result->typalign = (char *) palloc0(key->partnatts * sizeof(bool));
-
- partexprs_item = list_head(partexprs);
- for (i = 0; i < key->partnatts; i++)
- {
- AttrNumber attno = key->partattrs[i];
-
- if (attno != InvalidAttrNumber)
- {
- result->typid[i] = rel->rd_att->attrs[attno - 1]->atttypid;
- result->typmod[i] = rel->rd_att->attrs[attno - 1]->atttypmod;
- }
- else
- {
- result->typid[i] = exprType(lfirst(partexprs_item));
- result->typmod[i] = exprTypmod(lfirst(partexprs_item));
- partexprs_item = lnext(partexprs_item);
- }
- get_typlenbyvalalign(result->typid[i],
- &result->typlen[i],
- &result->typbyval[i],
- &result->typalign[i]);
- }
-
- if (partexprs)
- pfree(partexprs);
-
- return result;
-}
-
-/*
- * free_key_type_info
- */
-void
-free_partition_key_type_info(PartitionKeyTypeInfo *typinfo)
-{
- Assert(typinfo != NULL);
-
- pfree(typinfo->typid);
- pfree(typinfo->typmod);
- pfree(typinfo->typlen);
- pfree(typinfo->typbyval);
- pfree(typinfo->typalign);
- pfree(typinfo);
-}
-
-/*
* StorePartition
* Store information of new partition of rel into pg_partition
*
@@ -497,7 +516,7 @@ free_partition_key_type_info(PartitionKeyTypeInfo *typinfo)
* 'parentOid' is OID of the actual parent of 'partition'.
*/
void
-StorePartition(Relation rel, PartitionKey key, PartitionKeyTypeInfo *typinfo,
+StorePartition(Relation rel, PartitionKey key,
Oid partitionOid,
Oid parentOid,
bool valid,
@@ -522,10 +541,10 @@ StorePartition(Relation rel, PartitionKey key, PartitionKeyTypeInfo *typinfo,
Assert(listnvalues > 0);
arr_listvalues = construct_array(boundDatums,
listnvalues,
- typinfo->typid[0],
- typinfo->typlen[0],
- typinfo->typbyval[0],
- typinfo->typalign[0]);
+ key->typinfo->typid[0],
+ key->typinfo->typlen[0],
+ key->typinfo->typbyval[0],
+ key->typinfo->typalign[0]);
/* No range bounds */
nulls[Anum_pg_partition_partrangemaxs - 1] = true;
break;
@@ -614,8 +633,7 @@ RelationDropPartitions(Oid relid)
* though, since it could disappear due to relcache invalidation.)
*/
PartitionDesc
-RelationGetPartitionDesc(Relation relation, PartitionKey key,
- PartitionKeyTypeInfo *typinfo, bool include_all)
+RelationGetPartitionDesc(Relation relation, PartitionKey key, bool include_all)
{
int i,
j;
@@ -626,15 +644,11 @@ RelationGetPartitionDesc(Relation relation, PartitionKey key,
/* Quick copy and exit if already computed the descriptor */
if (relation->rd_partdesc != NULL)
- return CopyPartitionDesc(relation->rd_partdesc,
- key->partnatts,
- key->strategy,
- typinfo);
+ return CopyPartitionDesc(relation->rd_partdesc, key);
result = (PartitionDesc) palloc0(sizeof(PartitionDescData));
- partitions = GetRelPartitions(relation, key, typinfo, !include_all,
- &numparts);
+ partitions = GetRelPartitions(relation, key, !include_all, &numparts);
result->numparts = numparts;
if (partitions)
@@ -671,12 +685,12 @@ RelationGetPartitionDesc(Relation relation, PartitionKey key,
Datum *from = &partitions[i]->listvalues[j];
Datum *into = &result->listvalues[i][j];
- if (!typinfo->typbyval[0])
+ if (!key->typinfo->typbyval[0])
{
- if (typinfo->typlen[0] == -1)
+ if (key->typinfo->typlen[0] == -1)
*into = PointerGetDatum(PG_DETOAST_DATUM_COPY(*from));
else
- *into = datumCopy(*from, false, typinfo->typlen[0]);
+ *into = datumCopy(*from, false, key->typinfo->typlen[0]);
}
else
*into = *from;
@@ -694,12 +708,12 @@ RelationGetPartitionDesc(Relation relation, PartitionKey key,
Datum *from = &partitions[i]->rangemaxs[j];
Datum *into = &result->rangemaxs[j][i];
- if (!typinfo->typbyval[j])
+ if (!key->typinfo->typbyval[j])
{
- if (typinfo->typlen[j] == -1)
+ if (key->typinfo->typlen[j] == -1)
*into = PointerGetDatum(PG_DETOAST_DATUM_COPY(*from));
else
- *into = datumCopy(*from, false, typinfo->typlen[j]);
+ *into = datumCopy(*from, false, key->typinfo->typlen[j]);
}
else
*into = *from;
@@ -722,10 +736,7 @@ RelationGetPartitionDesc(Relation relation, PartitionKey key,
relation->rd_partdesc = MemoryContextAllocZero(relation->rd_pdcxt,
sizeof(PartitionDescData));
oldcxt = MemoryContextSwitchTo(relation->rd_pdcxt);
- relation->rd_partdesc = CopyPartitionDesc(result,
- key->partnatts,
- key->strategy,
- typinfo);
+ relation->rd_partdesc = CopyPartitionDesc(result, key);
MemoryContextSwitchTo(oldcxt);
return result;
@@ -769,8 +780,7 @@ range_partition_cmp_max(const void *a, const void *b, void *arg)
* Copy PartitionDesc of a partitioned relation into caller's context
*/
static PartitionDesc
-CopyPartitionDesc(PartitionDesc src, int partnatts, char strategy,
- PartitionKeyTypeInfo *typinfo)
+CopyPartitionDesc(PartitionDesc src, PartitionKey key)
{
PartitionDesc result;
int i, j;
@@ -782,7 +792,7 @@ CopyPartitionDesc(PartitionDesc src, int partnatts, char strategy,
if (result->numparts > 0)
{
result->oids = (Oid *) palloc0(result->numparts * sizeof(Oid));
- switch (strategy)
+ switch (key->strategy)
{
case PARTITION_STRAT_LIST:
result->listnvalues = (int *)
@@ -791,7 +801,7 @@ CopyPartitionDesc(PartitionDesc src, int partnatts, char strategy,
palloc0(result->numparts * sizeof(Datum *));
break;
case PARTITION_STRAT_RANGE:
- for (i = 0; i < partnatts; i++)
+ for (i = 0; i < key->partnatts; i++)
result->rangemaxs[i] = (Datum *)
palloc0(result->numparts * sizeof(Datum));
break;
@@ -802,7 +812,7 @@ CopyPartitionDesc(PartitionDesc src, int partnatts, char strategy,
{
result->oids[i] = src->oids[i];
- switch (strategy)
+ switch (key->strategy)
{
case PARTITION_STRAT_LIST:
result->listnvalues[i] = src->listnvalues[i];
@@ -814,12 +824,12 @@ CopyPartitionDesc(PartitionDesc src, int partnatts, char strategy,
Datum *from = &src->listvalues[i][j];
Datum *into = &result->listvalues[i][j];
- if (!typinfo->typbyval[0])
+ if (!key->typinfo->typbyval[0])
{
- if (typinfo->typlen[0] == -1)
+ if (key->typinfo->typlen[0] == -1)
*into = PointerGetDatum(PG_DETOAST_DATUM_COPY(*from));
else
- *into = datumCopy(*from, false, typinfo->typlen[0]);
+ *into = datumCopy(*from, false, key->typinfo->typlen[0]);
}
else
*into = *from;
@@ -827,17 +837,17 @@ CopyPartitionDesc(PartitionDesc src, int partnatts, char strategy,
break;
case PARTITION_STRAT_RANGE:
- for (j = 0; j < partnatts; j++)
+ for (j = 0; j < key->partnatts; j++)
{
Datum *from = &src->rangemaxs[j][i];
Datum *into = &result->rangemaxs[j][i];
- if (!typinfo->typbyval[j])
+ if (!key->typinfo->typbyval[j])
{
- if (typinfo->typlen[j] == -1)
+ if (key->typinfo->typlen[j] == -1)
*into = PointerGetDatum(PG_DETOAST_DATUM_COPY(*from));
else
- *into = datumCopy(*from, false, typinfo->typlen[j]);
+ *into = datumCopy(*from, false, key->typinfo->typlen[j]);
}
else
*into = *from;
@@ -859,8 +869,8 @@ CopyPartitionDesc(PartitionDesc src, int partnatts, char strategy,
* Returns number of partitions in *numparts.
*/
static PartitionInfo **
-GetRelPartitions(Relation rel, PartitionKey key, PartitionKeyTypeInfo *typinfo,
- bool only_valid, int *numparts)
+GetRelPartitions(Relation rel, PartitionKey key, bool only_valid,
+ int *numparts)
{
List *partoids;
int i;
@@ -906,10 +916,10 @@ GetRelPartitions(Relation rel, PartitionKey key, PartitionKeyTypeInfo *typinfo,
if (!isnull)
{
deconstruct_array(DatumGetArrayTypeP(datum),
- typinfo->typid[0],
- typinfo->typlen[0],
- typinfo->typbyval[0],
- typinfo->typalign[0],
+ key->typinfo->typid[0],
+ key->typinfo->typlen[0],
+ key->typinfo->typbyval[0],
+ key->typinfo->typalign[0],
&result->listvalues, NULL, &result->listnvalues);
ReleaseSysCache(tuple);
@@ -941,10 +951,10 @@ GetRelPartitions(Relation rel, PartitionKey key, PartitionKeyTypeInfo *typinfo,
int dummy;
deconstruct_array(arr,
- typinfo->typid[j],
- typinfo->typlen[j],
- typinfo->typbyval[j],
- typinfo->typalign[j],
+ key->typinfo->typid[j],
+ key->typinfo->typlen[j],
+ key->typinfo->typbyval[j],
+ key->typinfo->typalign[j],
&datum, &nulls, &dummy);
Assert(!nulls[0]);
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index adfaa2c..5e8726b 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -458,12 +458,9 @@ static void ATExecChangePersistence(List **wqueue, AlteredTableInfo *tab,
Relation rel, char persistence, LOCKMODE lockmode);
static void SetRelationPersistence(Oid relationId, char persistence);
static Datum *EvalPartitionBound(Relation rootParent, Relation parent,
- PartitionKey key, PartitionKeyTypeInfo *typinfo,
- PartitionValues *values, int *listnvalues);
-static Datum *evalPartitionListBound(List *values, PartitionKeyTypeInfo *typinfo,
- int nvalues);
-static Datum *evalPartitionRangeBound(List *values, PartitionKeyTypeInfo *typinfo,
- int partnatts);
+ PartitionKey key, PartitionValues *values, int *listnvalues);
+static Datum *evalPartitionListBound(List *values, PartitionKey key);
+static Datum *evalPartitionRangeBound(List *values, PartitionKey key);
static void ATExecAddPartition(Relation rootParent, RangeVar *name, RangeVar *parent,
PartitionValues *values);
static void ATExecRemovePartition(Relation rootParent, RangeVar *name, bool is_detach,
@@ -6050,10 +6047,9 @@ is_partition_attr(Relation rel, AttrNumber attnum, bool *is_expr)
foreach(cell, keys)
{
PartitionKey key = lfirst(cell);
- int16 level = key->level;
int partnatts = key->partnatts;
AttrNumber *partattrs = key->partattrs;
- List *partexprs = RelationGetPartitionExpr(rel, level);
+ List *partexprs = key->partexprs;
ListCell *partexpr_item;
int i;
@@ -12918,7 +12914,6 @@ ATExecAddPartition(Relation rootParent, RangeVar *name, RangeVar *parent,
Datum *boundDatums;
int listnvalues;
int parent_level;
- PartitionKeyTypeInfo *typinfo;
partRel = heap_openrv(name, NoLock);
@@ -12934,14 +12929,12 @@ ATExecAddPartition(Relation rootParent, RangeVar *name, RangeVar *parent,
partKey = is_subpart ? list_nth(rootParent->rd_partkeys, parent_level) :
linitial(rootParent->rd_partkeys);
- typinfo = get_partition_key_type_info(rootParent, partKey);
-
/* Evaluate and validate new partition's bound */
- boundDatums = EvalPartitionBound(rootParent, parentRel, partKey, typinfo,
- values, &listnvalues);
+ boundDatums = EvalPartitionBound(rootParent, parentRel, partKey, values,
+ &listnvalues);
/* Update the catalog */
- StorePartition(rootParent, partKey, typinfo,
+ StorePartition(rootParent, partKey,
RelationGetRelid(partRel),
RelationGetRelid(parentRel),
true,
@@ -12951,7 +12944,6 @@ ATExecAddPartition(Relation rootParent, RangeVar *name, RangeVar *parent,
/* Tell world about the new partition of parent. */
CacheInvalidateRelcache(parentRel);
- free_partition_key_type_info(typinfo);
heap_close(partRel, NoLock);
if (parentRel != rootParent)
heap_close(parentRel, AccessExclusiveLock);
@@ -12967,8 +12959,7 @@ ATExecAddPartition(Relation rootParent, RangeVar *name, RangeVar *parent,
*/
static Datum *
EvalPartitionBound(Relation rootParent, Relation parent, PartitionKey key,
- PartitionKeyTypeInfo *typinfo, PartitionValues *values,
- int *listnvalues)
+ PartitionValues *values, int *listnvalues)
{
PartitionDesc pdesc;
Datum *result;
@@ -12976,7 +12967,7 @@ EvalPartitionBound(Relation rootParent, Relation parent, PartitionKey key,
Oid overlapsWith;
int i;
- pdesc = RelationGetPartitionDesc(parent, key, typinfo, true);
+ pdesc = RelationGetPartitionDesc(parent, key, true);
/* result->oid is set by the caller */
@@ -12988,8 +12979,7 @@ EvalPartitionBound(Relation rootParent, Relation parent, PartitionKey key,
Assert(list_length(values->listvalues) >= 1);
result = (Datum *) palloc0(*listnvalues * sizeof(Datum));
- result = evalPartitionListBound(values->listvalues, typinfo,
- *listnvalues);
+ result = evalPartitionListBound(values->listvalues, key);
/* TODO: uniqueify(result->listvalues) */
@@ -13005,7 +12995,7 @@ EvalPartitionBound(Relation rootParent, Relation parent, PartitionKey key,
case PARTITION_STRAT_RANGE:
Assert(list_length(values->rangemaxs) == key->partnatts);
- rangemaxs = evalPartitionRangeBound(values->rangemaxs, typinfo, key->partnatts);
+ rangemaxs = evalPartitionRangeBound(values->rangemaxs, key);
if (range_partition_overlaps(pdesc, key, rangemaxs))
ereport(ERROR,
@@ -13037,8 +13027,10 @@ EvalPartitionBound(Relation rootParent, Relation parent, PartitionKey key,
lbs[0] = 1;
rangemax = construct_md_array(datums, nulls, 1, dims, lbs,
- typinfo->typid[i], typinfo->typlen[i],
- typinfo->typbyval[i], typinfo->typalign[i]);
+ key->typinfo->typid[i],
+ key->typinfo->typlen[i],
+ key->typinfo->typbyval[i],
+ key->typinfo->typalign[i]);
result[i] = PointerGetDatum(rangemax);
}
@@ -13053,19 +13045,19 @@ EvalPartitionBound(Relation rootParent, Relation parent, PartitionKey key,
* for a list partition
*/
static Datum *
-evalPartitionListBound(List *values, PartitionKeyTypeInfo *typinfo, int nvalues)
+evalPartitionListBound(List *values, PartitionKey key)
{
ListCell *cell;
ParseState *pstate;
EState *estate;
ExprContext *ecxt;
int i;
- Datum *datum;
+ Datum *datum;
if (!values)
return NULL;
- datum = (Datum *) palloc(nvalues * sizeof(Datum));
+ datum = (Datum *) palloc(list_length(values) * sizeof(Datum));
pstate = make_parsestate(NULL);
estate = CreateExecutorState();
ecxt = GetPerTupleExprContext(estate);
@@ -13089,12 +13081,12 @@ evalPartitionListBound(List *values, PartitionKeyTypeInfo *typinfo, int nvalues)
MemoryContextSwitchTo(oldcxt);
- if (!typinfo->typbyval[0])
+ if (!key->typinfo->typbyval[0])
{
- if (typinfo->typlen[0] == -1)
+ if (key->typinfo->typlen[0] == -1)
datum[i] = PointerGetDatum(PG_DETOAST_DATUM_COPY(datum[i]));
else
- datum[i] = datumCopy(datum[i], false, typinfo->typlen[0]);
+ datum[i] = datumCopy(datum[i], false, key->typinfo->typlen[0]);
}
ResetPerTupleExprContext(estate);
@@ -13110,7 +13102,7 @@ evalPartitionListBound(List *values, PartitionKeyTypeInfo *typinfo, int nvalues)
* clause for a range partition
*/
static Datum *
-evalPartitionRangeBound(List *values, PartitionKeyTypeInfo *typinfo, int partnatts)
+evalPartitionRangeBound(List *values, PartitionKey key)
{
ListCell *cell;
ParseState *pstate;
@@ -13122,7 +13114,7 @@ evalPartitionRangeBound(List *values, PartitionKeyTypeInfo *typinfo, int partnat
if (!values)
return NULL;
- datum = (Datum *) palloc(partnatts * sizeof(Datum));
+ datum = (Datum *) palloc(key->partnatts * sizeof(Datum));
pstate = make_parsestate(NULL);
estate = CreateExecutorState();
ecxt = GetPerTupleExprContext(estate);
@@ -13146,12 +13138,12 @@ evalPartitionRangeBound(List *values, PartitionKeyTypeInfo *typinfo, int partnat
MemoryContextSwitchTo(oldcxt);
- if (!typinfo->typbyval[i])
+ if (!key->typinfo->typbyval[i])
{
- if (typinfo->typlen[i] == -1)
+ if (key->typinfo->typlen[i] == -1)
datum[i] = PointerGetDatum(PG_DETOAST_DATUM_COPY(datum[i]));
else
- datum[i] = datumCopy(datum[i], false, typinfo->typlen[i]);
+ datum[i] = datumCopy(datum[i], false, key->typinfo->typlen[i]);
}
ResetPerTupleExprContext(estate);
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 94731ed..cdfea1c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3208,12 +3208,8 @@ transformPartitionValues(CreateStmtContext *cxt, PartitionKey key,
{
int i;
ListCell *cell;
- PartitionKeyTypeInfo *typinfo;
PartitionValues *result = (PartitionValues *)
makeNode(PartitionValues);
-
- typinfo = get_partition_key_type_info(cxt->rel, key);
-
switch (key->strategy)
{
case PARTITION_STRAT_LIST:
@@ -3234,8 +3230,8 @@ transformPartitionValues(CreateStmtContext *cxt, PartitionKey key,
valuetype = exprType(value);
value = coerce_to_target_type(cxt->pstate,
value, valuetype,
- typinfo->typid[0],
- typinfo->typmod[0],
+ key->typinfo->typid[0],
+ key->typinfo->typmod[0],
COERCION_ASSIGNMENT,
COERCE_IMPLICIT_CAST,
-1);
@@ -3244,7 +3240,7 @@ transformPartitionValues(CreateStmtContext *cxt, PartitionKey key,
(errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("partition key column is of type %s"
" but expression is of type %s",
- format_type_be(typinfo->typid[0]),
+ format_type_be(key->typinfo->typid[0]),
format_type_be(valuetype)),
errhint("You will need to rewrite or cast the expression."),
parser_errposition(cxt->pstate, exprLocation(orig_value))));
@@ -3288,8 +3284,8 @@ transformPartitionValues(CreateStmtContext *cxt, PartitionKey key,
valuetype = exprType(value);
value = coerce_to_target_type(NULL,
value, valuetype,
- typinfo->typid[i],
- typinfo->typmod[i],
+ key->typinfo->typid[i],
+ key->typinfo->typmod[i],
COERCION_ASSIGNMENT,
COERCE_IMPLICIT_CAST,
-1);
@@ -3298,7 +3294,7 @@ transformPartitionValues(CreateStmtContext *cxt, PartitionKey key,
(errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("partition key column is of type %s"
" but expression is of type %s",
- format_type_be(typinfo->typid[0]),
+ format_type_be(key->typinfo->typid[0]),
format_type_be(valuetype)),
errhint("You will need to rewrite or cast the expression."),
parser_errposition(cxt->pstate, exprLocation(orig_value))));
@@ -3309,7 +3305,6 @@ transformPartitionValues(CreateStmtContext *cxt, PartitionKey key,
break;
}
result->location = values->location;
- free_partition_key_type_info(typinfo);
return result;
}
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index ddb0da2..f6c656c 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -4514,94 +4514,6 @@ RelationGetExclusionInfo(Relation indexRelation,
}
/*
- * RelationGetPartitionExpr -- get the partition key expressions
- *
- * The returned node tree is copied into the caller's memory context. (We
- * don't want to return a pointer to the relcache copy,since it could
- * disappear due to relcache invalidation.)
- */
-List *
-RelationGetPartitionExpr(Relation relation, int16 level)
-{
- ListCell *cell;
- Relation catalog;
- HeapTuple tuple;
- List *result = NIL;
- Datum exprsDatum;
- bool isnull;
- char *exprsString;
- MemoryContext oldcxt;
-
- /* Shouldn't happen but play safe! */
- if (relation->rd_partkeys == NIL)
- return NIL;
-
- /* Find the proper key in relcache */
- foreach(cell, relation->rd_partkeys)
- {
- PartitionKey key = lfirst(cell);
-
- if (key->level != level)
- continue;
-
- /* Quick copy if we already computed the result */
- if (key->partexprs)
- return (List *) copyObject(key->partexprs);
-
- /* Need to compute from the tuple in the syscache */
- tuple = SearchSysCache2(PARTEDRELIDLEVEL,
- RelationGetRelid(relation),
- level);
- Assert(HeapTupleIsValid(tuple));
-
- /*
- * We build the tree we intend to return in the caller's context.
- * After successfully completing the work, we copy it into the
- * relcache entry. This avoids problems if we get some sort of
- * error partway through.
- */
- catalog = heap_open(PartitionedRelRelationId, AccessShareLock);
- exprsDatum = heap_getattr(tuple,
- Anum_pg_partitioned_rel_partexprs,
- RelationGetDescr(catalog),
- &isnull);
-
- /* No expressions in the key at this level */
- if (isnull)
- {
- ReleaseSysCache(tuple);
- heap_close(catalog, AccessShareLock);
- return NIL;
- }
-
- exprsString = TextDatumGetCString(exprsDatum);
- result = (List *) stringToNode(exprsString);
- pfree(exprsString);
-
- /*
- * Run the expressions through eval_const_expressions. This is not just an
- * optimization, but is necessary, because the planner will be comparing
- * them to similarly-processed qual clauses, and may fail to detect valid
- * matches without this. We don't bother with canonicalize_qual, however.
- */
- result = (List *) eval_const_expressions(NULL, (Node *) result);
-
- /* May as well fix opfuncids too */
- fix_opfuncids((Node *) result);
-
- /* Now save a copy of the completed tree in the relcache entry. */
- oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
- key->partexprs = (List *) copyObject(result);
- MemoryContextSwitchTo(oldcxt);
-
- ReleaseSysCache(tuple);
- heap_close(catalog, AccessShareLock);
- }
-
- return result;
-}
-
-/*
* Routines to support ereport() reports of relation-related errors
*
* These could have been put into elog.c, but it seems like a module layering
diff --git a/src/include/catalog/partition.h b/src/include/catalog/partition.h
index 7f1576a..c54fc1b 100644
--- a/src/include/catalog/partition.h
+++ b/src/include/catalog/partition.h
@@ -16,29 +16,30 @@
#include "fmgr.h"
#include "utils/relcache.h"
-/* Partition key information for one level */
-typedef struct PartitionKeyData
-{
- char strategy; /* partition strategy */
- int16 level; /* partition level */
- int16 partnatts; /* number of partition attributes */
- AttrNumber *partattrs; /* partition attnums */
- Oid *partopfamily; /* OIDs of opfamily per col */
- FmgrInfo *partsupfunc; /* lookup info for support funcs */
- List *partexprs; /* partition key expressions, if any */
-} PartitionKeyData;
-
-typedef struct PartitionKeyData *PartitionKey;
-
/* Type information for partition key columns */
-typedef struct PartitionKeyTypeInfo
+typedef struct KeyTypeInfo
{
Oid *typid;
int32 *typmod;
int16 *typlen;
bool *typbyval;
char *typalign;
-} PartitionKeyTypeInfo;
+} KeyTypeInfo;
+
+/* Partition key information for one level */
+typedef struct PartitionKeyData
+{
+ char strategy; /* partition strategy */
+ int16 level; /* partition level */
+ int16 partnatts; /* number of partition attributes */
+ AttrNumber *partattrs; /* partition attnums */
+ List *partexprs; /* partition key expressions, if any */
+ KeyTypeInfo *typinfo; /* key column type information */
+ Oid *partopfamily; /* OIDs of opfamily per col */
+ FmgrInfo *partsupfunc; /* lookup info for support funcs */
+} PartitionKeyData;
+
+typedef struct PartitionKeyData *PartitionKey;
/*
* Information about partitions of a partitioned table or sub-partitions
@@ -76,13 +77,9 @@ extern void RemovePartitionKeysByRelId(Oid relid);
extern void RelationBuildPartitionKeys(Relation relation);
extern List *CopyPartitionKeys(List *keys);
extern void FreePartitionKeys(List *keys);
-extern PartitionKeyTypeInfo *get_partition_key_type_info(Relation rel,
- PartitionKey key);
-extern void free_partition_key_type_info(PartitionKeyTypeInfo *typinfo);
extern bool relid_is_partitioned(Oid relid);
extern void StorePartition(Relation rel, PartitionKey key,
- PartitionKeyTypeInfo *typinfo,
Oid partitionOid,
Oid parentOid,
bool valid,
@@ -91,9 +88,7 @@ extern void StorePartition(Relation rel, PartitionKey key,
extern void RemovePartitionEntryByRelId(Oid relid);
extern void RelationDropPartitions(Oid relid);
extern PartitionDesc RelationGetPartitionDesc(Relation relation,
- PartitionKey key,
- PartitionKeyTypeInfo *typinfo,
- bool include_all);
+ PartitionKey key, bool include_all);
extern List *get_partitions(Oid relid, int lockmode);
extern List *get_all_partitions(Oid relid, int lockmode);
extern List *get_leaf_partitions(Oid relid, int lockmode);
diff --git a/src/include/utils/relcache.h b/src/include/utils/relcache.h
index 0aa2104..1b48304 100644
--- a/src/include/utils/relcache.h
+++ b/src/include/utils/relcache.h
@@ -42,8 +42,6 @@ extern Oid RelationGetOidIndex(Relation relation);
extern Oid RelationGetReplicaIndex(Relation relation);
extern List *RelationGetIndexExpressions(Relation relation);
extern List *RelationGetIndexPredicate(Relation relation);
-extern List *RelationGetPartitionExpr(Relation relation,
- int16 level);
typedef enum IndexAttrBitmapKind
{
--
1.7.1
[text/x-diff] 0009-Tuple-routing-functionality.patch (40.7K, ../../56C1302D.6070403@lab.ntt.co.jp/10-0009-Tuple-routing-functionality.patch)
download | inline diff:
From 0d7d2c90ca2a61dd679bbc98ecf69d865c8e8055 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Wed, 10 Feb 2016 19:42:54 +0900
Subject: [PATCH 09/10] Tuple routing functionality.
Newly introduced get_partition_for_tuple takes a tuple (TupleTableSlot)
and returns OID of the leaf partition to which the tuple maps to. Teach
CopyFrom() and ExecInsert() to utilize this new interface.
Newly introduced PartitionKeyExecInfo structure can be used to derive
partition key from a tuple being routed. It mirrors the PartitionKey but
contains some executor state as well. New field ri_PartitionKeyInfo
is a list of these structures.
ResultRelInfo has one more new field ri_PartitionDesc which is the
partition descriptor of the root table and initialized in
InitResultRelInfo along with ri_PartitionKeyInfo. Have to find a
way to be able to somehow cache relation descriptors of internal
partition in EState because they need to be computed (well copied
from relcache) for each tuple routed.
ExecUpdate and ExecDelete require more information viz. OID of the
partition that the modified or to-be-deleted row came from. Extend
JunkFilter mechanism to help with that. Also, during rewrite, if the
UPDATE/DELETE target is a partitioned table, add junk TLE for tableoid
column so that the above works. Of course, UPDATE and DELETE cases
don't work yet because there is no way yet to scan a partitioned table
which will be fixed shortly.
Note that INSERT's ON CONFLICT clause is not supported.
---
src/backend/catalog/partition.c | 338 ++++++++++++++++++++++++++++++++
src/backend/commands/copy.c | 106 ++++++++++-
src/backend/executor/execMain.c | 26 +--
src/backend/executor/nodeModifyTable.c | 299 ++++++++++++++++++++++++++--
src/backend/parser/parse_clause.c | 9 +
src/backend/rewrite/rewriteHandler.c | 25 +++
src/include/catalog/partition.h | 9 +
src/include/executor/executor.h | 15 ++
src/include/nodes/execnodes.h | 25 +++
src/include/nodes/nodes.h | 1 +
10 files changed, 815 insertions(+), 38 deletions(-)
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index cf48bf4..9baa0a0 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -76,6 +76,31 @@ static int32 rangemaxs_cmp_datum(const Datum *vals,
int partnatts,
FmgrInfo *partsupfunc);
+static void FormPartitionKeyDatum(PartitionKeyExecInfo *pkinfo,
+ TupleTableSlot *slot,
+ EState *estate,
+ Datum *values,
+ bool *isnull);
+static Oid get_partition_for_tuple_recurse(Relation rel,
+ ListCell *key_cell,
+ ListCell *pkinfo_cell,
+ PartitionDesc pdesc,
+ TupleTableSlot *slot,
+ EState *estate);
+static Oid list_partition_for_tuple(PartitionKey key, PartitionDesc pdesc,
+ Datum value);
+static Oid range_partition_for_tuple(PartitionKey key, PartitionDesc pdesc,
+ Datum *values);
+typedef int32 (*bsearch_compare_fn) (const Datum *vals,
+ const Datum *rangemaxs,
+ int partnatts,
+ FmgrInfo *partsupfunc);
+static int range_minmax_bsearch(PartitionDesc pdesc,
+ FmgrInfo *partsupfunc,
+ int partnatts,
+ const Datum *key,
+ bsearch_compare_fn cmp_fn);
+
/*
* StorePartitionKey
* Store the partition keys of rel into pg_partitioned_rel catalog
@@ -1447,3 +1472,316 @@ rangemaxs_cmp_datum(const Datum *vals, const Datum *rangemaxs,
return 0;
}
+
+/* ----------------
+ * BuildPartitionKeyExecInfo
+ * Construct a list of PartitionKeyExecInfo records for an open
+ * relation
+ *
+ * PartitionKeyExecInfo stores the information about the partition key
+ * that's needed when inserting tuples into a partitioned relations;
+ * especially partition expression state. Normally we build a
+ * PartitionKeyExecInfo for a partitioned relation just once per
+ * command, and then use it for (potentially) many tuples.
+ * ----------------
+ */
+List *
+BuildPartitionKeyExecInfo(Relation rel)
+{
+ List *result = NIL;
+ ListCell *cell;
+ int16 level;
+
+ level = 1;
+ foreach (cell, rel->rd_partkeys)
+ {
+ int i;
+ PartitionKey key = (PartitionKey) lfirst(cell);
+ PartitionKeyExecInfo *pi = makeNode(PartitionKeyExecInfo);
+ int numKeys = key->partnatts;
+
+ if (numKeys < 1 || numKeys > PARTITION_MAX_KEYS)
+ elog(ERROR, "invalid partnatts %d for level %d key of partitioned"
+ " relation %u", numKeys, level, RelationGetRelid(rel));
+
+ pi->pi_NumKeyAttrs = numKeys;
+ for (i = 0; i < numKeys; i++)
+ pi->pi_KeyAttrNumbers[i] = key->partattrs[i];
+
+ /* Fetch any partition key expressions */
+ pi->pi_Expressions = copyObject(key->partexprs);
+ pi->pi_ExpressionsState = NIL;
+
+ result = lappend(result, pi);
+ level++;
+ }
+
+ return result;
+}
+
+/*
+ * FormPartitionKeyDatum
+ * Construct values[] and isnull[] arrays for partition key columns
+ * from the tuple contained in slot
+ */
+static void
+FormPartitionKeyDatum(PartitionKeyExecInfo *pkinfo,
+ TupleTableSlot *slot,
+ EState *estate,
+ Datum *values,
+ bool *isnull)
+{
+ ListCell *partexpr_item;
+ int i;
+
+ if (pkinfo->pi_Expressions != NIL &&
+ pkinfo->pi_ExpressionsState == NIL)
+ {
+ /* First time through, set up expression evaluation state */
+ pkinfo->pi_ExpressionsState = (List *)
+ ExecPrepareExpr((Expr *) pkinfo->pi_Expressions,
+ estate);
+ /* Check caller has set up context correctly */
+ Assert(GetPerTupleExprContext(estate)->ecxt_scantuple == slot);
+ }
+ partexpr_item = list_head(pkinfo->pi_ExpressionsState);
+
+ for (i = 0; i < pkinfo->pi_NumKeyAttrs; i++)
+ {
+ AttrNumber keycol = pkinfo->pi_KeyAttrNumbers[i];
+ Datum pkDatum;
+ bool isNull;
+
+ if (keycol != 0)
+ {
+ /* Plain column; get the value directly from the heap tuple */
+ pkDatum = slot_getattr(slot, keycol, &isNull);
+ }
+ else
+ {
+ /* Expression; need to evaluate it */
+ if (partexpr_item == NULL)
+ elog(ERROR, "wrong number of partition key expressions");
+ pkDatum = ExecEvalExprSwitchContext((ExprState *) lfirst(partexpr_item),
+ GetPerTupleExprContext(estate),
+ &isNull,
+ NULL);
+ partexpr_item = lnext(partexpr_item);
+ }
+ values[i] = pkDatum;
+ isnull[i] = isNull;
+ }
+
+ if (partexpr_item != NULL)
+ elog(ERROR, "wrong number of partition key expressions");
+}
+
+/*
+ * get_partition_for_tuple
+ * Find the partition for tuple (in slot)
+ */
+Oid
+get_partition_for_tuple(Relation rel,
+ List *pkinfo,
+ PartitionDesc pdesc,
+ TupleTableSlot *slot,
+ EState *estate)
+{
+ if (pdesc->numparts == 0)
+ return InvalidOid;
+
+ return get_partition_for_tuple_recurse(rel,
+ list_head(rel->rd_partkeys),
+ list_head(pkinfo),
+ pdesc, slot, estate);
+}
+
+static Oid
+get_partition_for_tuple_recurse(Relation rel,
+ ListCell *key_cell,
+ ListCell *pkinfo_cell,
+ PartitionDesc pdesc,
+ TupleTableSlot *slot,
+ EState *estate)
+{
+ PartitionKey key = lfirst(key_cell);
+ PartitionKeyExecInfo *pkinfo = lfirst(pkinfo_cell);
+ Relation targetRel;
+ PartitionKey targetKey;
+ PartitionDesc target_pdesc;
+ Oid targetOid,
+ relid = RelationGetRelid(rel);
+ Datum values[PARTITION_MAX_KEYS];
+ bool isnull[PARTITION_MAX_KEYS];
+ int i;
+
+ if (pdesc->numparts == 0)
+ return InvalidOid;
+
+ /* Extract partition key from tuple */
+ Assert(GetPerTupleExprContext(estate)->ecxt_scantuple == slot);
+ FormPartitionKeyDatum(pkinfo,
+ slot,
+ estate,
+ values,
+ isnull);
+
+ /* Disallow nulls in partition key */
+ for (i = 0; i < pkinfo->pi_NumKeyAttrs; i++)
+ if (isnull[i])
+ ereport(ERROR,
+ (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+ errmsg("null value for partition key column \"%s\" not allowed",
+ get_attname(relid, pkinfo->pi_KeyAttrNumbers[i]))));
+
+
+ switch (key->strategy)
+ {
+ case PARTITION_STRAT_LIST:
+ /* Remember, list partition key always consists of single column */
+ targetOid = list_partition_for_tuple(key, pdesc, values[0]);
+ break;
+
+ case PARTITION_STRAT_RANGE:
+ targetOid = range_partition_for_tuple(key, pdesc, values);
+ break;
+ }
+
+ /* this is how recursion ends (or doesn't occur at all) */
+ if (lnext(key_cell) == NULL || !OidIsValid(targetOid))
+ return targetOid;
+
+ targetRel = heap_open(targetOid, AccessShareLock);
+ targetKey = lfirst(lnext(key_cell));
+ target_pdesc = RelationGetPartitionDesc(targetRel, targetKey, true);
+ targetOid = get_partition_for_tuple_recurse(rel, lnext(key_cell),
+ lnext(pkinfo_cell),
+ target_pdesc, slot, estate);
+ heap_close(targetRel, AccessShareLock);
+
+ return targetOid;
+}
+
+/*
+ * list_partition_for_tuple
+ * Find the list partition for a tuple
+ *
+ * There is single datum to compare because the partition key in case of
+ * list partitioning consists of only one column.
+ */
+static Oid
+list_partition_for_tuple(PartitionKey key, PartitionDesc pdesc, Datum value)
+{
+ int i,
+ j;
+ int32 result;
+ FmgrInfo cmpfn = key->partsupfunc[0];
+
+ Assert(pdesc->numparts > 0);
+
+ for (i = 0; i < pdesc->numparts; i++)
+ {
+ for (j = 0; j < pdesc->listnvalues[i]; j++)
+ {
+ result = compare_using_func(cmpfn,
+ pdesc->listvalues[i][j],
+ value);
+
+ if (!result)
+ return pdesc->oids[i];
+ }
+ }
+
+ return InvalidOid;
+}
+
+/*
+ * range_partition_for_tuple
+ * Find the range partition for a tuple
+ *
+ * Range partitioning supports multi-column key, so the key may contain
+ * more than one datum. Comparison function rangemaxs_cmp_datum can
+ * deal with that.
+ */
+static Oid
+range_partition_for_tuple(PartitionKey key, PartitionDesc pdesc, Datum *values)
+{
+ int partidx;
+
+ Assert(pdesc->numparts > 0);
+
+ partidx = range_minmax_bsearch(pdesc,
+ key->partsupfunc,
+ key->partnatts,
+ values,
+ rangemaxs_cmp_datum);
+
+ if (partidx >= 0)
+ return pdesc->oids[partidx];
+
+ return InvalidOid;
+}
+
+/*
+ * range_minmax_bsearch
+ * Search for partition such that left.rangemax <= key < rangemax
+ *
+ * Returns index of the so found partition or -1 if key is found to be
+ * greater than the rangemax of the last partition.
+ */
+static int
+range_minmax_bsearch(PartitionDesc pdesc,
+ FmgrInfo *partsupfunc,
+ int partnatts,
+ const Datum *key,
+ bsearch_compare_fn cmp_fn)
+{
+ int32 cmpval;
+ int low, high, idx, i, result;
+ Datum *rangemaxs;
+
+ rangemaxs = (Datum *) palloc0(partnatts * sizeof(Datum));
+
+ /* Good ol' bsearch */
+ low = 0;
+ high = pdesc->numparts - 1;
+ result = -1;
+ while (low <= high)
+ {
+ idx = (low + high) / 2;
+
+ for (i = 0; i < partnatts; i++)
+ rangemaxs[i] = pdesc->rangemaxs[i][idx];
+
+ cmpval = cmp_fn(key, rangemaxs, partnatts, partsupfunc);
+ if (cmpval < 0)
+ {
+ if (idx == 0)
+ {
+ /* Nothing on left */
+ result = idx;
+ break;
+ }
+ else
+ {
+ for (i = 0; i < partnatts; i++)
+ rangemaxs[i] = pdesc->rangemaxs[i][idx-1];
+
+ cmpval = cmp_fn(key, rangemaxs, partnatts, partsupfunc);
+
+ if (cmpval >= 0)
+ {
+ result = idx;
+ break;
+ }
+ else
+ high = idx - 1;
+ }
+ }
+ else
+ low = idx + 1;
+ }
+
+ pfree(rangemaxs);
+ return result;
+}
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 62b650f..e54a61e 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -36,6 +36,7 @@
#include "miscadmin.h"
#include "optimizer/clauses.h"
#include "optimizer/planner.h"
+#include "parser/parsetree.h"
#include "nodes/makefuncs.h"
#include "rewrite/rewriteHandler.h"
#include "storage/fd.h"
@@ -43,6 +44,7 @@
#include "utils/builtins.h"
#include "utils/lsyscache.h"
#include "utils/memutils.h"
+#include "catalog/partition.h"
#include "utils/portal.h"
#include "utils/rel.h"
#include "utils/rls.h"
@@ -2223,6 +2225,7 @@ CopyFrom(CopyState cstate)
Datum *values;
bool *nulls;
ResultRelInfo *resultRelInfo;
+ ResultRelInfo *saved_resultRelInfo = NULL; /* for partitioned table case */
EState *estate = CreateExecutorState(); /* for ExecConstraints() */
ExprContext *econtext;
TupleTableSlot *myslot;
@@ -2231,7 +2234,7 @@ CopyFrom(CopyState cstate)
ErrorContextCallback errcallback;
CommandId mycid = GetCurrentCommandId(true);
int hi_options = 0; /* start with default heap_insert options */
- BulkInsertState bistate;
+ BulkInsertState bistate = NULL;
uint64 processed = 0;
bool useHeapMultiInsert;
int nBufferedTuples = 0;
@@ -2240,6 +2243,8 @@ CopyFrom(CopyState cstate)
HeapTuple *bufferedTuples = NULL; /* initialize to silence warning */
Size bufferedTuplesSize = 0;
int firstBufferedLineNo = 0;
+ Oid prev_partOid = InvalidOid;
+ ResultRelInfo *prev_partrri = NULL;
Assert(cstate->rel);
@@ -2381,10 +2386,14 @@ CopyFrom(CopyState cstate)
* expressions. Such triggers or expressions might query the table we're
* inserting to, and act differently if the tuples that have already been
* processed and prepared for insertion are not there.
+ *
+ * Also, Do not use multi- mode if inserting into a partitioned table
+ * because we need to determine a partition for each row individually.
*/
if ((resultRelInfo->ri_TrigDesc != NULL &&
(resultRelInfo->ri_TrigDesc->trig_insert_before_row ||
resultRelInfo->ri_TrigDesc->trig_insert_instead_row)) ||
+ resultRelInfo->ri_PartitionDesc != NULL ||
cstate->volatile_defexprs)
{
useHeapMultiInsert = false;
@@ -2409,7 +2418,9 @@ CopyFrom(CopyState cstate)
values = (Datum *) palloc(tupDesc->natts * sizeof(Datum));
nulls = (bool *) palloc(tupDesc->natts * sizeof(bool));
- bistate = GetBulkInsertState();
+ if (resultRelInfo->ri_PartitionDesc == NULL)
+ bistate = GetBulkInsertState();
+
econtext = GetPerTupleExprContext(estate);
/* Set up callback to identify error line number */
@@ -2481,6 +2492,76 @@ CopyFrom(CopyState cstate)
if (cstate->rel->rd_att->constr)
ExecConstraints(resultRelInfo, slot, estate);
+ /* Find the partition for this tuple */
+ if (resultRelInfo->ri_PartitionDesc)
+ {
+ Oid partOid;
+
+ econtext->ecxt_scantuple = slot; /* for partition key expressions */
+ partOid = get_partition_for_tuple(cstate->rel,
+ resultRelInfo->ri_PartitionKeyInfo,
+ resultRelInfo->ri_PartitionDesc,
+ slot,
+ estate);
+ if (OidIsValid(partOid))
+ {
+ saved_resultRelInfo = resultRelInfo;
+
+ /* check if new tuple mapped to the same partition as the last one */
+ if (partOid != prev_partOid)
+ {
+ Relation partRelDesc;
+
+ /* close the last partition */
+ if (prev_partrri)
+ {
+ heap_close(prev_partrri->ri_RelationDesc, RowExclusiveLock);
+ ExecCloseIndices(prev_partrri);
+ pfree(prev_partrri);
+ }
+
+ /* open the new one */
+ partRelDesc = heap_open(partOid, RowExclusiveLock);
+ resultRelInfo = makeNode(ResultRelInfo);
+ InitResultRelInfo(resultRelInfo,
+ partRelDesc,
+ 1, /* dummy */
+ 0);
+ ExecOpenIndices(resultRelInfo, false);
+ prev_partOid = partOid;
+ }
+ else
+ {
+ /* keep using the last one */
+ resultRelInfo = prev_partrri;
+ }
+
+ estate->es_result_relation_info = resultRelInfo;
+ }
+ else
+ {
+ char *val_desc;
+ Bitmapset *modifiedCols;
+ Bitmapset *insertedCols;
+ Bitmapset *updatedCols;
+
+ insertedCols = GetInsertedColumns(resultRelInfo, estate);
+ updatedCols = GetUpdatedColumns(resultRelInfo, estate);
+ modifiedCols = bms_union(insertedCols, updatedCols);
+ val_desc = ExecBuildSlotValueDescription(RelationGetRelid(cstate->rel),
+ slot,
+ RelationGetDescr(cstate->rel),
+ modifiedCols,
+ 64);
+
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("cannot find partition of relation \"%s\" for the new row",
+ RelationGetRelationName(cstate->rel)),
+ val_desc ? errdetail("Failing row contains %s.", val_desc) : 0));
+ }
+ }
+
if (useHeapMultiInsert)
{
/* Add this tuple to the tuple buffer */
@@ -2511,7 +2592,8 @@ CopyFrom(CopyState cstate)
List *recheckIndexes = NIL;
/* OK, store the tuple and create index entries for it */
- heap_insert(cstate->rel, tuple, mycid, hi_options, bistate);
+ heap_insert(resultRelInfo->ri_RelationDesc,
+ tuple, mycid, hi_options, bistate);
if (resultRelInfo->ri_NumIndices > 0)
recheckIndexes = ExecInsertIndexTuples(slot, &(tuple->t_self),
@@ -2532,6 +2614,14 @@ CopyFrom(CopyState cstate)
*/
processed++;
}
+
+ /* Restore the parent resultRelInfo */
+ if (saved_resultRelInfo)
+ {
+ prev_partrri = resultRelInfo;
+ resultRelInfo = saved_resultRelInfo;
+ estate->es_result_relation_info = resultRelInfo;
+ }
}
/* Flush any remaining buffered tuples */
@@ -2544,7 +2634,15 @@ CopyFrom(CopyState cstate)
/* Done, clean up */
error_context_stack = errcallback.previous;
- FreeBulkInsertState(bistate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
+
+ if (prev_partrri)
+ {
+ heap_close(prev_partrri->ri_RelationDesc, RowExclusiveLock);
+ ExecCloseIndices(prev_partrri);
+ pfree(prev_partrri);
+ }
MemoryContextSwitchTo(oldcontext);
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 0f11922..13b5815 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -87,25 +87,9 @@ static bool ExecCheckRTEPermsModified(Oid relOid, Oid userid,
Bitmapset *modifiedCols,
AclMode requiredPerms);
static void ExecCheckXactReadOnly(PlannedStmt *plannedstmt);
-static char *ExecBuildSlotValueDescription(Oid reloid,
- TupleTableSlot *slot,
- TupleDesc tupdesc,
- Bitmapset *modifiedCols,
- int maxfieldlen);
static void EvalPlanQualStart(EPQState *epqstate, EState *parentestate,
Plan *planTree);
-/*
- * Note that GetUpdatedColumns() also exists in commands/trigger.c. There does
- * not appear to be any good header to put it into, given the structures that
- * it uses, so we let them be duplicated. Be sure to update both if one needs
- * to be changed, however.
- */
-#define GetInsertedColumns(relinfo, estate) \
- (rt_fetch((relinfo)->ri_RangeTableIndex, (estate)->es_range_table)->insertedCols)
-#define GetUpdatedColumns(relinfo, estate) \
- (rt_fetch((relinfo)->ri_RangeTableIndex, (estate)->es_range_table)->updatedCols)
-
/* end of local decls */
@@ -1177,6 +1161,7 @@ CheckValidRowMarkRel(Relation rel, RowMarkType markType)
switch (rel->rd_rel->relkind)
{
case RELKIND_RELATION:
+ case RELKIND_PARTITIONED_REL:
/* OK */
break;
case RELKIND_SEQUENCE:
@@ -1273,6 +1258,13 @@ InitResultRelInfo(ResultRelInfo *resultRelInfo,
resultRelInfo->ri_ConstraintExprs = NULL;
resultRelInfo->ri_junkFilter = NULL;
resultRelInfo->ri_projectReturning = NULL;
+ if (resultRelationDesc->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+ {
+ resultRelInfo->ri_PartitionKeyInfo = BuildPartitionKeyExecInfo(resultRelationDesc);
+ resultRelInfo->ri_PartitionDesc = RelationGetPartitionDesc(resultRelationDesc,
+ linitial(resultRelationDesc->rd_partkeys),
+ true);
+ }
}
/*
@@ -1914,7 +1906,7 @@ ExecWithCheckOptions(WCOKind kind, ResultRelInfo *resultRelInfo,
* column involved, that subset will be returned with a key identifying which
* columns they are.
*/
-static char *
+char *
ExecBuildSlotValueDescription(Oid reloid,
TupleTableSlot *slot,
TupleDesc tupdesc,
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 27051e8..492a9d0 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -39,11 +39,13 @@
#include "access/htup_details.h"
#include "access/xact.h"
+#include "catalog/partition.h"
#include "commands/trigger.h"
#include "executor/executor.h"
#include "executor/nodeModifyTable.h"
#include "foreign/fdwapi.h"
#include "miscadmin.h"
+#include "parser/parsetree.h"
#include "nodes/nodeFuncs.h"
#include "storage/bufmgr.h"
#include "storage/lmgr.h"
@@ -226,6 +228,8 @@ ExecInsert(ModifyTableState *mtstate,
{
HeapTuple tuple;
ResultRelInfo *resultRelInfo;
+ ResultRelInfo *saved_resultRelInfo = NULL; /* for partitioned table case */
+ ExprContext *econtext;
Relation resultRelationDesc;
Oid newId;
List *recheckIndexes = NIL;
@@ -340,6 +344,62 @@ ExecInsert(ModifyTableState *mtstate,
if (resultRelationDesc->rd_att->constr)
ExecConstraints(resultRelInfo, slot, estate);
+ /*
+ * Switch resultRelInfo to one corresponding to the partition
+ * that this tuple maps to.
+ */
+ if (resultRelInfo->ri_PartitionDesc)
+ {
+ Oid partOid;
+
+ econtext = GetPerTupleExprContext(estate);
+ econtext->ecxt_scantuple = slot;
+ partOid = get_partition_for_tuple(resultRelationDesc,
+ resultRelInfo->ri_PartitionKeyInfo,
+ resultRelInfo->ri_PartitionDesc,
+ slot,
+ estate);
+ if (partOid != InvalidOid)
+ {
+ Relation partRelDesc;
+
+ partRelDesc = heap_open(partOid, RowExclusiveLock);
+
+ saved_resultRelInfo = resultRelInfo;
+ resultRelInfo = makeNode(ResultRelInfo);
+ InitResultRelInfo(resultRelInfo,
+ partRelDesc,
+ 1, /* dummy */
+ 0);
+
+ ExecOpenIndices(resultRelInfo, false);
+ estate->es_result_relation_info = resultRelInfo;
+ resultRelationDesc = resultRelInfo->ri_RelationDesc;
+ }
+ else
+ {
+ char *val_desc;
+ Bitmapset *modifiedCols;
+ Bitmapset *insertedCols;
+ Bitmapset *updatedCols;
+
+ insertedCols = GetInsertedColumns(resultRelInfo, estate);
+ updatedCols = GetUpdatedColumns(resultRelInfo, estate);
+ modifiedCols = bms_union(insertedCols, updatedCols);
+ val_desc = ExecBuildSlotValueDescription(RelationGetRelid(resultRelationDesc),
+ slot,
+ RelationGetDescr(resultRelationDesc),
+ modifiedCols,
+ 64);
+
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("cannot find partition of relation \"%s\" for the new row",
+ RelationGetRelationName(resultRelationDesc)),
+ val_desc ? errdetail("Failing row contains %s.", val_desc) : 0));
+ }
+ }
+
if (onconflict != ONCONFLICT_NONE && resultRelInfo->ri_NumIndices > 0)
{
/* Perform a speculative insertion. */
@@ -477,6 +537,17 @@ ExecInsert(ModifyTableState *mtstate,
/* AFTER ROW INSERT Triggers */
ExecARInsertTriggers(estate, resultRelInfo, tuple, recheckIndexes);
+ /* Restore the resultRelInfo, if had been switched. */
+ if (saved_resultRelInfo)
+ {
+ heap_close(resultRelInfo->ri_RelationDesc, RowExclusiveLock);
+ ExecCloseIndices(resultRelInfo);
+ pfree(resultRelInfo);
+ resultRelInfo = saved_resultRelInfo;
+ estate->es_result_relation_info = resultRelInfo;
+ saved_resultRelInfo = NULL; /* aka, paranoia! */
+ }
+
list_free(recheckIndexes);
/*
@@ -502,6 +573,29 @@ ExecInsert(ModifyTableState *mtstate,
return NULL;
}
+/*
+ * find_partition_in_es_rt
+ * A temporary band-aid solution to get partition's RT index in executor
+ */
+static int
+find_partition_in_es_rt(EState *estate, Oid oid)
+{
+ ListCell *lc;
+ int index = 1;
+
+ foreach(lc, estate->es_range_table)
+ {
+ RangeTblEntry *rte = (RangeTblEntry *) lfirst(lc);
+
+ if (rte->relid == oid)
+ return index;
+
+ index++;
+ }
+
+ return 0;
+}
+
/* ----------------------------------------------------------------
* ExecDelete
*
@@ -522,6 +616,7 @@ ExecInsert(ModifyTableState *mtstate,
*/
static TupleTableSlot *
ExecDelete(ItemPointer tupleid,
+ Oid partitionOid,
HeapTuple oldtuple,
TupleTableSlot *planSlot,
EPQState *epqstate,
@@ -533,12 +628,14 @@ ExecDelete(ItemPointer tupleid,
HTSU_Result result;
HeapUpdateFailureData hufd;
TupleTableSlot *slot = NULL;
+ Index resultRelationRTindex;
/*
* get information on the (current) result relation
*/
resultRelInfo = estate->es_result_relation_info;
resultRelationDesc = resultRelInfo->ri_RelationDesc;
+ resultRelationRTindex = resultRelInfo->ri_RangeTableIndex;
/* BEFORE ROW DELETE Triggers */
if (resultRelInfo->ri_TrigDesc &&
@@ -599,6 +696,13 @@ ExecDelete(ItemPointer tupleid,
}
else
{
+ if (partitionOid != InvalidOid)
+ {
+ resultRelationRTindex = find_partition_in_es_rt(estate,
+ partitionOid);
+ resultRelationDesc = heap_open(partitionOid, RowExclusiveLock);
+ }
+
/*
* delete the tuple
*
@@ -649,7 +753,7 @@ ldelete:;
errhint("Consider using an AFTER trigger instead of a BEFORE trigger to propagate changes to other rows.")));
/* Else, already deleted by self; nothing to do */
- return NULL;
+ goto cleanup;
case HeapTupleMayBeUpdated:
break;
@@ -666,7 +770,7 @@ ldelete:;
epqslot = EvalPlanQual(estate,
epqstate,
resultRelationDesc,
- resultRelInfo->ri_RangeTableIndex,
+ resultRelationRTindex,
LockTupleExclusive,
&hufd.ctid,
hufd.xmax);
@@ -677,11 +781,11 @@ ldelete:;
}
}
/* tuple already deleted; nothing to do */
- return NULL;
+ goto cleanup;
default:
elog(ERROR, "unrecognized heap_delete status: %u", result);
- return NULL;
+ goto cleanup;
}
/*
@@ -751,10 +855,15 @@ ldelete:;
if (BufferIsValid(delbuffer))
ReleaseBuffer(delbuffer);
- return rslot;
+ slot = rslot;
}
- return NULL;
+cleanup:
+ /* close any partition relation, if we opened */
+ if (resultRelationDesc != resultRelInfo->ri_RelationDesc)
+ heap_close(resultRelationDesc, RowExclusiveLock);
+
+ return slot;
}
/* ----------------------------------------------------------------
@@ -781,6 +890,8 @@ ldelete:;
*/
static TupleTableSlot *
ExecUpdate(ItemPointer tupleid,
+ Oid srcPartitionOid,
+ bool is_partkey_update,
HeapTuple oldtuple,
TupleTableSlot *slot,
TupleTableSlot *planSlot,
@@ -790,6 +901,8 @@ ExecUpdate(ItemPointer tupleid,
{
HeapTuple tuple;
ResultRelInfo *resultRelInfo;
+ ResultRelInfo *saved_resultRelInfo = NULL; /* for partitioned table case */
+ ExprContext *econtext;
Relation resultRelationDesc;
HTSU_Result result;
HeapUpdateFailureData hufd;
@@ -896,6 +1009,72 @@ lreplace:;
ExecConstraints(resultRelInfo, slot, estate);
/*
+ * Check whether the new row changes partition
+ */
+ if (resultRelInfo->ri_PartitionDesc)
+ {
+ Oid partOid;
+
+ if (is_partkey_update)
+ {
+ econtext = GetPerTupleExprContext(estate);
+ econtext->ecxt_scantuple = slot;
+ partOid = get_partition_for_tuple(resultRelationDesc,
+ resultRelInfo->ri_PartitionKeyInfo,
+ resultRelInfo->ri_PartitionDesc,
+ slot,
+ estate);
+
+ if (partOid != srcPartitionOid)
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("cannot perform update that causes a row to change partition")));
+ }
+ else
+ partOid = srcPartitionOid;
+
+ if (OidIsValid(partOid))
+ {
+ Relation partRelDesc;
+
+ partRelDesc = heap_open(partOid, RowExclusiveLock);
+
+ saved_resultRelInfo = resultRelInfo;
+ resultRelInfo = makeNode(ResultRelInfo);
+ InitResultRelInfo(resultRelInfo,
+ partRelDesc,
+ find_partition_in_es_rt(estate, partOid),
+ 0);
+
+ ExecOpenIndices(resultRelInfo, false);
+ estate->es_result_relation_info = resultRelInfo;
+ resultRelationDesc = resultRelInfo->ri_RelationDesc;
+ }
+ else
+ {
+ char *val_desc;
+ Bitmapset *modifiedCols;
+ Bitmapset *insertedCols;
+ Bitmapset *updatedCols;
+
+ insertedCols = GetInsertedColumns(resultRelInfo, estate);
+ updatedCols = GetUpdatedColumns(resultRelInfo, estate);
+ modifiedCols = bms_union(insertedCols, updatedCols);
+ val_desc = ExecBuildSlotValueDescription(RelationGetRelid(resultRelationDesc),
+ slot,
+ RelationGetDescr(resultRelationDesc),
+ modifiedCols,
+ 64);
+
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("cannot find partition of relation \"%s\" for the new row",
+ RelationGetRelationName(resultRelationDesc)),
+ val_desc ? errdetail("Failing row contains %s.", val_desc) : 0));
+ }
+ }
+
+ /*
* replace the heap tuple
*
* Note: if es_crosscheck_snapshot isn't InvalidSnapshot, we check
@@ -943,7 +1122,7 @@ lreplace:;
errhint("Consider using an AFTER trigger instead of a BEFORE trigger to propagate changes to other rows.")));
/* Else, already updated by self; nothing to do */
- return NULL;
+ goto cleanup;
case HeapTupleMayBeUpdated:
break;
@@ -973,11 +1152,11 @@ lreplace:;
}
}
/* tuple already deleted; nothing to do */
- return NULL;
+ goto cleanup;
default:
elog(ERROR, "unrecognized heap_update status: %u", result);
- return NULL;
+ goto cleanup;
}
/*
@@ -1008,6 +1187,17 @@ lreplace:;
ExecARUpdateTriggers(estate, resultRelInfo, tupleid, oldtuple, tuple,
recheckIndexes);
+ /* Restore the resultRelInfo, if had been switched. */
+ if (saved_resultRelInfo)
+ {
+ heap_close(resultRelationDesc, RowExclusiveLock);
+ ExecCloseIndices(resultRelInfo);
+ pfree(resultRelInfo);
+ resultRelInfo = saved_resultRelInfo;
+ estate->es_result_relation_info = resultRelInfo;
+ saved_resultRelInfo = NULL;
+ }
+
list_free(recheckIndexes);
/*
@@ -1027,6 +1217,15 @@ lreplace:;
return ExecProcessReturning(resultRelInfo->ri_projectReturning,
slot, planSlot);
+cleanup:
+ if (saved_resultRelInfo)
+ {
+ heap_close(resultRelationDesc, RowExclusiveLock);
+ ExecCloseIndices(resultRelInfo);
+ pfree(resultRelInfo);
+ estate->es_result_relation_info = saved_resultRelInfo;
+ }
+
return NULL;
}
@@ -1214,8 +1413,8 @@ ExecOnConflictUpdate(ModifyTableState *mtstate,
*/
/* Execute UPDATE with projection */
- *returning = ExecUpdate(&tuple.t_self, NULL,
- mtstate->mt_conflproj, planSlot,
+ *returning = ExecUpdate(&tuple.t_self, InvalidOid, false,
+ NULL, mtstate->mt_conflproj, planSlot,
&mtstate->mt_epqstate, mtstate->ps.state,
canSetTag);
@@ -1299,6 +1498,7 @@ ExecModifyTable(ModifyTableState *node)
ItemPointerData tuple_ctid;
HeapTupleData oldtupdata;
HeapTuple oldtuple;
+ Oid partitionOid = InvalidOid;
/*
* This should NOT get called during EvalPlanQual; we should have passed a
@@ -1396,7 +1596,9 @@ ExecModifyTable(ModifyTableState *node)
bool isNull;
relkind = resultRelInfo->ri_RelationDesc->rd_rel->relkind;
- if (relkind == RELKIND_RELATION || relkind == RELKIND_MATVIEW)
+ if (relkind == RELKIND_RELATION ||
+ relkind == RELKIND_PARTITIONED_REL ||
+ relkind == RELKIND_MATVIEW)
{
datum = ExecGetJunkAttribute(slot,
junkfilter->jf_junkAttNo,
@@ -1447,6 +1649,22 @@ ExecModifyTable(ModifyTableState *node)
}
else
Assert(relkind == RELKIND_FOREIGN_TABLE);
+
+ /*
+ * If this is a partition key update, get the source
+ * partition OID.
+ */
+ if (relkind == RELKIND_PARTITIONED_REL)
+ {
+ datum = ExecGetJunkAttribute(slot,
+ junkfilter->jf_junkExtraAttNo,
+ &isNull);
+ /* shouldn't ever get a null result... */
+ if (isNull)
+ elog(ERROR, "tableoid is NULL");
+
+ partitionOid = DatumGetObjectId(datum);
+ }
}
/*
@@ -1464,11 +1682,12 @@ ExecModifyTable(ModifyTableState *node)
estate, node->canSetTag);
break;
case CMD_UPDATE:
- slot = ExecUpdate(tupleid, oldtuple, slot, planSlot,
- &node->mt_epqstate, estate, node->canSetTag);
+ slot = ExecUpdate(tupleid, partitionOid, node->mt_partkeyupd,
+ oldtuple, slot, planSlot, &node->mt_epqstate,
+ estate, node->canSetTag);
break;
case CMD_DELETE:
- slot = ExecDelete(tupleid, oldtuple, planSlot,
+ slot = ExecDelete(tupleid, partitionOid, oldtuple, planSlot,
&node->mt_epqstate, estate, node->canSetTag);
break;
default:
@@ -1500,6 +1719,35 @@ ExecModifyTable(ModifyTableState *node)
return NULL;
}
+/*
+ * IsPartitionKeyUpdate
+ * Checks if updated columns include any of the partitin key columns
+ *
+ * If there is a partition expression, simply return 'false'. We could maybe
+ * check if any updated column is involved in the expression hence causing
+ * partition key to be changed in a given row, but later.
+ */
+static bool
+IsPartitionKeyUpdate(ResultRelInfo *resultRelInfo, EState *estate)
+{
+ Relation rel = resultRelInfo->ri_RelationDesc;
+ ListCell *cell;
+ Bitmapset *updatedCols = GetUpdatedColumns(resultRelInfo, estate);
+
+ foreach (cell, rel->rd_partkeys)
+ {
+ PartitionKey key = lfirst(cell);
+ int i;
+
+ for (i = 0; i < key->partnatts; i++)
+ if (key->partattrs[i] == 0 ||
+ bms_is_member((int ) key->partattrs[i], updatedCols))
+ return true;
+ }
+
+ return false;
+}
+
/* ----------------------------------------------------------------
* ExecInitModifyTable
* ----------------------------------------------------------------
@@ -1516,6 +1764,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
Plan *subplan;
ListCell *l;
int i;
+ char relkind;
/* check for unsupported flags */
Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));
@@ -1828,10 +2077,9 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
if (operation == CMD_UPDATE || operation == CMD_DELETE)
{
/* For UPDATE/DELETE, find the appropriate junk attr now */
- char relkind;
-
relkind = resultRelInfo->ri_RelationDesc->rd_rel->relkind;
if (relkind == RELKIND_RELATION ||
+ relkind == RELKIND_PARTITIONED_REL ||
relkind == RELKIND_MATVIEW)
{
j->jf_junkAttNo = ExecFindJunkAttribute(j, "ctid");
@@ -1852,6 +2100,17 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
if (!AttributeNumberIsValid(j->jf_junkAttNo))
elog(ERROR, "could not find junk wholerow column");
}
+
+ /*
+ * For partitioned table, find the extra junk attribute,
+ * ie, "tableoid".
+ */
+ if (relkind == RELKIND_PARTITIONED_REL)
+ {
+ j->jf_junkExtraAttNo = ExecFindJunkAttribute(j, "tableoid");
+ if (!AttributeNumberIsValid(j->jf_junkExtraAttNo))
+ elog(ERROR, "could not find junk tableoid column");
+ }
}
resultRelInfo->ri_junkFilter = j;
@@ -1866,6 +2125,12 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
}
}
+ relkind = mtstate->resultRelInfo->ri_RelationDesc->rd_rel->relkind;
+ if (relkind == RELKIND_PARTITIONED_REL &&
+ operation == CMD_UPDATE &&
+ IsPartitionKeyUpdate(mtstate->resultRelInfo, estate))
+ mtstate->mt_partkeyupd = true;
+
/*
* Set up a tuple table slot for use for trigger output tuples. In a plan
* containing multiple ModifyTable nodes, all can share one such slot, so
diff --git a/src/backend/parser/parse_clause.c b/src/backend/parser/parse_clause.c
index 04fa127..499da4c 100644
--- a/src/backend/parser/parse_clause.c
+++ b/src/backend/parser/parse_clause.c
@@ -2881,6 +2881,15 @@ transformOnConflictArbiter(ParseState *pstate,
parser_errposition(pstate,
exprLocation((Node *) onConflictClause))));
+ /* ON CONFLICT on partitioned tables currently disallowed */
+ if (pstate->p_target_relation->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("ON CONFLICT is not supported with partitioned tables"),
+ parser_errposition(pstate,
+ exprLocation((Node *) onConflictClause))));
+
+
/* ON CONFLICT DO NOTHING does not require an inference clause */
if (infer)
{
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index a22a11e..fa28b4d 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -1222,6 +1222,7 @@ rewriteTargetListUD(Query *parsetree, RangeTblEntry *target_rte,
TargetEntry *tle;
if (target_relation->rd_rel->relkind == RELKIND_RELATION ||
+ target_relation->rd_rel->relkind == RELKIND_PARTITIONED_REL ||
target_relation->rd_rel->relkind == RELKIND_MATVIEW)
{
/*
@@ -1293,6 +1294,30 @@ rewriteTargetListUD(Query *parsetree, RangeTblEntry *target_rte,
parsetree->targetList = lappend(parsetree->targetList, tle);
}
+
+ /*
+ * Also emit tableoid so that ExecModifyTable knows which partition
+ * a given row came from. ExecUpdate will use it to throw error if
+ * the new row does not map to the same partition.
+ */
+ if (target_relation->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+ {
+ var = makeVar(parsetree->resultRelation,
+ TableOidAttributeNumber,
+ OIDOID,
+ -1,
+ InvalidOid,
+ 0);
+
+ attrname = "tableoid";
+
+ tle = makeTargetEntry((Expr *) var,
+ list_length(parsetree->targetList) + 1,
+ pstrdup(attrname),
+ true);
+
+ parsetree->targetList = lappend(parsetree->targetList, tle);
+ }
}
diff --git a/src/include/catalog/partition.h b/src/include/catalog/partition.h
index c54fc1b..81d8232 100644
--- a/src/include/catalog/partition.h
+++ b/src/include/catalog/partition.h
@@ -14,6 +14,8 @@
#define PARTITION_H
#include "fmgr.h"
+#include "executor/tuptable.h"
+#include "nodes/execnodes.h"
#include "utils/relcache.h"
/* Type information for partition key columns */
@@ -106,4 +108,11 @@ extern bool list_partition_overlaps(PartitionDesc pdesc,
extern bool range_partition_overlaps(PartitionDesc pdesc,
PartitionKey key,
Datum *rangemaxs);
+
+extern List *BuildPartitionKeyExecInfo(Relation rel);
+extern Oid get_partition_for_tuple(Relation rel,
+ List *pkinfo,
+ PartitionDesc pdesc,
+ TupleTableSlot *slot,
+ EState *estate);
#endif /* PARTITION_H */
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index 1a44085..a769126 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -219,6 +219,21 @@ extern void EvalPlanQualBegin(EPQState *epqstate, EState *parentestate);
extern void EvalPlanQualEnd(EPQState *epqstate);
/*
+ * Following used to be in execMain.c, brought here to be useful for copy.c,
+ * trigger.c, among others probably.
+ */
+#define GetInsertedColumns(relinfo, estate) \
+ (rt_fetch((relinfo)->ri_RangeTableIndex, (estate)->es_range_table)->insertedCols)
+#define GetUpdatedColumns(relinfo, estate) \
+ (rt_fetch((relinfo)->ri_RangeTableIndex, (estate)->es_range_table)->updatedCols)
+
+extern char *ExecBuildSlotValueDescription(Oid reloid,
+ TupleTableSlot *slot,
+ TupleDesc tupdesc,
+ Bitmapset *modifiedCols,
+ int maxfieldlen);
+
+/*
* prototypes from functions in execProcnode.c
*/
extern PlanState *ExecInitNode(Plan *node, EState *estate, int eflags);
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 064a050..dec6200 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -76,6 +76,27 @@ typedef struct IndexInfo
bool ii_BrokenHotChain;
} IndexInfo;
+/*
+ * PartitionKeyExecInfo
+ *
+ * This struct holds the information needed to extract partition
+ * column values from a heap tuple.
+ *
+ * NumKeyAttrs number of columns in partition key
+ * KeyAttrNumbers underlying-rel attribute numbers used as keys
+ * (zeroes indicate expressions)
+ * Expressions expr trees for expression entries, or NIL if none
+ * ExpressionsState exec state for expressions, or NIL if none
+ */
+typedef struct PartitionKeyExecInfo
+{
+ NodeTag type;
+ int pi_NumKeyAttrs;
+ AttrNumber pi_KeyAttrNumbers[PARTITION_MAX_KEYS];
+ List *pi_Expressions; /* list of Expr */
+ List *pi_ExpressionsState; /* list of ExprState */
+} PartitionKeyExecInfo;
+
/* ----------------
* ExprContext_CB
*
@@ -290,6 +311,7 @@ typedef struct JunkFilter
AttrNumber *jf_cleanMap;
TupleTableSlot *jf_resultSlot;
AttrNumber jf_junkAttNo;
+ AttrNumber jf_junkExtraAttNo;
} JunkFilter;
/* ----------------
@@ -341,6 +363,8 @@ typedef struct ResultRelInfo
ProjectionInfo *ri_projectReturning;
ProjectionInfo *ri_onConflictSetProj;
List *ri_onConflictSetWhere;
+ List *ri_PartitionKeyInfo;
+ struct PartitionDescData *ri_PartitionDesc;
} ResultRelInfo;
/* ----------------
@@ -1134,6 +1158,7 @@ typedef struct ModifyTableState
* tlist */
TupleTableSlot *mt_conflproj; /* CONFLICT ... SET ... projection
* target */
+ bool mt_partkeyupd; /* Does UPDATE change partition key cols */
} ModifyTableState;
/* ----------------
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 8032f0d..666bc1e 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -37,6 +37,7 @@ typedef enum NodeTag
T_ResultRelInfo,
T_EState,
T_TupleTableSlot,
+ T_PartitionKeyExecInfo,
/*
* TAGS FOR PLAN NODES (plannodes.h)
--
1.7.1
[text/x-diff] 0010-Embarrasingly-small-optimizer-patch-to-work-with-par.patch (13.7K, ../../56C1302D.6070403@lab.ntt.co.jp/11-0010-Embarrasingly-small-optimizer-patch-to-work-with-par.patch)
download | inline diff:
From db212397d755dbcdc7d5394b42a1d7b3cbdcb3aa Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Fri, 12 Feb 2016 19:25:58 +0900
Subject: [PATCH 10/10] Embarrasingly small optimizer patch to work with partitioned tables.
Basically, this creates a expand_inherited_rtentry clone that works
for partitioned tables instead of inheritance sets. AppendRelInfo's
are created for leaf partitions and added to PlannerInfo.append_rel_list.
Then, wherever RangeTblEntry.inh check is used to switch to append rel
processing, a check for relid_is_partitioned(relid) is added.
Note that it is not the intention of this commit to make constraint
exclusion also work for partitioned tables. A subsequent commit will
teach partition module to install an equivalent CHECK constraint
whenever a new partition is created that will enable constraint
exclusion based partition pruning.
One ugliness: EXPLAIN's set_deparse_planstate() uses appendplans[0] or
mergeplans[0] to initialize deparse_namespace.outer_planstate. That
gives expected behavior for Append plans meant for inheritance sets
because the parent is 0th member. Same is not true in case of Append
meant for partitioned tables (which this patch creates), so a user
will see first leaf partition's name where they would expect to see
name of the table they used in the query (IOW, the parent table).
---
src/backend/optimizer/path/allpaths.c | 7 +-
src/backend/optimizer/plan/planner.c | 3 +
src/backend/optimizer/prep/prepunion.c | 231 ++++++++++++++++++++++++++++++++
src/backend/optimizer/util/plancat.c | 14 ++
src/backend/optimizer/util/relnode.c | 4 +-
src/include/optimizer/prep.h | 2 +
6 files changed, 258 insertions(+), 3 deletions(-)
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index 380b15e..81c2323 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -19,6 +19,7 @@
#include "access/sysattr.h"
#include "access/tsmapi.h"
+#include "catalog/partition.h"
#include "catalog/pg_class.h"
#include "catalog/pg_operator.h"
#include "catalog/pg_proc.h"
@@ -324,7 +325,8 @@ set_rel_size(PlannerInfo *root, RelOptInfo *rel,
*/
set_dummy_rel_pathlist(rel);
}
- else if (rte->inh)
+ else if (rte->inh || (rte->rtekind == RTE_RELATION &&
+ relid_is_partitioned(rte->relid)))
{
/* It's an "append relation", process accordingly */
set_append_rel_size(root, rel, rti, rte);
@@ -401,7 +403,8 @@ set_rel_pathlist(PlannerInfo *root, RelOptInfo *rel,
{
/* We already proved the relation empty, so nothing more to do */
}
- else if (rte->inh)
+ else if (rte->inh || (rte->rtekind == RTE_RELATION &&
+ relid_is_partitioned(rte->relid)))
{
/* It's an "append relation", process accordingly */
set_append_rel_pathlist(root, rel, rti, rte);
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index f77c804..581924b 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -538,6 +538,9 @@ subquery_planner(PlannerGlobal *glob, Query *parse,
*/
expand_inherited_tables(root);
+ /* ... and partitioned tables. */
+ expand_partitioned_tables(root);
+
/*
* Set hasHavingQual to remember if HAVING clause is present. Needed
* because preprocess_expression will reduce a constant-true condition to
diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c
index e509a1a..54fae4b 100644
--- a/src/backend/optimizer/prep/prepunion.c
+++ b/src/backend/optimizer/prep/prepunion.c
@@ -33,6 +33,7 @@
#include "access/heapam.h"
#include "access/htup_details.h"
#include "access/sysattr.h"
+#include "catalog/partition.h"
#include "catalog/pg_inherits_fn.h"
#include "catalog/pg_type.h"
#include "miscadmin.h"
@@ -112,6 +113,11 @@ static Node *adjust_appendrel_attrs_mutator(Node *node,
static Relids adjust_relid_set(Relids relids, Index oldrelid, Index newrelid);
static List *adjust_inherited_tlist(List *tlist,
AppendRelInfo *context);
+static void expand_partitioned_rtentry(PlannerInfo *root, RangeTblEntry *rte,
+ Index rti);
+static void make_var_translation_list(Relation oldrelation,
+ Index newvarno,
+ List **translated_vars);
/*
@@ -1217,6 +1223,29 @@ expand_inherited_tables(PlannerInfo *root)
}
/*
+ * expand_partitioned_tables
+ * Expand each rangetable entry that is partitioned table into an
+ * "append relation".
+ */
+void
+expand_partitioned_tables(PlannerInfo *root)
+{
+ Index nrtes;
+ Index rti;
+ ListCell *rl;
+
+ nrtes = list_length(root->parse->rtable);
+ rl = list_head(root->parse->rtable);
+ for (rti = 1; rti <= nrtes; rti++)
+ {
+ RangeTblEntry *rte = (RangeTblEntry *) lfirst(rl);
+
+ expand_partitioned_rtentry(root, rte, rti);
+ rl = lnext(rl);
+ }
+}
+
+/*
* expand_inherited_rtentry
* Check whether a rangetable entry represents an inheritance set.
* If so, add entries for all the child tables to the query's
@@ -1433,6 +1462,161 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti)
}
/*
+ * expand_partitioned_rtentry
+ * Check whether a rangetable entry is a partitioned table.
+ * If so, add entries for all the leaf partitions to the query's
+ * rangetable, and build AppendRelInfo nodes for all the leaf partitions
+ * and add them to root->append_rel_list.
+ *
+ * A childless table is never considered to be an inheritance set; therefore
+ * a parent RTE must always have at least two associated AppendRelInfos.
+ */
+static void
+expand_partitioned_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti)
+{
+ Query *parse = root->parse;
+ Oid parentOID;
+ PlanRowMark *oldrc;
+ Relation oldrelation;
+ LOCKMODE lockmode;
+ List *partOIDs;
+ List *appinfos;
+ ListCell *l;
+
+ /* Quick exit */
+ if (rte->rtekind != RTE_RELATION || !relid_is_partitioned(rte->relid))
+ return;
+
+ parentOID = rte->relid;
+
+ /*
+ * Locking policy same as that described in expand_inherited_rtentry().
+ */
+ oldrc = get_plan_rowmark(root->rowMarks, rti);
+ if (rti == parse->resultRelation)
+ lockmode = RowExclusiveLock;
+ else if (oldrc && RowMarkRequiresRowShareLock(oldrc->markType))
+ lockmode = RowShareLock;
+ else
+ lockmode = AccessShareLock;
+
+ /* Scan for all members of inheritance set, acquire needed locks */
+ partOIDs = get_leaf_partitions(parentOID, lockmode);
+
+ /*
+ * If parent relation is selected FOR UPDATE/SHARE, we need to mark its
+ * PlanRowMark as isParent = true, and generate a new PlanRowMark for each
+ * partition.
+ */
+ if (oldrc)
+ oldrc->isParent = true;
+
+ /*
+ * Must open the parent relation to examine its tupdesc. We need not lock
+ * it; we assume the rewriter already did.
+ */
+ oldrelation = heap_open(parentOID, NoLock);
+
+ /* Scan the inheritance set and expand it */
+ appinfos = NIL;
+ foreach(l, partOIDs)
+ {
+ Oid partOID = lfirst_oid(l);
+ Relation newrelation;
+ RangeTblEntry *partrte;
+ Index partRTindex;
+ AppendRelInfo *appinfo;
+
+ newrelation = heap_open(partOID, NoLock);
+
+ /*
+ * It is possible that the parent table has partitions that are temp
+ * tables of other backends (yeah it's possible - imagine if other
+ * backend creates a leaf partition of a temp partitioned table).
+ * We cannot safely access such partitions (because of buffering issues),
+ * and the best thing to do seems to be to silently ignore them.
+ */
+ if (RELATION_IS_OTHER_TEMP(newrelation))
+ {
+ heap_close(newrelation, lockmode);
+ continue;
+ }
+
+ /*
+ * Build an RTE for the partition, and attach to query's rangetable list.
+ * We copy most fields of the parent's RTE, but replace relation OID
+ * and relkind, Also, set requiredPerms to zero since all required
+ * permissions checks are done on the original RTE.
+ */
+ partrte = copyObject(rte);
+ partrte->relid = partOID;
+ partrte->relkind = newrelation->rd_rel->relkind;
+ partrte->inh = false;
+ partrte->requiredPerms = 0;
+ parse->rtable = lappend(parse->rtable, partrte);
+ partRTindex = list_length(parse->rtable);
+
+ /*
+ * Build an AppendRelInfo for this parent and child.
+ */
+ appinfo = makeNode(AppendRelInfo);
+ appinfo->parent_relid = rti;
+ appinfo->child_relid = partRTindex;
+ appinfo->parent_reltype = oldrelation->rd_rel->reltype;
+ appinfo->child_reltype = newrelation->rd_rel->reltype;
+ make_var_translation_list(oldrelation, partRTindex, &appinfo->translated_vars);
+ appinfo->parent_reloid = parentOID;
+ appinfos = lappend(appinfos, appinfo);
+
+ /*
+ * Translate the column permissions bitmaps to the partition's attnums (we
+ * have to build the translated_vars list before we can do this).
+ *
+ * Note: we need to do this even though the executor won't run any
+ * permissions checks on the child RTE. The insertedCols/updatedCols
+ * bitmaps may be examined for trigger-firing purposes.
+ */
+ partrte->selectedCols = translate_col_privs(rte->selectedCols,
+ appinfo->translated_vars);
+ partrte->insertedCols = translate_col_privs(rte->insertedCols,
+ appinfo->translated_vars);
+ partrte->updatedCols = translate_col_privs(rte->updatedCols,
+ appinfo->translated_vars);
+
+ /*
+ * Build a PlanRowMark if parent is marked FOR UPDATE/SHARE.
+ */
+ if (oldrc)
+ {
+ PlanRowMark *newrc = makeNode(PlanRowMark);
+
+ newrc->rti = partRTindex;
+ newrc->prti = rti;
+ newrc->rowmarkId = oldrc->rowmarkId;
+ /* Reselect rowmark type, because relkind might not match parent */
+ newrc->markType = select_rowmark_type(partrte, oldrc->strength);
+ newrc->allMarkTypes = (1 << newrc->markType);
+ newrc->strength = oldrc->strength;
+ newrc->waitPolicy = oldrc->waitPolicy;
+ newrc->isParent = false;
+
+ /* Include partition's rowmark type in parent's allMarkTypes */
+ oldrc->allMarkTypes |= newrc->allMarkTypes;
+
+ root->rowMarks = lappend(root->rowMarks, newrc);
+ }
+
+ /* Close partition relations, but keep locks */
+ heap_close(newrelation, NoLock);
+ }
+
+ heap_close(oldrelation, NoLock);
+
+ /* Otherwise, OK to add to root->append_rel_list */
+ root->append_rel_list = list_concat(root->append_rel_list, appinfos);
+}
+
+/*
* make_inh_translation_list
* Build the list of translations from parent Vars to child Vars for
* an inheritance child.
@@ -1536,6 +1720,53 @@ make_inh_translation_list(Relation oldrelation, Relation newrelation,
}
/*
+ * make_var_translation_list
+ * Build the list of translations from parent Vars to partition Vars for
+ * a partition which is basically changing varnos to refer to the partition's
+ * RT entry.
+ */
+static void
+make_var_translation_list(Relation oldrelation,
+ Index newvarno,
+ List **translated_vars)
+{
+ List *vars = NIL;
+ TupleDesc old_tupdesc = RelationGetDescr(oldrelation);
+ int oldnatts = old_tupdesc->natts;
+ int old_attno;
+
+ for (old_attno = 0; old_attno < oldnatts; old_attno++)
+ {
+ Form_pg_attribute att;
+ char *attname;
+ Oid atttypid;
+ int32 atttypmod;
+ Oid attcollation;
+
+ att = old_tupdesc->attrs[old_attno];
+ if (att->attisdropped)
+ {
+ /* Just put NULL into this list entry */
+ vars = lappend(vars, NULL);
+ continue;
+ }
+ attname = NameStr(att->attname);
+ atttypid = att->atttypid;
+ atttypmod = att->atttypmod;
+ attcollation = att->attcollation;
+
+ vars = lappend(vars, makeVar(newvarno,
+ (AttrNumber) (old_attno + 1),
+ atttypid,
+ atttypmod,
+ attcollation,
+ 0));
+ }
+
+ *translated_vars = vars;
+}
+
+/*
* translate_col_privs
* Translate a bitmapset representing per-column privileges from the
* parent rel's attribute numbering to the child's.
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index 0ea9fcf..4453aae 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -805,6 +805,14 @@ estimate_rel_size(Relation rel, int32 *attr_widths,
switch (rel->rd_rel->relkind)
{
case RELKIND_RELATION:
+ if (relid_is_internal_partition(RelationGetRelid(rel)))
+ {
+ /* No storage */
+ *pages = 0;
+ *tuples = 0;
+ *allvisfrac = 0;
+ break;
+ }
case RELKIND_INDEX:
case RELKIND_MATVIEW:
case RELKIND_TOASTVALUE:
@@ -929,6 +937,12 @@ estimate_rel_size(Relation rel, int32 *attr_widths,
*tuples = rel->rd_rel->reltuples;
*allvisfrac = 0;
break;
+ case RELKIND_PARTITIONED_REL:
+ /* No storage */
+ *pages = 0;
+ *tuples = 0;
+ *allvisfrac = 0;
+ break;
default:
/* else it has no disk storage; probably shouldn't get here? */
*pages = 0;
diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c
index 420692f..394021f 100644
--- a/src/backend/optimizer/util/relnode.c
+++ b/src/backend/optimizer/util/relnode.c
@@ -15,6 +15,7 @@
#include "postgres.h"
#include "miscadmin.h"
+#include "catalog/partition.h"
#include "catalog/pg_class.h"
#include "foreign/foreign.h"
#include "optimizer/clauses.h"
@@ -199,7 +200,8 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptKind reloptkind)
* not in the main join tree, but we will need RelOptInfos to plan access
* to them.
*/
- if (rte->inh)
+ if (rte->inh || (rte->rtekind == RTE_RELATION &&
+ relid_is_partitioned(rte->relid)))
{
ListCell *l;
diff --git a/src/include/optimizer/prep.h b/src/include/optimizer/prep.h
index cebd8b6..611ed49 100644
--- a/src/include/optimizer/prep.h
+++ b/src/include/optimizer/prep.h
@@ -64,4 +64,6 @@ extern Node *adjust_appendrel_attrs(PlannerInfo *root, Node *node,
extern Node *adjust_appendrel_attrs_multilevel(PlannerInfo *root, Node *node,
RelOptInfo *child_rel);
+extern void expand_partitioned_tables(PlannerInfo *root);
+
#endif /* PREP_H */
--
1.7.1
^ permalink raw reply [nested|flat] 161+ messages in thread
* Re: Declarative partitioning
@ 2016-02-15 20:15 Corey Huinker <corey.huinker@gmail.com>
parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
1 sibling, 1 reply; 161+ messages in thread
From: Corey Huinker @ 2016-02-15 20:15 UTC (permalink / raw)
To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; +Cc: pgsql-hackers
>
>
> The individual patches have commit messages that describe code changes.
> This is registered in the upcoming CF. Feedback and review is greatly
> welcomed!
>
> Thanks,
> Amit
>
>
We have a current system that is currently a mix of tables, each of which
is range partitioned into approximately 15 partitions (using the pgxn range
partitioning extension), and those partitions are themselves date-series
partitioned via pg_partman. The largest table ingests about 100M rows per
day in a single ETL. I will try this patch out and see how well it compares
in handling the workload. Do you have any areas of interest or concern that
I should monitor?
^ permalink raw reply [nested|flat] 161+ messages in thread
* Re: Declarative partitioning
@ 2016-02-16 00:28 Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
parent: Corey Huinker <corey.huinker@gmail.com>
0 siblings, 2 replies; 161+ messages in thread
From: Amit Langote @ 2016-02-16 00:28 UTC (permalink / raw)
To: Corey Huinker <corey.huinker@gmail.com>; +Cc: pgsql-hackers
Hi Corey,
On 2016/02/16 5:15, Corey Huinker wrote:
>>
>> The individual patches have commit messages that describe code changes.
>> This is registered in the upcoming CF. Feedback and review is greatly
>> welcomed!
>>
> We have a current system that is currently a mix of tables, each of which
> is range partitioned into approximately 15 partitions (using the pgxn range
> partitioning extension), and those partitions are themselves date-series
> partitioned via pg_partman. The largest table ingests about 100M rows per
> day in a single ETL. I will try this patch out and see how well it compares
> in handling the workload. Do you have any areas of interest or concern that
> I should monitor?
Thanks a lot for willing to give it a spin!
I would say this patch series is more geared toward usability. For
example, you won't have to write a trigger to route tuples to correct
partitions. You can try your mentioned ETL load and see if the patch's
implementation of tuple routing fares any better than existing
trigger-based approach. Maybe, you were expecting something like load into
a stand-alone table and then ALTER TABLE INHERIT to instantly load into
the partitioned table (roll-in), but that command is not yet implemented
(ATTACH PARTITION command will show a "not implemented" error message).
Also, you won't see any optimizer and executor changes. Queries will still
use the same plans as existing inheritance-based partitioned tables,
although as I mentioned, constraint exclusion won't yet kick in. That will
be fixed very shortly.
And of course, comments on syntax are welcome as well.
Thanks,
Amit
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 161+ messages in thread
* Re: Declarative partitioning
@ 2016-02-16 02:07 Corey Huinker <corey.huinker@gmail.com>
parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
1 sibling, 0 replies; 161+ messages in thread
From: Corey Huinker @ 2016-02-16 02:07 UTC (permalink / raw)
To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; +Cc: pgsql-hackers
>
> Also, you won't see any optimizer and executor changes. Queries will still
> use the same plans as existing inheritance-based partitioned tables,
> although as I mentioned, constraint exclusion won't yet kick in. That will
> be fixed very shortly.
>
> And of course, comments on syntax are welcome as well.
>
> Thanks,
> Amit
>
>
>
Good to know the current limitations/expectations.
Our ETL has a great number of workers that do something like this:
1. grab a file
2. based on some metadata of that file, determine the partition that that
would receive ALL of the rows in that file. It's actually multiple tables,
all of which are partitioned, all of which fully expect the file data to
fit in exactly one partition.
3. \copy into a temp table
4. Transform the data and insert the relevant bits into each of the target
partitions derived in #2.
So while ATR is a *major* feature of true partitioning, it's not something
we'd actually need in our current production environment, but I can
certainly code it that way to benchmark ATR vs "know the destination
partition ahead of time" vs "insane layered range_partitioning trigger +
pg_partman trigger".
Currently we don't do anything like table swapping, but I've done that
enough in the past that I could probably concoct a test of that too, once
it's implemented.
As for the syntax, I'm not quite sure your patch addresses the concerned I
voiced earlier: specifically if the VALUES IN works for RANGE as well as
LIST, but I figured that would become clearer once I tried to actually use
it. Currently we have partitioning on C-collated text ranges (no, they
don't ship with postgres, I had to make a custom type) something like this:
part0: (,BIG_CLIENT)
part1: [BIG_CLIENT,BIG_CLIENT]
part2: (BIG_CLIENT,L)
part3: [L,MONSTROUSLY_BIG_CLIENT)
part4: [MONSTROUSLY_BIG_CLIENT,MONSTROUSLY_BIG_CLIENT]
part5: (MONSTROUSLY_BIG_CLIENT,RANDOM_CLIENT_LATE_IN_ALPHABET]
part6: (RANDOM_CLIENT_LATE_IN_ALPHABET,)
I can't implement that with a simple VALUES LESS THAN clause, unless I
happen to know 'x' in 'BIG_CLIENTx', where 'x' is the exact first character
in the collation sequence, which has to be something unprintable, and that
would make those who later read my code to say something unprintable. So
yeah, I'm hoping there's some way to cleanly represent such ranges.
^ permalink raw reply [nested|flat] 161+ messages in thread
* Re: Declarative partitioning
@ 2016-02-16 02:41 Josh berkus <josh@agliodbs.com>
parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
1 sibling, 1 reply; 161+ messages in thread
From: Josh berkus @ 2016-02-16 02:41 UTC (permalink / raw)
To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; Corey Huinker <corey.huinker@gmail.com>; +Cc: pgsql-hackers
On 02/15/2016 04:28 PM, Amit Langote wrote:
> Also, you won't see any optimizer and executor changes. Queries will still
> use the same plans as existing inheritance-based partitioned tables,
> although as I mentioned, constraint exclusion won't yet kick in. That will
> be fixed very shortly.
We're not going to use CE for the new partitioning long-term, are we?
This is just the first version, right?
--
--
Josh Berkus
Red Hat OSAS
(any opinions are my own)
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 161+ messages in thread
* Re: Declarative partitioning
@ 2016-02-16 02:46 Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
parent: Josh berkus <josh@agliodbs.com>
0 siblings, 1 reply; 161+ messages in thread
From: Amit Langote @ 2016-02-16 02:46 UTC (permalink / raw)
To: Josh berkus <josh@agliodbs.com>; Corey Huinker <corey.huinker@gmail.com>; +Cc: pgsql-hackers
Hi Josh,
On 2016/02/16 11:41, Josh berkus wrote:
> On 02/15/2016 04:28 PM, Amit Langote wrote:
>> Also, you won't see any optimizer and executor changes. Queries will still
>> use the same plans as existing inheritance-based partitioned tables,
>> although as I mentioned, constraint exclusion won't yet kick in. That will
>> be fixed very shortly.
>
> We're not going to use CE for the new partitioning long-term, are we? This
> is just the first version, right?
Yes. My approach in previous versions of stuffing major planner changes in
with the syntax patch was not quite proper in retrospect. So, I thought
I'd propose any major planner (and executor) changes later.
Thanks,
Amit
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 161+ messages in thread
* Re: Declarative partitioning
@ 2016-02-16 12:57 Robert Haas <robertmhaas@gmail.com>
parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
1 sibling, 1 reply; 161+ messages in thread
From: Robert Haas @ 2016-02-16 12:57 UTC (permalink / raw)
To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; +Cc: pgsql-hackers
On Fri, Jan 15, 2016 at 5:48 AM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:
> If we have a CREATE statement for each partition, how do we generalize
> that to partitions at different levels? For example, if we use something
> like the following to create a partition of parent_name:
>
> CREATE PARTITION partition_name OF parent_name FOR VALUES ...
> WITH ... TABLESPACE ...
>
> Do we then say:
>
> CREATE PARTITION subpartition_name OF partition_name ...
>
> to create a level 2 partition (sub-partition) of parent_name?
Yes, exactly.
Personally, I would be more inclined to make this a CREATE TABLE statement, like
CREATE TABLE partition_name PARTITION OF parent_name FOR VALUES ...
CREATE TABLE subpartition_name PARTITION OF partition_name FOR VALUES ...
> I ask that also because it's related to the choice of syntax to use to
> declare the partition key for the multi-level case. I'm considering the
> SUBPARTITION BY notation and perhaps we could generalize it to more than
> just 2 levels. So, for the above case, parent_name would have been created as:
>
> CREATE TABLE parent_name PARTITION BY ... SUBPARTITION BY ...
>
> Needless to say, when subpartition_name is created with the command we saw
> a moment ago, the root partitioned table would be locked. In fact, adding
> a partition anywhere in the hierarchy needs an exclusive lock on the root
> table. Also, partition rule (the FOR VALUES clause) would be validated
> against PARTITION BY or SUBPARTITION BY clause at the respective level.
>
> Although, I must admit I feel a little uneasy about the inherent asymmetry
> in using SUBPARTITION BY for key declaration whereas piggybacking CREATE
> PARTITION for creating sub-partitions. Is there a better way?
I think if you've got SUBPARTITION as a keyword in the syntax
anywhere, you're doing it wrong. The toplevel object shouldn't really
care whether its children are themselves partitioned or not.
> Do we want this at all? It seems difficult to generalize this to
> multi-level hierarchy of more than 2 levels.
It doesn't do anything for me. There may be somebody who wants it,
but I don't see much value myself.
> After thinking some more on this - I think that identical tuple
> descriptors may not just be a nice-to-have but critical in some cases. For
> example, consider built-in/trigger-less tuple routing. I'd imagine that
> the partition to insert a tuple into would be determined just before
> calling heap_insert() in ExecInsert() and CopyFrom(). That means the
> HeapTuple that is passed to heap_insert() to insert into the partition
> would be based on the root table's tuple descriptor. Note also that the
> tuple would have passed through BR, IR triggers, constraints of the root
> table. When the data is eventually queried from partitions directly, or
> well even via the root table (considering existing executor capabilities),
> partition's tuple descriptor at that point had better match the data that
> went onto the disk. That means we had better keep at least the following
> things in sync: number of attributes, name, position (attnum), type,
> notnull-ness of individual attributes. So in order to do that, recursively
> apply ADD/DROP COLUMN, SET WITH/WITHOUT OIDS, RENAME COLUMN, ALTER COLUMN
> TYPE, SET/DROP NOT NULL on the root table to all the partitions and
> prevent those sub-commands to be directly applied to any table
> (partitions) in the partitioning hierarchy but the root. I further don't
> see the point of allowing to set (or drop) column defaults in partitions
> because now INSERT or COPY FROM cannot be directly applied to partitions.
> Similar argument could be made for BR, IR triggers and CHECK constraints.
> Am I missing something in all of this?
Well, in the end there are basically two choices. Either tuple
descriptors have to match exactly, and then you can reuse a tuple
intended for one partition for some other partition without
projection; or else they don't, and you need to project. I'm not sure
that projection is expensive enough to make disallowing mismatched
tuple descriptors a necessary design choice - and certainly that
design choice is awkward from a UI standpoint, because we will
sometimes not be able to attach a partition for a reason that the user
can neither see in the \d output nor correct. But on the flip side,
not having to worry about projection is nice.
> An alternative to doing any of that very well may be to design
> trigger-less tuple routing to be smarter about possible mismatch of the
> tuple descriptors but I haven't given that a lot of thought. Is that
> really an alternative worth looking into?
Yes.
> On one hand, I think to keep treating "partition hierarchies" as
> "inheritance hierachies" might have some issues. I am afraid that
> documented inheritance semantics may not be what we want to keep using for
> the new partitioned tables. By that, I mean all the user-facing behaviors
> where inheritance has some bearing. Should it also affect new partitioned
> tables? Consider whether inheritance semantics would render infeasible
> some of the things that we'd like to introduce for the new partitioned
> tables such as automatic tuple routing, or keep us from improving planner
> smarts and executor capabilities for partitioned tables over what we
> already have.
>
> OTOH, I may be looking at it wrongly. We would not be required to enforce
> user-facing inheritance behaviors on the new partitioned tables after all.
> That is to say - it's just that new partitioned tables could still use
> relevant inheritance infrastructure behind-the-scenes for planning,
> execution and a few other things and not care about abiding by regular
> inheritance semantics. I should just go ahead and add special cases in all
> places where existing inheritance handling code stands to cause trouble
> down the line for partitioned tables. We might want to mention that we do
> so somewhere in documentation and also note that regular inheritance
> semantics does not apply. While it sounds probably fine as implementation
> for the feature released initially, a day will inevitably come when this
> behind-the-scenes implementation will be changed to something more
> amenable to better optimization. But that's for future...
This seems pretty vague to me. I don't know that I have an opinion
without a more specific list of questions.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 161+ messages in thread
* Re: Declarative partitioning
@ 2016-02-16 13:02 Robert Haas <robertmhaas@gmail.com>
parent: Tomas Vondra <tomas.vondra@2ndquadrant.com>
1 sibling, 0 replies; 161+ messages in thread
From: Robert Haas @ 2016-02-16 13:02 UTC (permalink / raw)
To: Tomas Vondra <tomas.vondra@2ndquadrant.com>; +Cc: pgsql-hackers
On Fri, Jan 22, 2016 at 8:54 AM, Tomas Vondra
<tomas.vondra@2ndquadrant.com> wrote:
> I'd like to comment on the one thing and that's the syntax. It seems to me
> we're really trying to reinvent the wheel and come up with our own version
> of the syntax. Is there a particular reason why not to look at the syntax of
> the other databases and adapt as much of the existing syntax as possible?
I'm not sure this is at all standardized. How do we decide whose
syntax to adopt? Oracle's just because it's Oracle? I don't
particularly like inheriting the limitations of Oracle's syntax - such
as at most 2 levels of partitioning, weird VALUES LESS THAN syntax
that doesn't look like anything else we do in PG and has no way to
specify an opclass, etc.
> I think that's for a few reasons - firstly it makes the life much easier for
> the DBAs and users who are either migrating to PostgreSQL or have to manage
> a mix of databases. Secondly, it serves as a valuable source of engineering
> info, preventing the "I haven't thought of this use case" problem.
>
> An example of this is the proposed syntax for adding a partition
>
> CREATE TABLE measurement_fail
> PARTITION OF measurement
> FOR VALUES START ('2006-02-15') END ('2006-03-01');
>
> which seems a bit awkward as both the databases I'm familiar with (Oracle
> and Sybase) use ALTER TABLE to do this
>
> ALTER TABLE measurement
> ADD PARTITION measurement_fail VALUES LESS THAN ( ... )
>
> And so on for the other commands.
One thing to think about is that pg_dump --binary-upgrade needs to
decorate each command that creates a relation. I guess you could
decorate ALTER TABLE with the same stuff we currently apply to CREATE
TABLE but...
> That being said, I entirely agree with Simon (and others) that getting the
> planner part work is the crucial part of the patch. But I also think that a
> proper abstraction (thanks to good syntax) may be a valuable hint how to
> define the catalogs and such.
No argument on that from me.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 161+ messages in thread
* Re: Declarative partitioning
@ 2016-02-17 02:56 Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
1 sibling, 1 reply; 161+ messages in thread
From: Amit Langote @ 2016-02-17 02:56 UTC (permalink / raw)
To: pgsql-hackers
On 2016/02/15 10:55, Amit Langote wrote:
> required. There is also basic planner support but no support yet to enable
> constraint exclusion on partitions (which will be fulfilled shortly by
> installing equivalent check constraints on partitions).
Just to follow up on this - attached now adds equivalent check constraint
with partition creation. Constraint exclusion should work. No regression
tests yet though.
From now on, instead of attaching multiple files like in the previous
message, I will send a single tar.gz which will contain patches created by
git-format-patch.
Thanks,
Amit
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Attachments:
[application/gzip] decl-part-20160217-1.tar.gz (81.8K, ../../56C3E15B.7030907@lab.ntt.co.jp/2-decl-part-20160217-1.tar.gz)
download
^ permalink raw reply [nested|flat] 161+ messages in thread
* Re: Declarative partitioning
@ 2016-02-18 05:41 Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
parent: Robert Haas <robertmhaas@gmail.com>
0 siblings, 2 replies; 161+ messages in thread
From: Amit Langote @ 2016-02-18 05:41 UTC (permalink / raw)
To: Robert Haas <robertmhaas@gmail.com>; +Cc: pgsql-hackers
On 2016/02/16 21:57, Robert Haas wrote:
> On Fri, Jan 15, 2016 at 5:48 AM, Amit Langote
> <Langote_Amit_f8@lab.ntt.co.jp> wrote:
>> If we have a CREATE statement for each partition, how do we generalize
>> that to partitions at different levels? For example, if we use something
>> like the following to create a partition of parent_name:
>>
>> CREATE PARTITION partition_name OF parent_name FOR VALUES ...
>> WITH ... TABLESPACE ...
>>
>> Do we then say:
>>
>> CREATE PARTITION subpartition_name OF partition_name ...
>>
>> to create a level 2 partition (sub-partition) of parent_name?
>
> Yes, exactly.
>
> Personally, I would be more inclined to make this a CREATE TABLE statement, like
>
> CREATE TABLE partition_name PARTITION OF parent_name FOR VALUES ...
> CREATE TABLE subpartition_name PARTITION OF partition_name FOR VALUES ...
I guess the first of which would actually be:
CREATE TABLE partition_name PARTITION OF parent_name FOR VALUES ...
PARTITION BY ...
Some might think that writing potentially the same PARTITION BY clause 100
times for 100 level-1 partitions could be cumbersome. That is what
SUBPARTITION BY notation may be good as a shorthand for.
> I think if you've got SUBPARTITION as a keyword in the syntax
> anywhere, you're doing it wrong. The toplevel object shouldn't really
> care whether its children are themselves partitioned or not.
This is fine for the internals. SUBPARTITION BY is mere syntax sugar. It
might as well be just cascaded PARTITION BYs. The point is to specify as
many of those with CREATE TABLE toplevel as the number of levels of
partitioning we want. That does however fix the number of levels in advance.
In the patch I have posted, here are some details of the tuple routing
implementation for instance - the top-level parent knows only of its
immediate partitions. Once a level-1 partition from that list is
determined using a tuple's level-1 key, the tuple is passed down to choose
one of its own partitions using the level-2 key. The key descriptor list
is associated with the top-level parent alone and the recursive code knows
to iterate the key list to apply nth key for nth level. The recursion
happens in the partition module.
Now there are also functions to let obtain, say *all* or only *leaf*
partitions (OIDs) of a top-level parent but they are used for certain DDL
scenarios. As far as DML is concerned, the level-at-a-time recursive
approach as described above is used. Queries not yet because the plan is a
flattened append of leaf partitions anyway.
If such notation convenience at the expense of loss of generality is not
worth it, I'm willing to go ahead and implement SUBPARTITION-less syntax.
CREATE TABLE toplevel() PARTITION BY
CREATE TABLE partition PARTITION OF toplevel FOR VALUES ... PARTITION BY
CREATE TABLE subpartition PARTITION OF partition FOR VALUES
ALTER TABLE partitioned ATTACH PARTITION name FOR VALUES ... USING TABLE
ALTER TABLE partitioned DETACH PARTITION name [ USING newname ]
While we are on the syntax story, how about FOR VALUES syntax for range
partitions (sorry for piggybacking it here in this message). At least some
people dislike LESS THAN notation. Corey Huinker says we should be using
range type literals for that. It's not clear though that using range type
literals directly is a way ahead. For one, range type API expects there to
exist a range type with given element type. Whereas all we require for
range partitioning proper is for column type to have a btree operator
class. Should we require it to have an associated range type as well?
Don't think that there exists an API to check that either. All in all,
range types are good to implement things in applications but not so much
within the backend (unless I'm missing something). I know reinventing the
wheel is disliked as well but perhaps we could offer something like the
following because Corey offered some examples which would help from the
flexibility:
START [ EXCL ] (startval) END [ INCL ] (endval)
That is, in range type notation, '[startval, endval)' is the default
behavior. So for each partition, there is at least the following pieces of
metadata:
Datum *startval;
bool startexcl;
Datum *endval;
bool endincl;
That requires quite some gymnastics during DDL (with no help from range
type infrastructure) to maintain the invariant that no two range
partitions overlap. Even gaps can result which are considered undesirable,
so maybe, the invariant to maintain would include no gaps in addition to
no overlap. That would have us looking for a definition of a "gap" for all
sorts of btree supporting data types.
Some people said something akin to interval partitioning would be good like:
PARTITION BY RANGE ON (columns) INCREMENT BY (INTERVAL '1 month' ) START
WITH value;
But that could be just a UI addition to the design where each partition
has [startval, endval) bounds. In any case, not a version 1 material I'd
think.
>> Do we want this at all? It seems difficult to generalize this to
>> multi-level hierarchy of more than 2 levels.
>
> It doesn't do anything for me. There may be somebody who wants it,
> but I don't see much value myself.
OK.
>> After thinking some more on this - I think that identical tuple
>> descriptors may not just be a nice-to-have but critical in some cases. For
>> example, consider built-in/trigger-less tuple routing. I'd imagine that
>> the partition to insert a tuple into would be determined just before
>> calling heap_insert() in ExecInsert() and CopyFrom(). That means the
>> HeapTuple that is passed to heap_insert() to insert into the partition
>> would be based on the root table's tuple descriptor. Note also that the
>> tuple would have passed through BR, IR triggers, constraints of the root
>> table. When the data is eventually queried from partitions directly, or
>> well even via the root table (considering existing executor capabilities),
>> partition's tuple descriptor at that point had better match the data that
>> went onto the disk. That means we had better keep at least the following
>> things in sync: number of attributes, name, position (attnum), type,
>> notnull-ness of individual attributes. So in order to do that, recursively
>> apply ADD/DROP COLUMN, SET WITH/WITHOUT OIDS, RENAME COLUMN, ALTER COLUMN
>> TYPE, SET/DROP NOT NULL on the root table to all the partitions and
>> prevent those sub-commands to be directly applied to any table
>> (partitions) in the partitioning hierarchy but the root. I further don't
>> see the point of allowing to set (or drop) column defaults in partitions
>> because now INSERT or COPY FROM cannot be directly applied to partitions.
>> Similar argument could be made for BR, IR triggers and CHECK constraints.
>> Am I missing something in all of this?
>
> Well, in the end there are basically two choices. Either tuple
> descriptors have to match exactly, and then you can reuse a tuple
> intended for one partition for some other partition without
> projection; or else they don't, and you need to project. I'm not sure
> that projection is expensive enough to make disallowing mismatched
> tuple descriptors a necessary design choice - and certainly that
> design choice is awkward from a UI standpoint, because we will
> sometimes not be able to attach a partition for a reason that the user
> can neither see in the \d output nor correct. But on the flip side,
> not having to worry about projection is nice.
>
>> An alternative to doing any of that very well may be to design
>> trigger-less tuple routing to be smarter about possible mismatch of the
>> tuple descriptors but I haven't given that a lot of thought. Is that
>> really an alternative worth looking into?
>
> Yes.
I see the trade-off. I agree that considering the significance for attach
partition case is quite important.
So, the tuple routing code should be ready to use projection if there
happens to be a partition with differing tuple descriptor. In the code I
posted, a ResultRelInfo is lazily built afresh for each inserted tuple in
ExecInsert's case and for each tuple where the chosen partition is
different from the previous tuple's in CopyFrom's case. One can feel that
there is a certain overhead to that approach for the bulk-loading case
(almost every CopyFrom invocation). Now if projection enters this picture,
we have to consider that too. Should we initialize ResultRelInfo's and
corresponding ProjectionInfo's for all partitions beforehand? Consider how
ModifyTable currently handles update on inheritance set, for example. That
would incur unnecessary overhead if only a single tuple is inserted. But
it would certainly help bulk-loading case. Am I missing something?
>> On one hand, I think to keep treating "partition hierarchies" as
>> "inheritance hierachies" might have some issues. I am afraid that
>> documented inheritance semantics may not be what we want to keep using for
>> the new partitioned tables. By that, I mean all the user-facing behaviors
>> where inheritance has some bearing. Should it also affect new partitioned
>> tables? Consider whether inheritance semantics would render infeasible
>> some of the things that we'd like to introduce for the new partitioned
>> tables such as automatic tuple routing, or keep us from improving planner
>> smarts and executor capabilities for partitioned tables over what we
>> already have.
>>
>> OTOH, I may be looking at it wrongly. We would not be required to enforce
>> user-facing inheritance behaviors on the new partitioned tables after all.
>> That is to say - it's just that new partitioned tables could still use
>> relevant inheritance infrastructure behind-the-scenes for planning,
>> execution and a few other things and not care about abiding by regular
>> inheritance semantics. I should just go ahead and add special cases in all
>> places where existing inheritance handling code stands to cause trouble
>> down the line for partitioned tables. We might want to mention that we do
>> so somewhere in documentation and also note that regular inheritance
>> semantics does not apply. While it sounds probably fine as implementation
>> for the feature released initially, a day will inevitably come when this
>> behind-the-scenes implementation will be changed to something more
>> amenable to better optimization. But that's for future...
>
> This seems pretty vague to me. I don't know that I have an opinion
> without a more specific list of questions.
Sorry for rather incoherent articulation in the previous message. Let me
try to say something that's perhaps more specific.
Consider that we create partitions as inheritance children, that is,
creating a partition using:
CREATE TABLE partition PARTITION OF parent FOR VALUES ...
is equivalent to saying
CREATE TABLE partition (...) INHERITS (parent)
except that the latter allows partition to have its own column
definitions, table constraints and multiple parents as long as things are
conflict-free. The former requires specifying partition bounding values.
The common thing between the two then is StoreCatalogInheritance(partOid,
parentOid) that will mark partition as inheritance child of parent in
pg_inherits. So, our new partitions are really inheritance children but
that's not apparent to users (where this last bit is important).
Then consider ALTER TABLE partition - should we need to handle it in way
different from existing inheritance code would do -
* Prevent something whereas regular inheritance wouldn't?
* Do something instead of/in addition to whatever regular inheritance does?
Consider adding a column to partition - regular inheritance wouldn't
prevent it because adding a column to child table doesn't get in the way
of how inheritance works. Now we can make it so that if table is a
partition created with CRATE TABLE PARTITION OF (which we can tell because
the table has pg_partition entry), we should *actively* prevent adding a
column. Inheritance already prevents dropping an inherited attribute in
child but says "cannot drop inherited column". IMO in a partition's case,
it should say "cannot drop columns of a partition".
OTOH, adding a column to parent propagates to all partitions by way of
ATSimpleRecursion(). But not everything that ATSimpleRecursion() does is
necessary for partitioned tables (which we can tell from relkind or maybe
because table has pg_partitioned_rel entry) - especially no need to
propagate column default and check constraint, etc. Because one cannot
apply insert/update on partitions anyway. Then because defaults and check
constraints on partitions are really useless, when a user adds them to a
partition directly, we should say, "cannot alter column default for a
partition" and "cannot add check constraint to a partition" with "instead
do that operation on the root parent" hint. But what is a "root parent"?
How to determine one for a given partition? Perhaps, it's not much of a
POLA violation a user must have done something to have ended up in the
error occurring, that is, created the table as partition of (partition of
...) something.
Thanks a lot for your time!
Thanks,
Amit
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 161+ messages in thread
* Re: Declarative partitioning
@ 2016-02-19 20:06 Corey Huinker <corey.huinker@gmail.com>
parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
1 sibling, 1 reply; 161+ messages in thread
From: Corey Huinker @ 2016-02-19 20:06 UTC (permalink / raw)
To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; +Cc: Robert Haas <robertmhaas@gmail.com>; pgsql-hackers
On Thu, Feb 18, 2016 at 12:41 AM, Amit Langote <
Langote_Amit_f8@lab.ntt.co.jp> wrote:
> START [ EXCL ] (startval) END [ INCL ] (endval)
>
> That is, in range type notation, '[startval, endval)' is the default
> behavior. So for each partition, there is at least the following pieces of
> metadata:
>
This is really close, and if it is what we ended up with we would be able
to use it.
I suggest that the range notation can be used even when no suitable range
type exists.
I assume the code for parsing a range spec regardless of data type already
exists, but in case it doesn't, take a range spec of unknown type:
[x,y)
x and y are either going to be raw strings or doublequoted strings with
possible doublequote escapes, each of which would be coercible into the the
type of the partition column.
In other words, if your string values were 'blah , blah ' and 'fabizzle',
the [) range spec would be ["blah , blah ",fabizzle).
Using regular range specs syntax also allows for the range to be unbounded
in either or both directions, which is a possibility, especially in newer
tables where the expected distribution of data is unknown.
p.s. Sorry I haven't been able to kick the tires just yet. We have a very
good use case for this, it's just a matter of getting a machine and the
time to devote to it.
^ permalink raw reply [nested|flat] 161+ messages in thread
* Re: Declarative partitioning
@ 2016-02-20 04:41 Peter Eisentraut <peter_e@gmx.net>
parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
0 siblings, 1 reply; 161+ messages in thread
From: Peter Eisentraut @ 2016-02-20 04:41 UTC (permalink / raw)
To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; pgsql-hackers
On 2/16/16 9:56 PM, Amit Langote wrote:
> From now on, instead of attaching multiple files like in the previous
> message, I will send a single tar.gz which will contain patches created by
> git-format-patch.
Please don't do that.
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 161+ messages in thread
* Re: Declarative partitioning
@ 2016-02-20 05:08 Amit Langote <amitlangote09@gmail.com>
parent: Peter Eisentraut <peter_e@gmx.net>
0 siblings, 0 replies; 161+ messages in thread
From: Amit Langote @ 2016-02-20 05:08 UTC (permalink / raw)
To: Peter Eisentraut <peter_e@gmx.net>; +Cc: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; pgsql-hackers
On Sat, Feb 20, 2016 at 1:41 PM, Peter Eisentraut <peter_e@gmx.net> wrote:
> On 2/16/16 9:56 PM, Amit Langote wrote:
>> From now on, instead of attaching multiple files like in the previous
>> message, I will send a single tar.gz which will contain patches created by
>> git-format-patch.
>
> Please don't do that.
OK, will remember. Sorry about that.
Thanks,
Amit
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 161+ messages in thread
* Re: Declarative partitioning
@ 2016-02-23 13:51 Robert Haas <robertmhaas@gmail.com>
parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
1 sibling, 1 reply; 161+ messages in thread
From: Robert Haas @ 2016-02-23 13:51 UTC (permalink / raw)
To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; +Cc: pgsql-hackers
On Thu, Feb 18, 2016 at 11:11 AM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:
>> Personally, I would be more inclined to make this a CREATE TABLE statement, like
>>
>> CREATE TABLE partition_name PARTITION OF parent_name FOR VALUES ...
>> CREATE TABLE subpartition_name PARTITION OF partition_name FOR VALUES ...
>
> I guess the first of which would actually be:
>
> CREATE TABLE partition_name PARTITION OF parent_name FOR VALUES ...
> PARTITION BY ...
>
> Some might think that writing potentially the same PARTITION BY clause 100
> times for 100 level-1 partitions could be cumbersome. That is what
> SUBPARTITION BY notation may be good as a shorthand for.
I think that anybody who is doing that much partitioning is going to
use a tool to generate the DDL anyway, so it doesn't really matter.
>> I think if you've got SUBPARTITION as a keyword in the syntax
>> anywhere, you're doing it wrong. The toplevel object shouldn't really
>> care whether its children are themselves partitioned or not.
>
> This is fine for the internals. SUBPARTITION BY is mere syntax sugar. It
> might as well be just cascaded PARTITION BYs. The point is to specify as
> many of those with CREATE TABLE toplevel as the number of levels of
> partitioning we want. That does however fix the number of levels in advance.
Which I think is not good. If we say that a table can be partitioned,
and a table that is a partition can also be partitioned, we've got a
nice general system. Fixing the number of partitioning levels for the
sake of a little syntactic sugar is, IMHO, getting our priorities
backwards.
> In the patch I have posted, here are some details of the tuple routing
> implementation for instance - the top-level parent knows only of its
> immediate partitions. Once a level-1 partition from that list is
> determined using a tuple's level-1 key, the tuple is passed down to choose
> one of its own partitions using the level-2 key. The key descriptor list
> is associated with the top-level parent alone and the recursive code knows
> to iterate the key list to apply nth key for nth level. The recursion
> happens in the partition module.
I haven't looked at the code, but that sounds like the right approach.
> Now there are also functions to let obtain, say *all* or only *leaf*
> partitions (OIDs) of a top-level parent but they are used for certain DDL
> scenarios. As far as DML is concerned, the level-at-a-time recursive
> approach as described above is used. Queries not yet because the plan is a
> flattened append of leaf partitions anyway.
OK.
> If such notation convenience at the expense of loss of generality is not
> worth it, I'm willing to go ahead and implement SUBPARTITION-less syntax.
>
> CREATE TABLE toplevel() PARTITION BY
> CREATE TABLE partition PARTITION OF toplevel FOR VALUES ... PARTITION BY
> CREATE TABLE subpartition PARTITION OF partition FOR VALUES
> ALTER TABLE partitioned ATTACH PARTITION name FOR VALUES ... USING TABLE
> ALTER TABLE partitioned DETACH PARTITION name [ USING newname ]
>
> While we are on the syntax story, how about FOR VALUES syntax for range
> partitions (sorry for piggybacking it here in this message). At least some
> people dislike LESS THAN notation. Corey Huinker says we should be using
> range type literals for that. It's not clear though that using range type
> literals directly is a way ahead. For one, range type API expects there to
> exist a range type with given element type. Whereas all we require for
> range partitioning proper is for column type to have a btree operator
> class. Should we require it to have an associated range type as well?
> Don't think that there exists an API to check that either. All in all,
> range types are good to implement things in applications but not so much
> within the backend (unless I'm missing something). I know reinventing the
> wheel is disliked as well but perhaps we could offer something like the
> following because Corey offered some examples which would help from the
> flexibility:
>
> START [ EXCL ] (startval) END [ INCL ] (endval)
I don't think using range type literals is gonna work. There's no
guarantee that the necessary range types exist. However, we could
possibly use a syntax inspired by the syntax for range types. I'm a
little nervous about asking people to type commands with mismatching
braces:
CREATE TABLE partition_name PARTITION OF parent_name FOR VALUES [ 1, 10 );
...but maybe it's a good idea. It certainly has the advantage of
being more compact than a lot of there ways we might choose to write
that. And I think LESS THAN sucks. It's just clunky and awkward
syntax.
> Some people said something akin to interval partitioning would be good like:
>
> PARTITION BY RANGE ON (columns) INCREMENT BY (INTERVAL '1 month' ) START
> WITH value;
>
> But that could be just a UI addition to the design where each partition
> has [startval, endval) bounds. In any case, not a version 1 material I'd
> think.
Agreed; not right now.
> I see the trade-off. I agree that considering the significance for attach
> partition case is quite important.
>
> So, the tuple routing code should be ready to use projection if there
> happens to be a partition with differing tuple descriptor. In the code I
> posted, a ResultRelInfo is lazily built afresh for each inserted tuple in
> ExecInsert's case and for each tuple where the chosen partition is
> different from the previous tuple's in CopyFrom's case. One can feel that
> there is a certain overhead to that approach for the bulk-loading case
> (almost every CopyFrom invocation). Now if projection enters this picture,
> we have to consider that too. Should we initialize ResultRelInfo's and
> corresponding ProjectionInfo's for all partitions beforehand? Consider how
> ModifyTable currently handles update on inheritance set, for example. That
> would incur unnecessary overhead if only a single tuple is inserted. But
> it would certainly help bulk-loading case. Am I missing something?
I'd say you are missing benchmarking data. :-)
Why should the single-tuple case be harmed here?
> Sorry for rather incoherent articulation in the previous message. Let me
> try to say something that's perhaps more specific.
>
> Consider that we create partitions as inheritance children, that is,
> creating a partition using:
>
> CREATE TABLE partition PARTITION OF parent FOR VALUES ...
>
> is equivalent to saying
>
> CREATE TABLE partition (...) INHERITS (parent)
>
> except that the latter allows partition to have its own column
> definitions, table constraints and multiple parents as long as things are
> conflict-free. The former requires specifying partition bounding values.
> The common thing between the two then is StoreCatalogInheritance(partOid,
> parentOid) that will mark partition as inheritance child of parent in
> pg_inherits. So, our new partitions are really inheritance children but
> that's not apparent to users (where this last bit is important).
So far, this sounds good to me.
> Then consider ALTER TABLE partition - should we need to handle it in way
> different from existing inheritance code would do -
>
> * Prevent something whereas regular inheritance wouldn't?
> * Do something instead of/in addition to whatever regular inheritance does?
>
> Consider adding a column to partition - regular inheritance wouldn't
> prevent it because adding a column to child table doesn't get in the way
> of how inheritance works. Now we can make it so that if table is a
> partition created with CRATE TABLE PARTITION OF (which we can tell because
> the table has pg_partition entry), we should *actively* prevent adding a
> column. Inheritance already prevents dropping an inherited attribute in
> child but says "cannot drop inherited column". IMO in a partition's case,
> it should say "cannot drop columns of a partition".
Agreed. So maybe pg_inherits gets an additional column that indicates
whether this is "regular" inheritance or "partitioning" inheritance,
and in the latter case there are additional restrictions.
> OTOH, adding a column to parent propagates to all partitions by way of
> ATSimpleRecursion(). But not everything that ATSimpleRecursion() does is
> necessary for partitioned tables (which we can tell from relkind or maybe
> because table has pg_partitioned_rel entry) - especially no need to
> propagate column default and check constraint, etc. Because one cannot
> apply insert/update on partitions anyway.
Hmm. I feel like maybe that should be allowed. If the user wants to
bulk-load a bunch of data and knows that it goes in a particular
partition, why should they have to pay the overhead of determining
that anew for each row?
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 161+ messages in thread
* Re: Declarative partitioning
@ 2016-02-24 08:11 Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
parent: Corey Huinker <corey.huinker@gmail.com>
0 siblings, 1 reply; 161+ messages in thread
From: Amit Langote @ 2016-02-24 08:11 UTC (permalink / raw)
To: Corey Huinker <corey.huinker@gmail.com>; +Cc: Robert Haas <robertmhaas@gmail.com>; pgsql-hackers
On 2016/02/20 5:06, Corey Huinker wrote:
> On Thu, Feb 18, 2016 at 12:41 AM, Amit Langote wrote:
>
>> START [ EXCL ] (startval) END [ INCL ] (endval)
>>
>> That is, in range type notation, '[startval, endval)' is the default
>> behavior. So for each partition, there is at least the following pieces of
>> metadata:
>>
>
> This is really close, and if it is what we ended up with we would be able
> to use it.
>
> I suggest that the range notation can be used even when no suitable range
> type exists.
>
> I assume the code for parsing a range spec regardless of data type already
> exists, but in case it doesn't, take a range spec of unknown type:
>
> [x,y)
>
> x and y are either going to be raw strings or doublequoted strings with
> possible doublequote escapes, each of which would be coercible into the the
> type of the partition column.
>
> In other words, if your string values were 'blah , blah ' and 'fabizzle',
> the [) range spec would be ["blah , blah ",fabizzle).
Hm, I see. How about multi-column keys? Do we care enough about that use
case? I don't see a nice-enough-looking range literal for such keys.
Consider for instance,
With the partitioned table defined as:
CREATE TABLE foo(c1 char(2), c2 char(2)) PARTITION BY RANGE (c1, c2);
where we'd use LESS THAN as:
CREATE TABLE foo_ax1x PARTITION OF foo FOR VALUES LESS THAN ('b', '2');
CREATE TABLE foo_ax2x PARTITION OF foo FOR VALUES LESS THAN ('b', '3');
CREATE TABLE foo_ax3x PARTITION OF foo FOR VALUES LESS THAN ('b', '4');
CREATE TABLE foo_bx1x PARTITION OF foo FOR VALUES LESS THAN ('c', '2');
CREATE TABLE foo_bx2x PARTITION OF foo FOR VALUES LESS THAN ('c', '3');
CREATE TABLE foo_bx3x PARTITION OF foo FOR VALUES LESS THAN ('c', '4');
I guess it would be the following with the new range spec:
CREATE TABLE foo_ax1x PARTITION OF foo FOR VALUES ['b', 'b'], ['1', '2');
CREATE TABLE foo_ax2x PARTITION OF foo FOR VALUES ['b', 'b'], ['2', '3');
CREATE TABLE foo_ax3x PARTITION OF foo FOR VALUES ['b', 'b'], ['3', '4');
CREATE TABLE foo_bx1x PARTITION OF foo FOR VALUES ['c', 'c'], ['1', '2');
CREATE TABLE foo_bx2x PARTITION OF foo FOR VALUES ['c', 'c'], ['2', '3');
CREATE TABLE foo_bx3x PARTITION OF foo FOR VALUES ['c', 'c'], ['3', '4');
The first column does not unambiguously select a partition with such keys.
Now I can imagine that it is possible to "emulate" such a multi-column
range key with k columns by k levels of range-range-* partitioning:
CREATE TABLE foo(c1 char(2), c2 char(2)) PARTITION BY RANGE (c1);
CREATE TABLE foo_ax PARTITION OF foo
FOR VALUES ['b, 'c') PARTITION BY RANGE (c2);
CREATE TABLE foo_ax1x PARTITION OF foo_ax FOR VALUES ['1', '2');
CREATE TABLE foo_ax2x PARTITION OF foo_ax FOR VALUES ['2', '3');
CREATE TABLE foo_ax3x PARTITION OF foo_ax FOR VALUES ['3', '4');
CREATE TABLE foo_bx PARTITION OF foo
FOR VALUES ['b, 'c') PARTITION BY RANGE (c2);
CREATE TABLE foo_bx1x PARTITION OF foo_bx FOR VALUES ['1', '2');
CREATE TABLE foo_bx2x PARTITION OF foo_bx FOR VALUES ['2', '3');
CREATE TABLE foo_bx3x PARTITION OF foo_bx FOR VALUES ['3', '4');
But IIRC, I have been contradicted once before regarding whether two ways
are exactly the same.
> Using regular range specs syntax also allows for the range to be unbounded
> in either or both directions, which is a possibility, especially in newer
> tables where the expected distribution of data is unknown.
We would want to also think about what subset of many permutations of this
syntax to accept range specs for new partitions. Mostly to preserve the
non-overlapping invariant and I think it would also be nice to prevent gaps.
Consider that once we create:
PARTITION FOR VALUES [current_date,);
Now to create a new partition starting at later date, we have to have a
"split partition" feature which would scan the above partition to
distribute the existing data rows appropriately to the resulting two
partitions. Right?
IOW, one shouldn't create an unbounded partition if more partitions in the
unbounded direction are expected to be created. It would be OK for
unbounded partitions to be on the lower end most of the times.
> p.s. Sorry I haven't been able to kick the tires just yet. We have a very
> good use case for this, it's just a matter of getting a machine and the
> time to devote to it.
I would appreciate it. You could wait a little more for my next
submission which will contain some revisions to the tuple routing code.
Thanks,
Amit
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 161+ messages in thread
* Re: Declarative partitioning
@ 2016-02-24 18:31 Corey Huinker <corey.huinker@gmail.com>
parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
0 siblings, 1 reply; 161+ messages in thread
From: Corey Huinker @ 2016-02-24 18:31 UTC (permalink / raw)
To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; +Cc: Robert Haas <robertmhaas@gmail.com>; pgsql-hackers
>
> Hm, I see. How about multi-column keys? Do we care enough about that use
> case? I don't see a nice-enough-looking range literal for such keys.
> Consider for instance,
>
> With the partitioned table defined as:
>
> CREATE TABLE foo(c1 char(2), c2 char(2)) PARTITION BY RANGE (c1, c2);
>
Good question! I would assume that we'd use a syntax that presumes c1 and
c2 are a hypothetical composite type. But what does that look like?
To answer it, I tried this:
# create type duple as (a text, b text);
CREATE TYPE
# create type duplerange as range (subtype = duple);
CREATE TYPE
# select '(beebop,alula)'::duple;
duple
----------------
(beebop,alula)
(1 row)
# select '("hey ho","letsgo")'::duple;
duple
-------------------
("hey ho",letsgo)
(1 row)
analytics=# select duplerange('(beebop,alula)','("hey ho","letsgo")','(]');
duplerange
------------------------------------------
("(beebop,alula)","(""hey ho"",letsgo)"]
(1 row)
So I would assume that we'd use a syntax that presumed the columns were in
a composite range type.
Which means your creates would look like (following Robert Haas's implied
suggestion that we leave off the string literal quotes):
CREATE TABLE foo_ax1x PARTITION OF foo FOR VALUES ( , (b,2) );
CREATE TABLE foo_ax1x PARTITION OF foo FOR VALUES [ (b,2), (b,3) );
CREATE TABLE foo_ax1x PARTITION OF foo FOR VALUES [ (b,3), (b,4) );
CREATE TABLE foo_ax1x PARTITION OF foo FOR VALUES [ (b,4), (c,2) );
CREATE TABLE foo_ax1x PARTITION OF foo FOR VALUES [ (c,2), (c,3) );
CREATE TABLE foo_ax1x PARTITION OF foo FOR VALUES [ (c,3), (c,4) );
That's not terrible looking.
We would want to also think about what subset of many permutations of this
> syntax to accept range specs for new partitions. Mostly to preserve the
> non-overlapping invariant and I think it would also be nice to prevent
> gaps.
>
Gaps *might* be intentional. I can certainly see where we'd want to set up
warnings for discontinuity, or perhaps some utility functions:
pg_partitions_ranges_are_continuous('master_table_name')
pg_partitions_are_adjacent('master_table_name','p1','p2')
But for the most part, range partitions evolve from splits when one
partition grows too big, so that won't be such a problem.
Consider that once we create:
>
> PARTITION FOR VALUES [current_date,);
>
> Now to create a new partition starting at later date, we have to have a
> "split partition" feature which would scan the above partition to
> distribute the existing data rows appropriately to the resulting two
> partitions. Right?
>
Correct. And depending on policy, that might be desirable and might be not.
If the table were for death records, we'd very much want to reject rows in
the future, if only to avoid upsetting the person.
If the table were of movie release dates, we'd *expect* that only dates
(,current_date] would be entered, but if someone chose to leak a release
date, we'd want to capture that and deal with it later.
So yeah, we're going to (eventually) need a SPLIT PARTITION that migrates
rows to a new partition.
> IOW, one shouldn't create an unbounded partition if more partitions in the
> unbounded direction are expected to be created. It would be OK for
> unbounded partitions to be on the lower end most of the times.
>
On this I'll have to disagree. My own use case where I use my
range_partitioning extension starts off with a single partition () and all
new partitions are splits of that. The ranges evolve over time as
partitions grow and slow down. It's nice because we're not trying to
predict where growth will be, we split where growth is.
>
> > p.s. Sorry I haven't been able to kick the tires just yet. We have a very
> > good use case for this, it's just a matter of getting a machine and the
> > time to devote to it.
>
> I would appreciate it. You could wait a little more for my next
> submission which will contain some revisions to the tuple routing code.
>
>
Ok, I'll wait a bit. In the mean time I can tell you a bit about the
existing production system I'm hoping to replicate in true partitioning
looks like this:
Big Master Table:
Range partition by C collated text
Date Range
Date Range
...
Range partition by C collated text
Date Range
Date Range
...
...
Currently this is accomplished through my range_partitioning module, and
then using pg_partman on those partitions. It works, but it's a lot of
moving parts.
The machine will be a 32 core AWS box. As per usual with AWS, it will be
have ample memory and CPU, and be somewhat starved for I/O.
Question: I haven't dove into the code, but I was curious about your tuple
routing algorithm. Is there any way for the algorithm to begin it's scan of
candidate partitions based on the destination of the last row inserted this
statement? I ask because most use cases (that I am aware of) have data that
would naturally cluster in the same partition.
^ permalink raw reply [nested|flat] 161+ messages in thread
* Re: Declarative partitioning
@ 2016-02-25 15:40 Jean-Pierre Pelletier <jppelletier@e-djuster.com>
0 siblings, 0 replies; 161+ messages in thread
From: Jean-Pierre Pelletier @ 2016-02-25 15:40 UTC (permalink / raw)
To: pgsql-hackers
Why not based it on "Exclusion Constraint" ?
Most discussions as of late seems to focus on Range overlaps
which appeal (I would think) is that it supports both "equality" and
"overlaps",
two popular partitioning schemes.
"Equality" as in "value1 = value2" can be implemented with "range
overlaps"
as "range(value1,value) = range(value,value2)".
I would think that Partitioning schemes can be Declarative, Efficient and
not restricted to Equality and Overlaps
as long as all partitions (of a partitioned table) are using a single
partitioning definition expressed as:
- An Immutable Expression on tuple columns, in the simplest case a single
column
- An Operator, in the simplest case, "equality"
That seems very close to the semantic of "Constraint Exclusion" as
described here:
http://thoughts.davisjeff.com/2010/09/25/exclusion-constraints-are-general
ized-sql-unique/
If partitioning could be based on EC, it would bring these additional
benefits:
- The choice of operator as long as it is boolean. commutative and
Indexable
- The use of Expression/Function and not just bare columns
Jean-Pierre Pelletier
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 161+ messages in thread
* Re: Declarative partitioning
@ 2016-02-25 15:43 Jean-Pierre Pelletier <jppelletier@e-djuster.ca>
0 siblings, 1 reply; 161+ messages in thread
From: Jean-Pierre Pelletier @ 2016-02-25 15:43 UTC (permalink / raw)
To: pgsql-hackers
Why not based it on "Exclusion Constraint" ?
Most discussions as of late seems to focus on Range overlaps which appeal
(I would think) is that it supports both "equality" and "overlaps", two
popular partitioning schemes.
"Equality" as in "value1 = value2" can be implemented with "range
overlaps"
as "range(value1,value) = range(value,value2)".
I would think that Partitioning schemes can be Declarative, Efficient and
not restricted to Equality and Overlaps as long as all partitions (of a
partitioned table) are using a single partitioning definition expressed
as:
- An Immutable Expression on tuple columns, in the simplest case a single
column
- An Operator, in the simplest case, "equality"
That seems very close to the semantic of "Constraint Exclusion" as
described here:
http://thoughts.davisjeff.com/2010/09/25/exclusion-constraints-are-general
ized-sql-unique/
If partitioning could be based on EC, it would bring these additional
benefits:
- The choice of operator as long as it is boolean. commutative and
Indexable
- The use of Expression/Function and not just bare columns
Jean-Pierre Pelletier
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 161+ messages in thread
* Re: Declarative partitioning
@ 2016-02-26 06:05 Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
parent: Robert Haas <robertmhaas@gmail.com>
0 siblings, 1 reply; 161+ messages in thread
From: Amit Langote @ 2016-02-26 06:05 UTC (permalink / raw)
To: Robert Haas <robertmhaas@gmail.com>; +Cc: pgsql-hackers
On 2016/02/23 22:51, Robert Haas wrote:
> On Thu, Feb 18, 2016 at 11:11 AM, Amit Langote wrote:
>> Some might think that writing potentially the same PARTITION BY clause 100
>> times for 100 level-1 partitions could be cumbersome. That is what
>> SUBPARTITION BY notation may be good as a shorthand for.
>
> I think that anybody who is doing that much partitioning is going to
> use a tool to generate the DDL anyway, so it doesn't really matter.
>>> I think if you've got SUBPARTITION as a keyword in the syntax
>>> anywhere, you're doing it wrong. The toplevel object shouldn't really
>>> care whether its children are themselves partitioned or not.
>>
>> This is fine for the internals. SUBPARTITION BY is mere syntax sugar. It
>> might as well be just cascaded PARTITION BYs. The point is to specify as
>> many of those with CREATE TABLE toplevel as the number of levels of
>> partitioning we want. That does however fix the number of levels in advance.
>
> Which I think is not good. If we say that a table can be partitioned,
> and a table that is a partition can also be partitioned, we've got a
> nice general system. Fixing the number of partitioning levels for the
> sake of a little syntactic sugar is, IMHO, getting our priorities
> backwards.
OK.
To reiterate the syntax:
CREATE TABLE parent(...) PARTITION BY
CREATE TABLE partition
PARTITION OF parent FOR VALUES ... [ PARTITION BY ]
ALTER TABLE partitioned
ATTACH PARTITION name FOR VALUES ...
USING TABLE source_table [ NO VALIDATE ]
ALTER TABLE partitioned
DETACH PARTITION name [ WITH new_name ]
A note about NO VALIDATE in ATTACH PARTITION:
If specified, it means user is telling the system to "trust" that none of
the rows contained in the source table lie outside partition boundary
specification (the FOR VALUES clause). Which is not the same thing as
adding a NOT VALID check constraint because the check constraint is
assumed invalid by the optimizer until explicitly validated by VALIDATE
CONSTRAINT. The default behavior is to validate by scanning the source
table to check for violating rows and fail adding the partition, if any.
Because adding a partition requires an exclusive lock on the parent, the
default behavior may cause us to have the lock for long durations which
may be undesirable. Should do better than that?
>> While we are on the syntax story, how about FOR VALUES syntax for range
>> partitions (sorry for piggybacking it here in this message). At least some
>> people dislike LESS THAN notation. Corey Huinker says we should be using
>> range type literals for that. It's not clear though that using range type
>> literals directly is a way ahead. For one, range type API expects there to
>> exist a range type with given element type. Whereas all we require for
>> range partitioning proper is for column type to have a btree operator
>> class. Should we require it to have an associated range type as well?
>> Don't think that there exists an API to check that either. All in all,
>> range types are good to implement things in applications but not so much
>> within the backend (unless I'm missing something). I know reinventing the
>> wheel is disliked as well but perhaps we could offer something like the
>> following because Corey offered some examples which would help from the
>> flexibility:
>>
>> START [ EXCL ] (startval) END [ INCL ] (endval)
>
> I don't think using range type literals is gonna work. There's no
> guarantee that the necessary range types exist. However, we could
> possibly use a syntax inspired by the syntax for range types. I'm a
> little nervous about asking people to type commands with mismatching
> braces:
>
> CREATE TABLE partition_name PARTITION OF parent_name FOR VALUES [ 1, 10 );
>
> ...but maybe it's a good idea. It certainly has the advantage of
> being more compact than a lot of there ways we might choose to write
> that. And I think LESS THAN sucks. It's just clunky and awkward
> syntax.
Slightly concerned about multi-column range partition key but as suggested
by Corey, we can use record-like notation.
CREATE TABLE foo(c1 char(2), c2 char(2)) PARTITION BY RANGE (c1, c2);
CREATE TABLE foo_ax1x PARTITION OF foo FOR VALUES [ ('a', 1), ('a', 2) );
CREATE TABLE foo_ax2x PARTITION OF foo FOR VALUES [ ('a', 2), ('a', 3) );
CREATE TABLE foo_ax3x PARTITION OF foo FOR VALUES [ ('a', 3), ('a', 4) );
CREATE TABLE foo_bx1x PARTITION OF foo FOR VALUES [ ('b', 1), ('b', 2) );
CREATE TABLE foo_bx2x PARTITION OF foo FOR VALUES [ ('b', 2), ('b', 3) );
CREATE TABLE foo_bx3x PARTITION OF foo FOR VALUES [ ('b', 3), ('b', 4) );
>> I see the trade-off. I agree that considering the significance for attach
>> partition case is quite important.
>>
>> So, the tuple routing code should be ready to use projection if there
>> happens to be a partition with differing tuple descriptor. In the code I
>> posted, a ResultRelInfo is lazily built afresh for each inserted tuple in
>> ExecInsert's case and for each tuple where the chosen partition is
>> different from the previous tuple's in CopyFrom's case. One can feel that
>> there is a certain overhead to that approach for the bulk-loading case
>> (almost every CopyFrom invocation). Now if projection enters this picture,
>> we have to consider that too. Should we initialize ResultRelInfo's and
>> corresponding ProjectionInfo's for all partitions beforehand? Consider how
>> ModifyTable currently handles update on inheritance set, for example. That
>> would incur unnecessary overhead if only a single tuple is inserted. But
>> it would certainly help bulk-loading case. Am I missing something?
>
> I'd say you are missing benchmarking data. :-)
>
> Why should the single-tuple case be harmed here?
I think setting up N ResultRelInfos in advance where the tuple would only
ever require one might be superfluous. But that may point to some flaw in
my original design or thinking about the case.
As I said, my approach so far is to "lazily" set up a ResultRelInfo once a
valid leaf partition for the tuple is found and OID returned. Now there
will also be a ProjectionInfo if the target partition's physical
descriptor happens to differ from the root table. All we ever start with
in ExecInsert() is root table's ResultRelInfo which comes with executor
state related to partition key (expression states, if any) and information
about root table's partitions. ResultRelInfos for actual partitions that
the tuple is inserted into are ephemeral and are created for
ExecInsertIndexTuples's perusal only, that is, switched back to root
table's right after inserting index tuples. Maybe, there should be an
array in EState of ResultRelInfos and ProjectionInfos of size N where N is
the number of leaf partitions and indexed by leaf partition sequence
number. The sequence number would be based on OID order. That means the
partition module now returns the sequence number instead of OID.
I indeed should collect some performance numbers for any resulting
implementation.
>> Consider that we create partitions as inheritance children, that is,
>> creating a partition using:
>>
>> CREATE TABLE partition PARTITION OF parent FOR VALUES ...
>>
>> is equivalent to saying
>>
>> CREATE TABLE partition (...) INHERITS (parent)
>>
>> except that the latter allows partition to have its own column
>> definitions, table constraints and multiple parents as long as things are
>> conflict-free. The former requires specifying partition bounding values.
>> The common thing between the two then is StoreCatalogInheritance(partOid,
>> parentOid) that will mark partition as inheritance child of parent in
>> pg_inherits. So, our new partitions are really inheritance children but
>> that's not apparent to users (where this last bit is important).
>
> So far, this sounds good to me.
>
>> Then consider ALTER TABLE partition - should we need to handle it in way
>> different from existing inheritance code would do -
>>
>> * Prevent something whereas regular inheritance wouldn't?
>> * Do something instead of/in addition to whatever regular inheritance does?
>>
>> Consider adding a column to partition - regular inheritance wouldn't
>> prevent it because adding a column to child table doesn't get in the way
>> of how inheritance works. Now we can make it so that if table is a
>> partition created with CRATE TABLE PARTITION OF (which we can tell because
>> the table has pg_partition entry), we should *actively* prevent adding a
>> column. Inheritance already prevents dropping an inherited attribute in
>> child but says "cannot drop inherited column". IMO in a partition's case,
>> it should say "cannot drop columns of a partition".
>
> Agreed. So maybe pg_inherits gets an additional column that indicates
> whether this is "regular" inheritance or "partitioning" inheritance,
> and in the latter case there are additional restrictions.
So far how I have thought it would work is that "partitions" have a
pg_partition entry. So, wherever we want to check if an inheritance child
is a partition, we just look up the OID in the syscache on
pg_partition.partrelid.
>> OTOH, adding a column to parent propagates to all partitions by way of
>> ATSimpleRecursion(). But not everything that ATSimpleRecursion() does is
>> necessary for partitioned tables (which we can tell from relkind or maybe
>> because table has pg_partitioned_rel entry) - especially no need to
>> propagate column default and check constraint, etc. Because one cannot
>> apply insert/update on partitions anyway.
>
> Hmm. I feel like maybe that should be allowed. If the user wants to
> bulk-load a bunch of data and knows that it goes in a particular
> partition, why should they have to pay the overhead of determining
> that anew for each row?
Hm, I thought tuple routing through the parent is the (only) way to ensure
that a given partition only contains rows meant for that partition. IOW,
there are no CHECK constraints per partition guarding the rows going in.
However, there is a provision in the latest patch to "derive" a CHECK
constraint by combining the key info and bound info and maybe
ExecConstraints() should check those in addition to
TupleConstr.has_not_null and TupleConstr.check constraints. Thoughts?
Thanks,
Amit
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 161+ messages in thread
* Re: Declarative partitioning
@ 2016-02-26 07:14 Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
parent: Jean-Pierre Pelletier <jppelletier@e-djuster.ca>
0 siblings, 0 replies; 161+ messages in thread
From: Amit Langote @ 2016-02-26 07:14 UTC (permalink / raw)
To: jppelletier@e-djuster.com; pgsql-hackers
Hi,
Thanks for your feedback.
On 2016/02/26 0:43, Jean-Pierre Pelletier wrote:
> Why not based it on "Exclusion Constraint" ?
>
> Most discussions as of late seems to focus on Range overlaps which appeal
> (I would think) is that it supports both "equality" and "overlaps", two
> popular partitioning schemes.
>
> "Equality" as in "value1 = value2" can be implemented with "range
> overlaps"
> as "range(value1,value) = range(value,value2)".
>
> I would think that Partitioning schemes can be Declarative, Efficient and
> not restricted to Equality and Overlaps as long as all partitions (of a
> partitioned table) are using a single partitioning definition expressed
> as:
> - An Immutable Expression on tuple columns, in the simplest case a single
> column
> - An Operator, in the simplest case, "equality"
>
> That seems very close to the semantic of "Constraint Exclusion" as
> described here:
> http://thoughts.davisjeff.com/2010/09/25/exclusion-constraints-are-general
> ized-sql-unique/
>
> If partitioning could be based on EC, it would bring these additional
> benefits:
> - The choice of operator as long as it is boolean. commutative and
> Indexable
> - The use of Expression/Function and not just bare columns
Note that proposed patch does more or less what you say we should be doing
minus the "exclusion constraint" part.
With the proposed, you can specify an expression(s)/column(s) as partition
key along with an "operator class" for the column like below:
CREATE TABLE foo (a type_name) PARTITION BY LIST (a USING opclass_name);
CREATE TABLE bar (a type_name) PARTITION BY RANGE (a USING opclass_name);
Right now, supported partition strategies are RANGE and LIST where "btree
operators" suffice. So in the above example, type_name must have a
suitable btree operators class defined in the system which could be
opclass_name. If opclass_name was created as the default for type_name,
one need not write USING opclass_name.
Then when you create a partition of foo:
CREATE TABLE foo_partition PARTITION OF foo FOR VALUES IN (val1, val2);
system enforces that foo_partition only contains values such that:
a = ANY ( ARRAY [val1, val2] ),
where the operator "=" refers to an operator belonging to the operator
class opclass_name (hence can have a user-defined notion of "equality").
And when you create a partition of bar:
CREATE TABLE bar_partition PARTITION OF bar FOR VALUES [val1, val2);
system enforces that bar_partition only contains values such that:
val1 <= a < val2,
where operators "<=" and "<" refer to the operators belonging to the
operator class opclass_name (hence can have a user-defined notion of
ordering).
Further, system can also optimize queries based on its knowledge of
operators appearing in query clauses and implicit constraints just
mentioned above. Specifically, it can can exclude partitions using
"constraint exclusion" which is NOT the same thing as "exclusion
constraints", as you might be aware.
"Exclusion constraints" depend on having suitable a index (just like
unique constraint enforcing btree index) that uses the specified operator
to enforce the constraint:
postgres=# CREATE TABLE circles (
postgres(# c circle,
postgres(# EXCLUDE USING gist (c WITH &&)
postgres(# );
CREATE TABLE
postgres=# \d+ circles
Table "public.circles"
Column | Type | Modifiers | Storage | Stats target | Description
-------+--------+-----------+---------+--------------+-------------
c | circle | | plain | |
Indexes:
"circles_c_excl" EXCLUDE USING gist (c WITH &&)
The talk of "non-overlapping partitions" in this thread refers to the
invariant that partition DDL should maintain which uses ad-hoc logic to do
that but which is based on the semantics of the specified operators.
Thanks,
Amit
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 161+ messages in thread
* Re: Declarative partitioning
@ 2016-02-28 22:14 Ildar Musin <i.musin@postgrespro.ru>
parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
0 siblings, 1 reply; 161+ messages in thread
From: Ildar Musin @ 2016-02-28 22:14 UTC (permalink / raw)
To: pgsql-hackers
16/02/16 07:46, Amit Langote wrote:
> Hi Josh,
>
> On 2016/02/16 11:41, Josh berkus wrote:
>> On 02/15/2016 04:28 PM, Amit Langote wrote:
>>> Also, you won't see any optimizer and executor changes. Queries will still
>>> use the same plans as existing inheritance-based partitioned tables,
>>> although as I mentioned, constraint exclusion won't yet kick in. That will
>>> be fixed very shortly.
>> We're not going to use CE for the new partitioning long-term, are we? This
>> is just the first version, right?
> Yes. My approach in previous versions of stuffing major planner changes in
> with the syntax patch was not quite proper in retrospect. So, I thought
> I'd propose any major planner (and executor) changes later.
>
> Thanks,
> Amit
>
Hello Amit,
Thank you for your work. I'm currently working on extension aimed at
planner optimization for partitioned tables
(https://github.com/postgrespro/pg_pathman). At this moment I have an
implementation of binary search for range partitioned tables with basic
partitioning keys (date, timestamp, integers etc). And I'd like to try
to combine your syntax and infrastructure with my binary search
implementation.
There likely will be changes in range syntax and partitions cache
structure based on discussion. So looking forward for your next patch.
Ildar
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 161+ messages in thread
* Re: Declarative partitioning
@ 2016-02-29 05:01 Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
parent: Ildar Musin <i.musin@postgrespro.ru>
0 siblings, 0 replies; 161+ messages in thread
From: Amit Langote @ 2016-02-29 05:01 UTC (permalink / raw)
To: Ildar Musin <i.musin@postgrespro.ru>; pgsql-hackers
Hi Ildar,
On 2016/02/29 7:14, Ildar Musin wrote:
> 16/02/16 07:46, Amit Langote wrote:
>> On 2016/02/16 11:41, Josh berkus wrote:
>>> We're not going to use CE for the new partitioning long-term, are we? This
>>> is just the first version, right?
>> Yes. My approach in previous versions of stuffing major planner changes in
>> with the syntax patch was not quite proper in retrospect. So, I thought
>> I'd propose any major planner (and executor) changes later.
>>
>> Thanks,
>> Amit
>>
> Hello Amit,
>
> Thank you for your work. I'm currently working on extension aimed at
> planner optimization for partitioned tables
> (https://github.com/postgrespro/pg_pathman). At this moment I have an
> implementation of binary search for range partitioned tables with basic
> partitioning keys (date, timestamp, integers etc). And I'd like to try to
> combine your syntax and infrastructure with my binary search implementation.
> There likely will be changes in range syntax and partitions cache
> structure based on discussion. So looking forward for your next patch.
Sure, thanks! I will look at your extension as well.
Thanks,
Amit
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 161+ messages in thread
* Re: Declarative partitioning
@ 2016-03-08 01:39 Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
parent: Corey Huinker <corey.huinker@gmail.com>
0 siblings, 2 replies; 161+ messages in thread
From: Amit Langote @ 2016-03-08 01:39 UTC (permalink / raw)
To: Corey Huinker <corey.huinker@gmail.com>; +Cc: Robert Haas <robertmhaas@gmail.com>; pgsql-hackers
Hi Corey,
Sorry for replying so late.
On 2016/02/25 3:31, Corey Huinker wrote:
>
[ ... ]
> So I would assume that we'd use a syntax that presumed the columns were in
> a composite range type.
>
> Which means your creates would look like (following Robert Haas's implied
> suggestion that we leave off the string literal quotes):
>
> CREATE TABLE foo_ax1x PARTITION OF foo FOR VALUES ( , (b,2) );
> CREATE TABLE foo_ax1x PARTITION OF foo FOR VALUES [ (b,2), (b,3) );
> CREATE TABLE foo_ax1x PARTITION OF foo FOR VALUES [ (b,3), (b,4) );
> CREATE TABLE foo_ax1x PARTITION OF foo FOR VALUES [ (b,4), (c,2) );
> CREATE TABLE foo_ax1x PARTITION OF foo FOR VALUES [ (c,2), (c,3) );
> CREATE TABLE foo_ax1x PARTITION OF foo FOR VALUES [ (c,3), (c,4) );
>
> That's not terrible looking.
So I tried a grammar that looked like the following:
range_spec: lb_inc bound ',' bound ub_inc {<create-range-struct>}
lb_inc: '[' { $$ = true; } | '(' { $$ = false; }
ub_inc: ']' { $$ = true; } | ')' { $$ = false; }
bound: a_expr
{
if (IsA($1, RowExpr)) /* (key1, key2, ...) */
$$ = ((RowExpr) $1)->args;
else /* key */
$$ = list_make1($1);
}
| /* EMPTY */ { $$ = NIL; }
Everything seemed to go dandy until I tried FOR VALUES (blah , blah],
where psql wouldn't send the command string without accepting the closing
parenthesis, :(. So maybe I should try to put the whole thing in '', that
is, accept the full range_spec in a string, but then we are back to
requiring full-blown range parse function which I was trying to avoid by
using the aforementioned grammar. So, I decided to move ahead with the
following grammar for time being:
START (lower-bound) [ EXCLUSIVE ]
| END (upper-bound) [ INCLUSIVE ]
| START (lower-bound) [ EXCLUSIVE ] END (upper-bound) [ INCLUSIVE ]
Where,
*-bound: a_expr
| *-bound ',' a_expr
Note that in the absence of explicit specification, lower-bound is
inclusive and upper-bound is exclusive.
So the verbosity couldn't be helped unless accepting range literal in
string form and exporting rangetypes.c:range_parse() with range
partitioning-specific hacks (consider composite bounds) to parse it are
acceptable things.
>> IOW, one shouldn't create an unbounded partition if more partitions in the
>> unbounded direction are expected to be created. It would be OK for
>> unbounded partitions to be on the lower end most of the times.
>>
>
> On this I'll have to disagree. My own use case where I use my
> range_partitioning extension starts off with a single partition () and all
> new partitions are splits of that. The ranges evolve over time as
> partitions grow and slow down. It's nice because we're not trying to
> predict where growth will be, we split where growth is.
Okay, perhaps I should not presume a certain usage. However, as you know,
the usage like yours requires some mechanism of data redistribution (also
not without some syntax), which I am not targeting with the initial patch.
If that was the only way of creating partitions, matters would be a
little easier - you only specify a split point and have some rule about
inclusivity around the split point. But we have to start with the usage
where each new partition is separately created with explicit partition
bound specification that is complete in itself and that's where the logic
to check partition invariants may get a bit complicated.
> Ok, I'll wait a bit. In the mean time I can tell you a bit about the
> existing production system I'm hoping to replicate in true partitioning
> looks like this:
>
> Big Master Table:
> Range partition by C collated text
> Date Range
> Date Range
> ...
> Range partition by C collated text
> Date Range
> Date Range
> ...
> ...
>
> Currently this is accomplished through my range_partitioning module, and
> then using pg_partman on those partitions. It works, but it's a lot of
> moving parts.
>
> The machine will be a 32 core AWS box. As per usual with AWS, it will be
> have ample memory and CPU, and be somewhat starved for I/O.
>
> Question: I haven't dove into the code, but I was curious about your tuple
> routing algorithm. Is there any way for the algorithm to begin it's scan of
> candidate partitions based on the destination of the last row inserted this
> statement? I ask because most use cases (that I am aware of) have data that
> would naturally cluster in the same partition.
No. Actually the tuple-routing function starts afresh for each row. For
range partitions, it's binary search over an array of upper bounds. There
is no row-to-row state caching in the partition module itself.
Thanks,
Amit
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 161+ messages in thread
* Re: Declarative partitioning
@ 2016-03-09 00:17 Corey Huinker <corey.huinker@gmail.com>
parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
1 sibling, 1 reply; 161+ messages in thread
From: Corey Huinker @ 2016-03-09 00:17 UTC (permalink / raw)
To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; +Cc: Robert Haas <robertmhaas@gmail.com>; pgsql-hackers
>
> Sorry for replying so late.
>
No worries! We have jobs to do aside from this.
>
> Everything seemed to go dandy until I tried FOR VALUES (blah , blah],
> where psql wouldn't send the command string without accepting the closing
> parenthesis, :(. So maybe I should try to put the whole thing in '', that
> is, accept the full range_spec in a string, but then we are back to
> requiring full-blown range parse function which I was trying to avoid by
> using the aforementioned grammar. So, I decided to move ahead with the
> following grammar for time being:
>
> START (lower-bound) [ EXCLUSIVE ]
> | END (upper-bound) [ INCLUSIVE ]
> | START (lower-bound) [ EXCLUSIVE ] END (upper-bound) [ INCLUSIVE ]
>
> Where,
>
> *-bound: a_expr
> | *-bound ',' a_expr
>
> Note that in the absence of explicit specification, lower-bound is
> inclusive and upper-bound is exclusive.
>
Thanks for trying. I agree that it would be a full blown range parser, and
I'm not yet advanced enough to help you with that.
So presently partitions that are unbounded on the lower end aren't
possible, but that's a creation syntax issue, not an infrastructure issue.
Correct?
> Okay, perhaps I should not presume a certain usage. However, as you know,
> the usage like yours requires some mechanism of data redistribution (also
> not without some syntax), which I am not targeting with the initial patch.
>
I'm quite fine with limitations in this initial patch, especially if they
don't limit what's possible in the future.
> > Question: I haven't dove into the code, but I was curious about your
> tuple
> > routing algorithm. Is there any way for the algorithm to begin it's scan
> of
> > candidate partitions based on the destination of the last row inserted
> this
> > statement? I ask because most use cases (that I am aware of) have data
> that
> > would naturally cluster in the same partition.
>
> No. Actually the tuple-routing function starts afresh for each row. For
> range partitions, it's binary search over an array of upper bounds. There
> is no row-to-row state caching in the partition module itself.
>
>
bsearch should be fine, that's what I've used in my own custom partitioning
schemes.
Was there a new patch, and if so, is it the one you want me to kick the
tires on?
^ permalink raw reply [nested|flat] 161+ messages in thread
* Re: Declarative partitioning
@ 2016-03-09 00:57 Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
parent: Corey Huinker <corey.huinker@gmail.com>
0 siblings, 2 replies; 161+ messages in thread
From: Amit Langote @ 2016-03-09 00:57 UTC (permalink / raw)
To: Corey Huinker <corey.huinker@gmail.com>; +Cc: Robert Haas <robertmhaas@gmail.com>; pgsql-hackers
Hi,
On 2016/03/09 9:17, Corey Huinker wrote:
>>
>> Sorry for replying so late.
> No worries! We have jobs to do aside from this.
Thanks!
>> Everything seemed to go dandy until I tried FOR VALUES (blah , blah],
>> where psql wouldn't send the command string without accepting the closing
>> parenthesis, :(. So maybe I should try to put the whole thing in '', that
>> is, accept the full range_spec in a string, but then we are back to
>> requiring full-blown range parse function which I was trying to avoid by
>> using the aforementioned grammar. So, I decided to move ahead with the
>> following grammar for time being:
>>
>> START (lower-bound) [ EXCLUSIVE ]
>> | END (upper-bound) [ INCLUSIVE ]
>> | START (lower-bound) [ EXCLUSIVE ] END (upper-bound) [ INCLUSIVE ]
>>
>> Where,
>>
>> *-bound: a_expr
>> | *-bound ',' a_expr
>>
>> Note that in the absence of explicit specification, lower-bound is
>> inclusive and upper-bound is exclusive.
>
> Thanks for trying. I agree that it would be a full blown range parser, and
> I'm not yet advanced enough to help you with that.
>
> So presently partitions that are unbounded on the lower end aren't
> possible, but that's a creation syntax issue, not an infrastructure issue.
> Correct?
In case it wasn't apparent, you can create those:
FOR VALUES END (upper-bound) [INCLUSIVE]
which is equivalent to:
FOR VALUES '(, upper-bound)' or FOR VALUES '(, upper-bound]'
>>> Question: I haven't dove into the code, but I was curious about your
>> tuple
>>> routing algorithm. Is there any way for the algorithm to begin it's scan
>> of
>>> candidate partitions based on the destination of the last row inserted
>> this
>>> statement? I ask because most use cases (that I am aware of) have data
>> that
>>> would naturally cluster in the same partition.
>>
>> No. Actually the tuple-routing function starts afresh for each row. For
>> range partitions, it's binary search over an array of upper bounds. There
>> is no row-to-row state caching in the partition module itself.
>>
>>
> bsearch should be fine, that's what I've used in my own custom partitioning
> schemes.
>
> Was there a new patch, and if so, is it the one you want me to kick the
> tires on?
You didn't miss any. I need a little more time to send the next revision
with significant design overhaul.
Thanks,
Amit
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 161+ messages in thread
* Re: Declarative partitioning
@ 2016-03-09 01:41 Corey Huinker <corey.huinker@gmail.com>
parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
1 sibling, 0 replies; 161+ messages in thread
From: Corey Huinker @ 2016-03-09 01:41 UTC (permalink / raw)
To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; +Cc: Robert Haas <robertmhaas@gmail.com>; pgsql-hackers
>
> > So presently partitions that are unbounded on the lower end aren't
> > possible, but that's a creation syntax issue, not an infrastructure
> issue.
> > Correct?
>
> In case it wasn't apparent, you can create those:
>
> FOR VALUES END (upper-bound) [INCLUSIVE]
>
> which is equivalent to:
>
> FOR VALUES '(, upper-bound)' or FOR VALUES '(, upper-bound]'
>
>
Thanks for clarifying. My BNF-fu is weak.
>
>
^ permalink raw reply [nested|flat] 161+ messages in thread
* Re: Declarative partitioning
@ 2016-03-09 14:19 Robert Haas <robertmhaas@gmail.com>
parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
1 sibling, 1 reply; 161+ messages in thread
From: Robert Haas @ 2016-03-09 14:19 UTC (permalink / raw)
To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; +Cc: Corey Huinker <corey.huinker@gmail.com>; pgsql-hackers
On Mon, Mar 7, 2016 at 8:39 PM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:
>> Which means your creates would look like (following Robert Haas's implied
>> suggestion that we leave off the string literal quotes):
>>
>> CREATE TABLE foo_ax1x PARTITION OF foo FOR VALUES ( , (b,2) );
>> CREATE TABLE foo_ax1x PARTITION OF foo FOR VALUES [ (b,2), (b,3) );
>> CREATE TABLE foo_ax1x PARTITION OF foo FOR VALUES [ (b,3), (b,4) );
>> CREATE TABLE foo_ax1x PARTITION OF foo FOR VALUES [ (b,4), (c,2) );
>> CREATE TABLE foo_ax1x PARTITION OF foo FOR VALUES [ (c,2), (c,3) );
>> CREATE TABLE foo_ax1x PARTITION OF foo FOR VALUES [ (c,3), (c,4) );
>>
>> That's not terrible looking.
>
> So I tried a grammar that looked like the following:
>
> range_spec: lb_inc bound ',' bound ub_inc {<create-range-struct>}
>
> lb_inc: '[' { $$ = true; } | '(' { $$ = false; }
> ub_inc: ']' { $$ = true; } | ')' { $$ = false; }
>
> bound: a_expr
> {
> if (IsA($1, RowExpr)) /* (key1, key2, ...) */
> $$ = ((RowExpr) $1)->args;
> else /* key */
> $$ = list_make1($1);
> }
> | /* EMPTY */ { $$ = NIL; }
I think we're converging on a good syntax, but I don't think the
choice of nothingness to represent an open range is a good idea, both
because it will probably create grammar conflicts now or later and
also because it actually is sort of confusing and unintuitive to read
given the rest of our syntax. I suggest using UNBOUNDED instead.
Also, I don't think allowing an a_expr as a bound is remotely sensible
- I think you should allow only Sconst, NumericOnly, NULL, and
UNBOUNDED.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 161+ messages in thread
* Re: Declarative partitioning
@ 2016-03-09 14:20 Robert Haas <robertmhaas@gmail.com>
parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
0 siblings, 0 replies; 161+ messages in thread
From: Robert Haas @ 2016-03-09 14:20 UTC (permalink / raw)
To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; +Cc: pgsql-hackers
On Fri, Feb 26, 2016 at 1:05 AM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:
> I think setting up N ResultRelInfos in advance where the tuple would only
> ever require one might be superfluous. But that may point to some flaw in
> my original design or thinking about the case.
You have a point. But maybe we should get it working first and
optimize it later.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 161+ messages in thread
* Re: Declarative partitioning
@ 2016-03-09 17:48 Corey Huinker <corey.huinker@gmail.com>
parent: Robert Haas <robertmhaas@gmail.com>
0 siblings, 0 replies; 161+ messages in thread
From: Corey Huinker @ 2016-03-09 17:48 UTC (permalink / raw)
To: Robert Haas <robertmhaas@gmail.com>; +Cc: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; pgsql-hackers
>
>
> I think we're converging on a good syntax, but I don't think the
> choice of nothingness to represent an open range is a good idea, both
> because it will probably create grammar conflicts now or later and
> also because it actually is sort of confusing and unintuitive to read
> given the rest of our syntax. I suggest using UNBOUNDED instead.
>
>
As much as it reminds me of the window syntax I loathe (ROWS BETWEEN
UNBOUNDED ....gah), I'm inclined to agree with Robert here.
It also probably helps for code forensics in the sense that it's easier to
text search for a something than a nothing.
^ permalink raw reply [nested|flat] 161+ messages in thread
* Re: Declarative partitioning
@ 2016-03-16 13:56 Alexander Korotkov <a.korotkov@postgrespro.ru>
parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
1 sibling, 1 reply; 161+ messages in thread
From: Alexander Korotkov @ 2016-03-16 13:56 UTC (permalink / raw)
To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; +Cc: Corey Huinker <corey.huinker@gmail.com>; Robert Haas <robertmhaas@gmail.com>; pgsql-hackers
Hi, Amit!
I tried to apply your patch. It still applies, but has some duplicate
oids. After fixing duplicate oids, I've noticed following warning during
compilation by clang-700.1.81.
scan.c:10308:23: warning: unused variable 'yyg' [-Wunused-variable]
struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; /* This var may be
unused depending upon options. */
^
tablecmds.c:12922:6: warning: variable 'is_subpart' is used uninitialized
whenever 'if' condition is false [-Wsometimes-uninitialized]
if (parent != NULL)
^~~~~~~~~~~~~~
tablecmds.c:12931:12: note: uninitialized use occurs here
partKey = is_subpart ? list_nth(rootParent->rd_partkeys,
parent_level) :
^~~~~~~~~~
tablecmds.c:12922:2: note: remove the 'if' if its condition is always true
if (parent != NULL)
^~~~~~~~~~~~~~~~~~~
tablecmds.c:12912:19: note: initialize the variable 'is_subpart' to silence
this warning
bool is_subpart;
^
= '\0'
tablecmds.c:13375:37: warning: variable 'operoid' is uninitialized when
used here [-Wuninitialized]
comp_left_expr =
make_opclause(operoid, BOOLOID, false,
^~~~~~~
tablecmds.c:13326:17: note: initialize the variable 'operoid' to silence
this warning
Oid operoid;
^
= 0
Regression tests passed cleanly for me. I also examined code a bit. As I
get, for DML queries, declarative partitioning works like inheritance. It
just provides alternative way for collecting append_rel_list.
We're working on the other side of partitioning problem. Without solving
syntax problem, we're solving performance problems in pg_pathman extension:
https://github.com/postgrespro/pg_pathman. We already have interesting
results which you can see in blog posts [1], [2], [3].
We already have fast algorithm for partition selection in optimizer [1] and
effective optimization of filter conditions [3]. And we're planning to
implement following features:
- Execute time partitions selection (useful for nested loops and
prepared statements);
- Optimization of ordered output from patitioned tables (use Append
instead of MergeAppend when possible);
- Optimization of hash join when both tables are patitioned by join key.
9.6 feature freeze in coming, and we're planning our development resources
for 9.7. Besides providing an extension, we would like these features to
eventually arrive to core. In order to achieve this we need to port out
functionality over your declarative syntax. At first, we would need some
way for caching partition metadata suitable for fast partition selection.
For range partition it could be sorted array or RB-tree of partition
bounds. When we have this infrastructure, we can start porting pieces of
pg_pathman functionality to declarative partitiong.
So, our draft plan of patches would be following:
- Implement partition metadata cache suitable for fast partition
selection.
- Fast partition selection using metadata cache.
- Optimization of filter conditions passed to partitions.
- Execute time partitions selection (useful for nested loops and
prepared statements);
- Optimization of ordered output from patitioned tables (use Append
instead of MergeAppend when possible);
- Optimization of hash join when both tables are patitioned by join key.
I'd like to validate that this development plan doesn't overlaps with your
plans. If out plans are not overlapping then let's accept this plan of
work for 9.7.
Links.
[1] http://www.postgrespro.com/blog/pgsql/pg_pathman
[2] http://akorotkov.github.io/blog/2016/03/04/pg_pathman-beta-release/
[3]
http://akorotkov.github.io/blog/2016/03/14/pg_pathman-condition-processing/
------
Alexander Korotkov
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company
^ permalink raw reply [nested|flat] 161+ messages in thread
* Re: Declarative partitioning
@ 2016-03-16 14:29 Amit Langote <amitlangote09@gmail.com>
parent: Alexander Korotkov <a.korotkov@postgrespro.ru>
0 siblings, 1 reply; 161+ messages in thread
From: Amit Langote @ 2016-03-16 14:29 UTC (permalink / raw)
To: Alexander Korotkov <a.korotkov@postgrespro.ru>; +Cc: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; Corey Huinker <corey.huinker@gmail.com>; Robert Haas <robertmhaas@gmail.com>; pgsql-hackers
Hi Alexander,
Thanks a lot for taking a look!
On Wed, Mar 16, 2016 at 10:56 PM, Alexander Korotkov
<a.korotkov@postgrespro.ru> wrote:
> I tried to apply your patch. It still applies, but has some duplicate oids.
Actually, a reworked version I will post hopefully early next week
will have fixed this.
> After fixing duplicate oids, I've noticed following warning during
> compilation by clang-700.1.81.
>
> scan.c:10308:23: warning: unused variable 'yyg' [-Wunused-variable]
> struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; /* This var may be
> unused depending upon options. */
> ^
> tablecmds.c:12922:6: warning: variable 'is_subpart' is used uninitialized
> whenever 'if' condition is false [-Wsometimes-uninitialized]
> if (parent != NULL)
> ^~~~~~~~~~~~~~
> tablecmds.c:12931:12: note: uninitialized use occurs here
> partKey = is_subpart ? list_nth(rootParent->rd_partkeys,
> parent_level) :
> ^~~~~~~~~~
> tablecmds.c:12922:2: note: remove the 'if' if its condition is always true
> if (parent != NULL)
> ^~~~~~~~~~~~~~~~~~~
> tablecmds.c:12912:19: note: initialize the variable 'is_subpart' to silence
> this warning
> bool is_subpart;
> ^
> = '\0'
> tablecmds.c:13375:37: warning: variable 'operoid' is uninitialized when used
> here [-Wuninitialized]
> comp_left_expr =
> make_opclause(operoid, BOOLOID, false,
>
> ^~~~~~~
> tablecmds.c:13326:17: note: initialize the variable 'operoid' to silence
> this warning
> Oid operoid;
> ^
> = 0
Oh, I will find and fix these issues if they are still left in the
newer version. Thanks for reporting.
> Regression tests passed cleanly for me. I also examined code a bit. As I
> get, for DML queries, declarative partitioning works like inheritance. It
> just provides alternative way for collecting append_rel_list.
Right, my latest patch doesn't touch this part at all.
> We're working on the other side of partitioning problem. Without solving
> syntax problem, we're solving performance problems in pg_pathman extension:
> https://github.com/postgrespro/pg_pathman. We already have interesting
> results which you can see in blog posts [1], [2], [3].
I have been following the blog posts. Results look promising, :)
> We already have fast algorithm for partition selection in optimizer [1] and
> effective optimization of filter conditions [3]. And we're planning to
> implement following features:
>
> Execute time partitions selection (useful for nested loops and prepared
> statements);
> Optimization of ordered output from patitioned tables (use Append instead of
> MergeAppend when possible);
> Optimization of hash join when both tables are patitioned by join key.
>
> 9.6 feature freeze in coming, and we're planning our development resources
> for 9.7. Besides providing an extension, we would like these features to
> eventually arrive to core. In order to achieve this we need to port out
> functionality over your declarative syntax. At first, we would need some
> way for caching partition metadata suitable for fast partition selection.
> For range partition it could be sorted array or RB-tree of partition bounds.
> When we have this infrastructure, we can start porting pieces of pg_pathman
> functionality to declarative partitiong.
I had to think about the internal metadata representation (and its
caching) when developing the tuple routing solution. I am hopeful
that it is suitable for other executor mechanisms we will build for
partitioned tables.
> So, our draft plan of patches would be following:
>
> Implement partition metadata cache suitable for fast partition selection.
> Fast partition selection using metadata cache.
> Optimization of filter conditions passed to partitions.
> Execute time partitions selection (useful for nested loops and prepared
> statements);
> Optimization of ordered output from patitioned tables (use Append instead of
> MergeAppend when possible);
> Optimization of hash join when both tables are patitioned by join key.
>
> I'd like to validate that this development plan doesn't overlaps with your
> plans. If out plans are not overlapping then let's accept this plan of work
> for 9.7.
It looks OK to me. Thanks for sharing it.
Thanks,
Amit
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 161+ messages in thread
* Re: Declarative partitioning
@ 2016-03-16 14:49 Alexander Korotkov <a.korotkov@postgrespro.ru>
parent: Amit Langote <amitlangote09@gmail.com>
0 siblings, 1 reply; 161+ messages in thread
From: Alexander Korotkov @ 2016-03-16 14:49 UTC (permalink / raw)
To: Amit Langote <amitlangote09@gmail.com>; +Cc: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; Corey Huinker <corey.huinker@gmail.com>; Robert Haas <robertmhaas@gmail.com>; pgsql-hackers
On Wed, Mar 16, 2016 at 5:29 PM, Amit Langote <amitlangote09@gmail.com>
wrote:
> > 9.6 feature freeze in coming, and we're planning our development
> resources
> > for 9.7. Besides providing an extension, we would like these features to
> > eventually arrive to core. In order to achieve this we need to port out
> > functionality over your declarative syntax. At first, we would need some
> > way for caching partition metadata suitable for fast partition selection.
> > For range partition it could be sorted array or RB-tree of partition
> bounds.
> > When we have this infrastructure, we can start porting pieces of
> pg_pathman
> > functionality to declarative partitiong.
>
> I had to think about the internal metadata representation (and its
> caching) when developing the tuple routing solution. I am hopeful
> that it is suitable for other executor mechanisms we will build for
> partitioned tables.
>
Yes, it appears that I missed it. You already have sorted array for range
partitioning and binary search implementation. This is good.
I'm a bit worrying about list partitioning because in this case you scan
array sequentially. We could optimize this case for better handling of
many list partitions. This is probably not most common case though.
> So, our draft plan of patches would be following:
> >
> > Implement partition metadata cache suitable for fast partition selection.
> > Fast partition selection using metadata cache.
> > Optimization of filter conditions passed to partitions.
> > Execute time partitions selection (useful for nested loops and prepared
> > statements);
> > Optimization of ordered output from patitioned tables (use Append
> instead of
> > MergeAppend when possible);
> > Optimization of hash join when both tables are patitioned by join key.
> >
> > I'd like to validate that this development plan doesn't overlaps with
> your
> > plans. If out plans are not overlapping then let's accept this plan of
> work
> > for 9.7.
>
> It looks OK to me. Thanks for sharing it.
>
Great! Let's work together.
------
Alexander Korotkov
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company
^ permalink raw reply [nested|flat] 161+ messages in thread
* Re: Declarative partitioning
@ 2016-03-21 19:55 Robert Haas <robertmhaas@gmail.com>
parent: Alexander Korotkov <a.korotkov@postgrespro.ru>
0 siblings, 2 replies; 161+ messages in thread
From: Robert Haas @ 2016-03-21 19:55 UTC (permalink / raw)
To: Alexander Korotkov <a.korotkov@postgrespro.ru>; +Cc: Amit Langote <amitlangote09@gmail.com>; Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; Corey Huinker <corey.huinker@gmail.com>; pgsql-hackers
On Wed, Mar 16, 2016 at 10:49 AM, Alexander Korotkov
<a.korotkov@postgrespro.ru> wrote:
>> > I'd like to validate that this development plan doesn't overlaps with
>> > your
>> > plans. If out plans are not overlapping then let's accept this plan of
>> > work
>> > for 9.7.
>>
>> It looks OK to me. Thanks for sharing it.
>
>
> Great! Let's work together.
So, the last patch on this thread was posted on February 17th, and the
CF entry was marked Waiting on Author on March 2nd. Even if we had a
new patch in hand at this point, I don't think there's any real chance
of being able to get this done for 9.6; there are too many things left
to do here in terms of figuring out syntax and scope, and of course
performance testing. Moreover, when this goes in, it's going to open
up lots of opportunities for follow-up optimizations that we surely do
not have time to follow up on for 9.6. And, as it is, the patch
hasn't been updated in over a month and is clearly not in final form
as it exists today.
Therefore, I have marked this Returned with Feedback. I look forward
to returning to this topic for 9.7, and I'm willing to step up to the
plate and review this more aggressively at that time, with an eye
toward committing it when we've got it in good shape. But I don't
think there's any way to proceed with it for 9.6.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 161+ messages in thread
* Re: Declarative partitioning
@ 2016-03-21 20:00 Simon Riggs <simon@2ndQuadrant.com>
parent: Robert Haas <robertmhaas@gmail.com>
1 sibling, 0 replies; 161+ messages in thread
From: Simon Riggs @ 2016-03-21 20:00 UTC (permalink / raw)
To: Robert Haas <robertmhaas@gmail.com>; +Cc: Alexander Korotkov <a.korotkov@postgrespro.ru>; Amit Langote <amitlangote09@gmail.com>; Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; Corey Huinker <corey.huinker@gmail.com>; pgsql-hackers
On 21 March 2016 at 19:55, Robert Haas <robertmhaas@gmail.com> wrote:
> On Wed, Mar 16, 2016 at 10:49 AM, Alexander Korotkov
> <a.korotkov@postgrespro.ru> wrote:
> >> > I'd like to validate that this development plan doesn't overlaps with
> >> > your
> >> > plans. If out plans are not overlapping then let's accept this plan
> of
> >> > work
> >> > for 9.7.
> >>
> >> It looks OK to me. Thanks for sharing it.
> >
> >
> > Great! Let's work together.
>
> So, the last patch on this thread was posted on February 17th, and the
> CF entry was marked Waiting on Author on March 2nd. Even if we had a
> new patch in hand at this point, I don't think there's any real chance
> of being able to get this done for 9.6; there are too many things left
> to do here in terms of figuring out syntax and scope, and of course
> performance testing. Moreover, when this goes in, it's going to open
> up lots of opportunities for follow-up optimizations that we surely do
> not have time to follow up on for 9.6. And, as it is, the patch
> hasn't been updated in over a month and is clearly not in final form
> as it exists today.
>
> Therefore, I have marked this Returned with Feedback. I look forward
> to returning to this topic for 9.7, and I'm willing to step up to the
> plate and review this more aggressively at that time, with an eye
> toward committing it when we've got it in good shape. But I don't
> think there's any way to proceed with it for 9.6.
>
Good decision.
--
Simon Riggs http://www.2ndQuadrant.com/
<http://www.2ndquadrant.com/;
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
^ permalink raw reply [nested|flat] 161+ messages in thread
* Re: Declarative partitioning
@ 2016-03-22 02:23 Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
parent: Robert Haas <robertmhaas@gmail.com>
1 sibling, 1 reply; 161+ messages in thread
From: Amit Langote @ 2016-03-22 02:23 UTC (permalink / raw)
To: Robert Haas <robertmhaas@gmail.com>; Alexander Korotkov <a.korotkov@postgrespro.ru>; +Cc: Amit Langote <amitlangote09@gmail.com>; Corey Huinker <corey.huinker@gmail.com>; pgsql-hackers
On 2016/03/22 4:55, Robert Haas wrote:
> So, the last patch on this thread was posted on February 17th, and the
> CF entry was marked Waiting on Author on March 2nd. Even if we had a
> new patch in hand at this point, I don't think there's any real chance
> of being able to get this done for 9.6; there are too many things left
> to do here in terms of figuring out syntax and scope, and of course
> performance testing. Moreover, when this goes in, it's going to open
> up lots of opportunities for follow-up optimizations that we surely do
> not have time to follow up on for 9.6. And, as it is, the patch
> hasn't been updated in over a month and is clearly not in final form
> as it exists today.
>
> Therefore, I have marked this Returned with Feedback. I look forward
> to returning to this topic for 9.7, and I'm willing to step up to the
> plate and review this more aggressively at that time, with an eye
> toward committing it when we've got it in good shape. But I don't
> think there's any way to proceed with it for 9.6.
OK. I agree with the decision.
Actually, I was just about to post a patch set today, but I figure it's
too late for that. Anyway, I look forward to working on this for 9.7.
Thanks,
Amit
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 161+ messages in thread
* Re: Declarative partitioning
@ 2016-04-14 12:34 Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
0 siblings, 1 reply; 161+ messages in thread
From: Ashutosh Bapat @ 2016-04-14 12:34 UTC (permalink / raw)
To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; +Cc: Robert Haas <robertmhaas@gmail.com>; Alexander Korotkov <a.korotkov@postgrespro.ru>; Amit Langote <amitlangote09@gmail.com>; Corey Huinker <corey.huinker@gmail.com>; pgsql-hackers
Hi Amit,
Here are some random comments. You said that you were about to post a new
patch, so you might have already taken care of those comments. But anyway
here they are.
1. Following patches do not apply cleanly for me.
0001
0003 - conflicts at #include for partition.h in rel.h.
0004 - conflicts in src/backend/catalog/Makefile
0005 - conflicts in src/backend/parser/gram.y
2. The patches are using now used OIDs 3318-3323. Corresponding objects
need new unused oids.
3. In patch 0001-*, you are using indkey instead of partkey in one of the
comments and also in documentation.
4. After all patches are applied I am getting several errors like "error:
#error "lock.h may not be included from frontend code", while building
rmgrdesc.c. This
seems to be because rel.h includes partition.h, which leads to inclusion of
lock.h in rmgrdesc.c. Are you getting the same error message? It looks like
we need separate header file for declaring function which can be used at
the time of execution, which is anyway better irrespective of the compiler
error.
5. In expand_partitioned_rtentry(), instead of finding all the leaf level
relations, it might be better to build the RTEs, RelOptInfo and paths for
intermediate relations as well. This helps in partition pruning. In your
introductory write-up you have mentioned this. It might be better if v1
includes this change, so that the partition hierarchy is visible in EXPLAIN
output as well.
6. Explain output of scan on a partitioned table shows Append node with
individual table scans as sub-plans. May be we should annotate Append node
with
the name of the partitioned table to make EXPLAIN output more readable.
7. \d+ output of partitioned table does not show partitioning information,
something necessary for V1.
8. Instead of storing partition key column numbers and expressions
separately, can we store everything as expression; columns being a single
Var node expression? That will avoid constructing Var nodes to lookup in
equivalence classes for partition pruning or join optimizations.
10. The code distinguishes between the top level table and its partitions
which in turn are partitioned. We should try to minimize this distinction
as much as possible so as to use recursive functions for operating on
partitions. E.g. each of the partitioned partitions may be labelled
RELKIND_PARTITIONED_REL? A 0/NULL parent would distinguish between root
partition and child partitions.
On Tue, Mar 22, 2016 at 7:53 AM, Amit Langote <Langote_Amit_f8@lab.ntt.co.jp
> wrote:
> On 2016/03/22 4:55, Robert Haas wrote:
> > So, the last patch on this thread was posted on February 17th, and the
> > CF entry was marked Waiting on Author on March 2nd. Even if we had a
> > new patch in hand at this point, I don't think there's any real chance
> > of being able to get this done for 9.6; there are too many things left
> > to do here in terms of figuring out syntax and scope, and of course
> > performance testing. Moreover, when this goes in, it's going to open
> > up lots of opportunities for follow-up optimizations that we surely do
> > not have time to follow up on for 9.6. And, as it is, the patch
> > hasn't been updated in over a month and is clearly not in final form
> > as it exists today.
> >
> > Therefore, I have marked this Returned with Feedback. I look forward
> > to returning to this topic for 9.7, and I'm willing to step up to the
> > plate and review this more aggressively at that time, with an eye
> > toward committing it when we've got it in good shape. But I don't
> > think there's any way to proceed with it for 9.6.
>
> OK. I agree with the decision.
>
> Actually, I was just about to post a patch set today, but I figure it's
> too late for that. Anyway, I look forward to working on this for 9.7.
>
> Thanks,
> Amit
>
>
>
>
> --
> Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
> To make changes to your subscription:
> http://www.postgresql.org/mailpref/pgsql-hackers
>
--
Best Wishes,
Ashutosh Bapat
EnterpriseDB Corporation
The Postgres Database Company
^ permalink raw reply [nested|flat] 161+ messages in thread
* Re: Declarative partitioning
@ 2016-04-15 04:35 Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
parent: Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
0 siblings, 4 replies; 161+ messages in thread
From: Amit Langote @ 2016-04-15 04:35 UTC (permalink / raw)
To: Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; +Cc: Robert Haas <robertmhaas@gmail.com>; Alexander Korotkov <a.korotkov@postgrespro.ru>; Amit Langote <amitlangote09@gmail.com>; Corey Huinker <corey.huinker@gmail.com>; pgsql-hackers
Hi Ashutosh,
On 2016/04/14 21:34, Ashutosh Bapat wrote:
> Hi Amit,
> Here are some random comments. You said that you were about to post a new
> patch, so you might have already taken care of those comments. But anyway
> here they are.
Thanks a lot for the comments. The patch set changed quite a bit since the
last version. Once the CF entry was marked returned with feedback on
March 22, I held off sending the new version at all. Perhaps, it would
have been OK. Anyway here it is, if you are interested. I will create an
entry in CF 2016-09 for the same.
Also, see below replies to you individual comments.
> 1. Following patches do not apply cleanly for me.
> 0001
> 0003 - conflicts at #include for partition.h in rel.h.
> 0004 - conflicts in src/backend/catalog/Makefile
> 0005 - conflicts in src/backend/parser/gram.y
These should be fixed although the attached one is a significantly
different patch set.
> 2. The patches are using now used OIDs 3318-3323. Corresponding objects
> need new unused oids.
Right, fixed.
> 3. In patch 0001-*, you are using indkey instead of partkey in one of the
> comments and also in documentation.
Fixed.
> 4. After all patches are applied I am getting several errors like "error:
> #error "lock.h may not be included from frontend code", while building
> rmgrdesc.c. This
> seems to be because rel.h includes partition.h, which leads to inclusion of
> lock.h in rmgrdesc.c. Are you getting the same error message? It looks like
> we need separate header file for declaring function which can be used at
> the time of execution, which is anyway better irrespective of the compiler
> error.
Yeah, I too am beginning to feel that dividing partition.h into separate
headers would be a good idea in long term. For time being, I have managed
to get rid of partition.h #included in rel.h which solves the issue.
> 5. In expand_partitioned_rtentry(), instead of finding all the leaf level
> relations, it might be better to build the RTEs, RelOptInfo and paths for
> intermediate relations as well. This helps in partition pruning. In your
> introductory write-up you have mentioned this. It might be better if v1
> includes this change, so that the partition hierarchy is visible in EXPLAIN
> output as well.
>
> 6. Explain output of scan on a partitioned table shows Append node with
> individual table scans as sub-plans. May be we should annotate Append node
> with
> the name of the partitioned table to make EXPLAIN output more readable.
I got rid of all the optimizer changes in the new version (except a line
or two). I did that by switching to storing parent-child relationships in
pg_inherits so that all the existing optimizer code for inheritance sets
works unchanged. Also, the implementation detail that required to put
only leaf partitions in the append list is also gone. Previously no
storage was allocated for partitioned tables (either root or any of the
internal partitions), so it was being done that way.
Regarding 6, it seems to me that because Append does not have a associated
relid (like scan nodes have with scanrelid). Maybe we need to either fix
Append or create some enhanced version of Append which would also support
dynamic pruning.
> 7. \d+ output of partitioned table does not show partitioning information,
> something necessary for V1.
This has been fixed in the attached.
> 8. Instead of storing partition key column numbers and expressions
> separately, can we store everything as expression; columns being a single
> Var node expression? That will avoid constructing Var nodes to lookup in
> equivalence classes for partition pruning or join optimizations.
Hmm, let me consider that. FWIW, if you look at my proposed patch (or
description thereof) back in CF 2015-11 [1], you will see that I had
modeled matching clauses to partition key on lines of how similar
processing is done for indexes (as in how indexes are matched with clauses
- down all the way to match_clause_to_indexcol()).
None of that exists in the current patch set but when we get to that
(optimizer patch that is), I perhaps wouldn't do it radically differently.
I admit however that I hadn't thought really hard about join optimization
stuff so I may be missing something.
> 10. The code distinguishes between the top level table and its partitions
> which in turn are partitioned. We should try to minimize this distinction
> as much as possible so as to use recursive functions for operating on
> partitions. E.g. each of the partitioned partitions may be labelled
> RELKIND_PARTITIONED_REL? A 0/NULL parent would distinguish between root
> partition and child partitions.
Exactly how it's done in the attached. Any table that is partitioned is
now a RELKIND_PARTITIONED_REL.
Thanks,
Amit
[1] http://www.postgresql.org/message-id/563341AE.5010207@lab.ntt.co.jp
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Attachments:
[text/x-diff] 0001-Add-syntax-to-specify-partition-key-v1.patch (41.5K, ../../57106F9E.3000309@lab.ntt.co.jp/2-0001-Add-syntax-to-specify-partition-key-v1.patch)
download | inline diff:
From c127bf2888996938295848096886afc0f146747d Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Mon, 22 Feb 2016 14:06:33 +0900
Subject: [PATCH 1/5] Add syntax to specify partition key.
This adds the following clause to CREATE TABLE:
PARTITION BY method (col [USING opclass] [, ...])
where 'method' is either RANGE or LIST. 'col' could be either a simple
column reference or a parenthesized expression. 'opclass' is name
of btree operator class to use with the column. If method is LIST,
only one column is allowed, whereas, for RANGE method up to 32 columns
can be specified. If this clause is specified, the following constraints
viz. PARTITION KEY, UNIQUE, FOREIGN KEY, EXCLUSION are not allowed
because they cannot be supported on partitioned tables.
This adds 2 new parser nodes viz. PartitionElem and PartitionBy. The former
is similar to IndexElem and the latter consists of details of partition
key such as the method and list of PartitionElems. The new member partby
of CreateStmt is a pointer to the PartitionBy struct created when PARTITION
BY clause is present.
---
doc/src/sgml/ref/create_table.sgml | 35 +++++
src/backend/commands/indexcmds.c | 4 +-
src/backend/commands/tablecmds.c | 215 ++++++++++++++++++++++++++++
src/backend/nodes/copyfuncs.c | 33 +++++
src/backend/nodes/equalfuncs.c | 28 ++++
src/backend/nodes/outfuncs.c | 27 ++++
src/backend/parser/gram.y | 110 ++++++++++++---
src/backend/parser/parse_agg.c | 11 ++
src/backend/parser/parse_expr.c | 5 +
src/backend/parser/parse_utilcmd.c | 63 ++++++++
src/include/commands/defrem.h | 2 +
src/include/nodes/nodes.h | 2 +
src/include/nodes/parsenodes.h | 35 +++++
src/include/parser/kwlist.h | 1 +
src/include/parser/parse_node.h | 3 +-
src/include/pg_config_manual.h | 5 +
src/test/regress/expected/create_table.out | 79 ++++++++++
src/test/regress/sql/create_table.sql | 68 +++++++++
18 files changed, 704 insertions(+), 22 deletions(-)
diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml
index d1807ed..3da79a8 100644
--- a/doc/src/sgml/ref/create_table.sgml
+++ b/doc/src/sgml/ref/create_table.sgml
@@ -28,6 +28,7 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
[, ... ]
] )
[ INHERITS ( <replaceable>parent_table</replaceable> [, ... ] ) ]
+[ PARTITION BY {RANGE | LIST} ( { <replaceable class="parameter">column_name</replaceable> | ( <replaceable class="parameter">expression</replaceable> ) } [ <replaceable class="parameter">opclass</replaceable> ] [, ...] )
[ WITH ( <replaceable class="PARAMETER">storage_parameter</replaceable> [= <replaceable class="PARAMETER">value</replaceable>] [, ... ] ) | WITH OIDS | WITHOUT OIDS ]
[ ON COMMIT { PRESERVE ROWS | DELETE ROWS | DROP } ]
[ TABLESPACE <replaceable class="PARAMETER">tablespace_name</replaceable> ]
@@ -38,6 +39,7 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
| <replaceable>table_constraint</replaceable> }
[, ... ]
) ]
+[ PARTITION BY {RANGE | LIST} ( { <replaceable class="parameter">column_name</replaceable> | ( <replaceable class="parameter">expression</replaceable> ) } [ <replaceable class="parameter">opclass</replaceable> ] [, ...] )
[ WITH ( <replaceable class="PARAMETER">storage_parameter</replaceable> [= <replaceable class="PARAMETER">value</replaceable>] [, ... ] ) | WITH OIDS | WITHOUT OIDS ]
[ ON COMMIT { PRESERVE ROWS | DELETE ROWS | DROP } ]
[ TABLESPACE <replaceable class="PARAMETER">tablespace_name</replaceable> ]
@@ -314,6 +316,39 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
</varlistentry>
<varlistentry>
+ <term><literal>PARTITION BY {RANGE | LIST} ( { <replaceable class="parameter">column_name</replaceable> | ( <replaceable class="parameter">expression</replaceable> ) } [ <replaceable class="parameter">opclass</replaceable> ] [, ...] ) </literal></term>
+ <listitem>
+ <para>
+ The optional <literal>PARTITION BY</> clause specifies a method of
+ partitioning the table and the corresponding partition key. Table
+ thus created is called <firstterm>partitioned</firstterm> table. Key
+ consists of an ordered list of column names and/or expressions when
+ using the <literal>RANGE</> method, whereas only a single column or
+ expression can be specified when using the <literal>LIST</> method.
+ The type of a key column or an expresion must have an associated
+ btree operator class or one must be specified along with the column
+ or the expression.
+ </para>
+
+ <para>
+ A partitioned table is divided into sub-tables (called partitions), which
+ in turn, are created using separate commands. The table itself is empty
+ and does not have any storage files. A data row inserted into the table
+ is mapped to and stored in one of its partitions (if one exists) based
+ on the values of columns and/or expressions in the partition key and
+ partition rules associated with the partitions.
+ </para>
+
+ <para>
+ Currently, there are following limitations on definition of partitioned
+ tables: one cannot specify any UNIQUE, PRIMARY KEY, EXCLUDE and/or
+ FOREIGN KEY constraints.
+ </para>
+
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><literal>LIKE <replaceable>source_table</replaceable> [ <replaceable>like_option</replaceable> ... ]</literal></term>
<listitem>
<para>
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 13b04e6..fd1d7b2 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -67,8 +67,6 @@ static void ComputeIndexAttrs(IndexInfo *indexInfo,
char *accessMethodName, Oid accessMethodId,
bool amcanorder,
bool isconstraint);
-static Oid GetIndexOpClass(List *opclass, Oid attrType,
- char *accessMethodName, Oid accessMethodId);
static char *ChooseIndexName(const char *tabname, Oid namespaceId,
List *colnames, List *exclusionOpNames,
bool primary, bool isconstraint);
@@ -1219,7 +1217,7 @@ ComputeIndexAttrs(IndexInfo *indexInfo,
/*
* Resolve possibly-defaulted operator class specification
*/
-static Oid
+Oid
GetIndexOpClass(List *opclass, Oid attrType,
char *accessMethodName, Oid accessMethodId)
{
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index eaf76d2..17f4807 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -433,6 +433,11 @@ static void RangeVarCallbackForDropRelation(const RangeVar *rel, Oid relOid,
Oid oldRelOid, void *arg);
static void RangeVarCallbackForAlterRelation(const RangeVar *rv, Oid relid,
Oid oldrelid, void *arg);
+static PartitionBy *transformPartitionBy(Relation rel, PartitionBy *partitionby);
+static void ComputePartitionAttrs(Oid relid, List *partParams,
+ AttrNumber *partattrs,
+ List **partexprs,
+ Oid *partoplass);
/* ----------------------------------------------------------------
@@ -710,6 +715,21 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
AddRelationNewConstraints(rel, rawDefaults, stmt->constraints,
true, true, false);
+ /* Process partition key, if any */
+ if (stmt->partby)
+ {
+ PartitionBy *partby;
+ int16 partnatts;
+ AttrNumber partattrs[PARTITION_MAX_KEYS];
+ Oid partopclass[PARTITION_MAX_KEYS];
+ List *partexprs = NIL;
+
+ partnatts = list_length(stmt->partby->partParams);
+ partby = transformPartitionBy(rel, stmt->partby);
+ ComputePartitionAttrs(relationId, stmt->partby->partParams,
+ partattrs, &partexprs, partopclass);
+ }
+
ObjectAddressSet(address, RelationRelationId, relationId);
/*
@@ -12051,3 +12071,198 @@ RangeVarCallbackForAlterRelation(const RangeVar *rv, Oid relid, Oid oldrelid,
ReleaseSysCache(tuple);
}
+
+/*
+ * transformPartitionBy
+ * Transform any expressions present in the partition key
+ */
+static PartitionBy *
+transformPartitionBy(Relation rel, PartitionBy *partitionby)
+{
+ PartitionBy *partby;
+ ParseState *pstate;
+ RangeTblEntry *rte;
+ ListCell *l;
+
+ partby = (PartitionBy *) makeNode(PartitionBy);
+
+ partby->strategy = partitionby->strategy;
+ partby->location = partitionby->location;
+ partby->partParams = NIL;
+
+ /*
+ * Create a dummy ParseState and insert the target relation as its sole
+ * rangetable entry. We need a ParseState for transformExpr.
+ */
+ pstate = make_parsestate(NULL);
+ rte = addRangeTableEntryForRelation(pstate, rel, NULL, false, true);
+ addRTEtoQuery(pstate, rte, true, true, true);
+
+ /* take care of any partition expressions */
+ foreach(l, partitionby->partParams)
+ {
+ ListCell *column;
+ PartitionElem *pelem = (PartitionElem *) lfirst(l);
+
+ /* Check for PARTITION BY ... ON (foo, foo) */
+ foreach(column, partby->partParams)
+ {
+ PartitionElem *pparam = (PartitionElem *) lfirst(column);
+
+ if (pelem->name && pparam->name &&
+ !strcmp(pelem->name, pparam->name))
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_COLUMN),
+ errmsg("column \"%s\" appears twice in partition key", pelem->name),
+ parser_errposition(pstate, pelem->location)));
+ }
+
+ if (pelem->expr)
+ {
+ /* Now do parse transformation of the expression */
+ pelem->expr = transformExpr(pstate, pelem->expr,
+ EXPR_KIND_PARTITION_KEY);
+
+ /* we have to fix its collations too */
+ assign_expr_collations(pstate, pelem->expr);
+
+ /*
+ * transformExpr() should have already rejected subqueries,
+ * aggregates, and window functions, based on the EXPR_KIND_ for
+ * a partition key expression.
+ *
+ * Also reject expressions returning sets; this is for consistency
+ * DefineRelation() will make more checks.
+ */
+ if (expression_returns_set(pelem->expr))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("partition key expression cannot return a set"),
+ parser_errposition(pstate, pelem->location)));
+ }
+
+ partby->partParams = lappend(partby->partParams, pelem);
+ }
+
+ return partby;
+}
+
+/*
+ * ComputePartitionAttrs
+ * Compute per-partition-column information from partParams
+ */
+static void
+ComputePartitionAttrs(Oid relid, List *partParams, AttrNumber *partattrs,
+ List **partexprs, Oid *partopclass)
+{
+ int attn;
+ ListCell *lc;
+
+ attn = 0;
+ foreach(lc, partParams)
+ {
+ PartitionElem *pelem = (PartitionElem *) lfirst(lc);
+ Oid atttype;
+ Oid opclassOid;
+
+ if (pelem->name != NULL)
+ {
+ HeapTuple atttuple;
+ Form_pg_attribute attform;
+
+ atttuple = SearchSysCacheAttName(relid, pelem->name);
+ if (!HeapTupleIsValid(atttuple))
+ {
+ /* difference in error message spellings is historical */
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_COLUMN),
+ errmsg("column \"%s\" named in partition key does not exist",
+ pelem->name)));
+ }
+ attform = (Form_pg_attribute) GETSTRUCT(atttuple);
+
+ if (attform->attnum <= 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_COLUMN),
+ errmsg("cannot use system column \"%s\" in partition key",
+ pelem->name)));
+
+ partattrs[attn] = attform->attnum;
+ atttype = attform->atttypid;
+ ReleaseSysCache(atttuple);
+ }
+ else
+ {
+ /* Partition key expression */
+ Node *expr = pelem->expr;
+
+ Assert(expr != NULL);
+ atttype = exprType(expr);
+
+ if (IsA(expr, CollateExpr))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("cannot use COLLATE in partition key expression")));
+
+ if (IsA(expr, Var) &&
+ ((Var *) expr)->varattno != InvalidAttrNumber)
+ {
+ /*
+ * User wrote "(column)" or "(column COLLATE something)".
+ * Treat it like simple attribute anyway.
+ */
+ partattrs[attn] = ((Var *) expr)->varattno;
+ }
+ else
+ {
+ partattrs[attn] = 0; /* marks expression */
+ *partexprs = lappend(*partexprs, expr);
+
+ /*
+ * transformExpr() should have already rejected subqueries,
+ * aggregates, and window functions, based on the EXPR_KIND_
+ * for a partition key expression.
+ */
+
+ /*
+ * An expression using mutable functions is probably wrong even
+ * even to use in a partition key
+ */
+ expr = (Node *) expression_planner((Expr *) expr);
+
+ if (IsA(expr, Const))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("cannot use a constant expression as partition key")));
+
+ if (contain_mutable_functions(expr))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("functions in partition key expression must be marked IMMUTABLE")));
+ }
+ }
+
+ /*
+ * Identify the opclass to use. At the moment, we use "btree" operators
+ * that seems enough for list and range partitioning.
+ */
+ if (!pelem->opclass)
+ {
+ opclassOid = GetDefaultOpClass(atttype, BTREE_AM_OID);
+
+ if (!OidIsValid(opclassOid))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("data type %s has no default btree operator class",
+ format_type_be(atttype)),
+ errhint("You must specify an existing btree operator class or define one for the type.")));
+ }
+ else
+ opclassOid = GetIndexOpClass(pelem->opclass,
+ atttype,
+ "btree",
+ BTREE_AM_OID);
+
+ partopclass[attn++] = opclassOid;
+ }
+}
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index a21928b..5f4f66f 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2999,6 +2999,7 @@ CopyCreateStmtFields(const CreateStmt *from, CreateStmt *newnode)
COPY_NODE_FIELD(relation);
COPY_NODE_FIELD(tableElts);
COPY_NODE_FIELD(inhRelations);
+ COPY_NODE_FIELD(partby);
COPY_NODE_FIELD(ofTypename);
COPY_NODE_FIELD(constraints);
COPY_NODE_FIELD(options);
@@ -4154,6 +4155,32 @@ _copyAlterPolicyStmt(const AlterPolicyStmt *from)
return newnode;
}
+static PartitionBy *
+_copyPartitionBy(const PartitionBy *from)
+{
+
+ PartitionBy *newnode = makeNode(PartitionBy);
+
+ COPY_SCALAR_FIELD(strategy);
+ COPY_NODE_FIELD(partParams);
+ COPY_LOCATION_FIELD(location);
+
+ return newnode;
+}
+
+static PartitionElem *
+_copyPartitionElem(const PartitionElem *from)
+{
+ PartitionElem *newnode = makeNode(PartitionElem);
+
+ COPY_STRING_FIELD(name);
+ COPY_NODE_FIELD(expr);
+ COPY_NODE_FIELD(opclass);
+ COPY_LOCATION_FIELD(location);
+
+ return newnode;
+}
+
/* ****************************************************************
* pg_list.h copy functions
* ****************************************************************
@@ -5047,6 +5074,12 @@ copyObject(const void *from)
case T_RoleSpec:
retval = _copyRoleSpec(from);
break;
+ case T_PartitionBy:
+ retval = _copyPartitionBy(from);
+ break;
+ case T_PartitionElem:
+ retval = _copyPartitionElem(from);
+ break;
default:
elog(ERROR, "unrecognized node type: %d", (int) nodeTag(from));
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 3c6c567..001a468 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1154,6 +1154,7 @@ _equalCreateStmt(const CreateStmt *a, const CreateStmt *b)
COMPARE_NODE_FIELD(relation);
COMPARE_NODE_FIELD(tableElts);
COMPARE_NODE_FIELD(inhRelations);
+ COMPARE_NODE_FIELD(partby);
COMPARE_NODE_FIELD(ofTypename);
COMPARE_NODE_FIELD(constraints);
COMPARE_NODE_FIELD(options);
@@ -2617,6 +2618,27 @@ _equalRoleSpec(const RoleSpec *a, const RoleSpec *b)
return true;
}
+static bool
+_equalPartitionBy(const PartitionBy *a, const PartitionBy *b)
+{
+ COMPARE_SCALAR_FIELD(strategy);
+ COMPARE_NODE_FIELD(partParams);
+ COMPARE_LOCATION_FIELD(location);
+
+ return true;
+}
+
+static bool
+_equalPartitionElem(const PartitionElem *a, const PartitionElem *b)
+{
+ COMPARE_STRING_FIELD(name);
+ COMPARE_NODE_FIELD(expr);
+ COMPARE_NODE_FIELD(opclass);
+ COMPARE_LOCATION_FIELD(location);
+
+ return true;
+}
+
/*
* Stuff from pg_list.h
*/
@@ -3367,6 +3389,12 @@ equal(const void *a, const void *b)
case T_RoleSpec:
retval = _equalRoleSpec(a, b);
break;
+ case T_PartitionBy:
+ retval = _equalPartitionBy(a, b);
+ break;
+ case T_PartitionElem:
+ retval = _equalPartitionElem(a, b);
+ break;
default:
elog(ERROR, "unrecognized node type: %d",
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 5ac7446..4e70a69 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2367,6 +2367,7 @@ _outCreateStmtInfo(StringInfo str, const CreateStmt *node)
WRITE_NODE_FIELD(relation);
WRITE_NODE_FIELD(tableElts);
WRITE_NODE_FIELD(inhRelations);
+ WRITE_NODE_FIELD(partby);
WRITE_NODE_FIELD(ofTypename);
WRITE_NODE_FIELD(constraints);
WRITE_NODE_FIELD(options);
@@ -3215,6 +3216,26 @@ _outConstraint(StringInfo str, const Constraint *node)
}
}
+static void
+_outPartitionBy(StringInfo str, const PartitionBy *node)
+{
+ WRITE_NODE_TYPE("PARTITIONBY");
+
+ WRITE_CHAR_FIELD(strategy);
+ WRITE_NODE_FIELD(partParams);
+ WRITE_LOCATION_FIELD(location);
+}
+
+static void
+_outPartitionElem(StringInfo str, const PartitionElem *node)
+{
+ WRITE_NODE_TYPE("PARTITIONELEM");
+
+ WRITE_STRING_FIELD(name);
+ WRITE_NODE_FIELD(expr);
+ WRITE_NODE_FIELD(opclass);
+ WRITE_LOCATION_FIELD(location);
+}
/*
* outNode -
@@ -3794,6 +3815,12 @@ outNode(StringInfo str, const void *obj)
case T_XmlSerialize:
_outXmlSerialize(str, obj);
break;
+ case T_PartitionBy:
+ _outPartitionBy(str, obj);
+ break;
+ case T_PartitionElem:
+ _outPartitionElem(str, obj);
+ break;
default:
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 18ec5f0..0607439 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -227,6 +227,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
struct ImportQual *importqual;
InsertStmt *istmt;
VariableSetStmt *vsetstmt;
+ PartitionElem *partelem;
+ PartitionBy *partby;
}
%type <node> stmt schema_stmt
@@ -539,6 +541,9 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
opt_frame_clause frame_extent frame_bound
%type <str> opt_existing_window_name
%type <boolean> opt_if_not_exists
+%type <partby> PartitionBy OptPartitionBy
+%type <partelem> part_elem
+%type <list> part_params
/*
* Non-keyword token types. These are hard-wired into the "flex" lexer.
@@ -603,7 +608,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
KEY
LABEL LANGUAGE LARGE_P LAST_P LATERAL_P
- LEADING LEAKPROOF LEAST LEFT LEVEL LIKE LIMIT LISTEN LOAD LOCAL
+ LEADING LEAKPROOF LEAST LEFT LEVEL LIKE LIMIT LIST LISTEN LOAD LOCAL
LOCALTIME LOCALTIMESTAMP LOCATION LOCK_P LOCKED LOGGED
MAPPING MATCH MATERIALIZED MAXVALUE METHOD MINUTE_P MINVALUE MODE MONTH_P MOVE
@@ -2808,69 +2813,75 @@ copy_generic_opt_arg_list_item:
*****************************************************************************/
CreateStmt: CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
- OptInherit OptWith OnCommitOption OptTableSpace
+ OptInherit OptPartitionBy OptWith OnCommitOption OptTableSpace
{
CreateStmt *n = makeNode(CreateStmt);
$4->relpersistence = $2;
n->relation = $4;
n->tableElts = $6;
n->inhRelations = $8;
+ n->partby = $9;
n->ofTypename = NULL;
n->constraints = NIL;
- n->options = $9;
- n->oncommit = $10;
- n->tablespacename = $11;
+ n->options = $10;
+ n->oncommit = $11;
+ n->tablespacename = $12;
n->if_not_exists = false;
$$ = (Node *)n;
}
| CREATE OptTemp TABLE IF_P NOT EXISTS qualified_name '('
- OptTableElementList ')' OptInherit OptWith OnCommitOption
- OptTableSpace
+ OptTableElementList ')' OptInherit OptPartitionBy OptWith
+ OnCommitOption OptTableSpace
{
CreateStmt *n = makeNode(CreateStmt);
$7->relpersistence = $2;
n->relation = $7;
n->tableElts = $9;
n->inhRelations = $11;
+ n->partby = $12;
n->ofTypename = NULL;
n->constraints = NIL;
- n->options = $12;
- n->oncommit = $13;
- n->tablespacename = $14;
+ n->options = $13;
+ n->oncommit = $14;
+ n->tablespacename = $15;
n->if_not_exists = true;
$$ = (Node *)n;
}
| CREATE OptTemp TABLE qualified_name OF any_name
- OptTypedTableElementList OptWith OnCommitOption OptTableSpace
+ OptTypedTableElementList OptPartitionBy OptWith OnCommitOption
+ OptTableSpace
{
CreateStmt *n = makeNode(CreateStmt);
$4->relpersistence = $2;
n->relation = $4;
n->tableElts = $7;
n->inhRelations = NIL;
+ n->partby = $8;
n->ofTypename = makeTypeNameFromNameList($6);
n->ofTypename->location = @6;
n->constraints = NIL;
- n->options = $8;
- n->oncommit = $9;
- n->tablespacename = $10;
+ n->options = $9;
+ n->oncommit = $10;
+ n->tablespacename = $11;
n->if_not_exists = false;
$$ = (Node *)n;
}
| CREATE OptTemp TABLE IF_P NOT EXISTS qualified_name OF any_name
- OptTypedTableElementList OptWith OnCommitOption OptTableSpace
+ OptTypedTableElementList OptPartitionBy OptWith OnCommitOption
+ OptTableSpace
{
CreateStmt *n = makeNode(CreateStmt);
$7->relpersistence = $2;
n->relation = $7;
n->tableElts = $10;
n->inhRelations = NIL;
+ n->partby = $11;
n->ofTypename = makeTypeNameFromNameList($9);
n->ofTypename->location = @9;
n->constraints = NIL;
- n->options = $11;
- n->oncommit = $12;
- n->tablespacename = $13;
+ n->options = $12;
+ n->oncommit = $13;
+ n->tablespacename = $14;
n->if_not_exists = true;
$$ = (Node *)n;
}
@@ -3415,6 +3426,68 @@ OptInherit: INHERITS '(' qualified_name_list ')' { $$ = $3; }
| /*EMPTY*/ { $$ = NIL; }
;
+/* Optional partition key definition */
+OptPartitionBy: PartitionBy { $$ = $1; }
+ | /*EMPTY*/ { $$ = NULL; }
+ ;
+
+PartitionBy: PARTITION BY RANGE '(' part_params ')'
+ {
+ PartitionBy *n = makeNode(PartitionBy);
+
+ n->strategy = PARTITION_STRAT_RANGE;
+ n->partParams = $5;
+ n->location = @1;
+
+ $$ = n;
+ }
+ | PARTITION BY LIST '(' part_params ')'
+ {
+ PartitionBy *n = makeNode(PartitionBy);
+
+ n->strategy = PARTITION_STRAT_LIST;
+ n->partParams = $5;
+ n->location = @1;
+
+ $$ = n;
+ }
+ ;
+
+part_params: part_elem { $$ = list_make1($1); }
+ | part_params ',' part_elem { $$ = lappend($1, $3); }
+ ;
+
+part_elem: ColId opt_class
+ {
+ PartitionElem *n = makeNode(PartitionElem);
+
+ n->name = $1;
+ n->expr = NULL;
+ n->opclass = $2;
+ n->location = @1;
+ $$ = n;
+ }
+ | func_expr_windowless opt_class
+ {
+ PartitionElem *n = makeNode(PartitionElem);
+
+ n->name = NULL;
+ n->expr = $1;
+ n->opclass = $2;
+ n->location = @1;
+ $$ = n;
+ }
+ | '(' a_expr ')' opt_class
+ {
+ PartitionElem *n = makeNode(PartitionElem);
+
+ n->name = NULL;
+ n->expr = $2;
+ n->opclass = $4;
+ n->location = @1;
+ $$ = n;
+ }
+ ;
/* WITH (options) is preferred, WITH OIDS and WITHOUT OIDS are legacy forms */
OptWith:
WITH reloptions { $$ = $2; }
@@ -13839,6 +13912,7 @@ unreserved_keyword:
| LAST_P
| LEAKPROOF
| LEVEL
+ | LIST
| LISTEN
| LOAD
| LOCAL
diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c
index 91bfe66..b56fd4f 100644
--- a/src/backend/parser/parse_agg.c
+++ b/src/backend/parser/parse_agg.c
@@ -486,6 +486,14 @@ check_agglevels_and_constraints(ParseState *pstate, Node *expr)
err = _("grouping operations are not allowed in trigger WHEN conditions");
break;
+ case EXPR_KIND_PARTITION_KEY:
+ if (isAgg)
+ err = _("aggregate functions are not allowed in partition key expressions");
+ else
+ err = _("grouping operations are not allowed in partition key expressions");
+
+ break;
+
/*
* There is intentionally no default: case here, so that the
@@ -843,6 +851,9 @@ transformWindowFuncCall(ParseState *pstate, WindowFunc *wfunc,
case EXPR_KIND_TRIGGER_WHEN:
err = _("window functions are not allowed in trigger WHEN conditions");
break;
+ case EXPR_KIND_PARTITION_KEY:
+ err = _("window functions are not allowed in partition key expressions");
+ break;
/*
* There is intentionally no default: case here, so that the
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index ebda55d..70a663c 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -1715,6 +1715,9 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
case EXPR_KIND_TRIGGER_WHEN:
err = _("cannot use subquery in trigger WHEN condition");
break;
+ case EXPR_KIND_PARTITION_KEY:
+ err = _("cannot use subquery in partition key expressions");
+ break;
/*
* There is intentionally no default: case here, so that the
@@ -3225,6 +3228,8 @@ ParseExprKindName(ParseExprKind exprKind)
return "EXECUTE";
case EXPR_KIND_TRIGGER_WHEN:
return "WHEN";
+ case EXPR_KIND_PARTITION_KEY:
+ return "partition key expression";
/*
* There is intentionally no default: case here, so that the
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 6528494..e267f74 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -87,6 +87,7 @@ typedef struct
List *alist; /* "after list" of things to do after creating
* the table */
IndexStmt *pkey; /* PRIMARY KEY index, if any */
+ bool ispartitioned; /* true if table is partitioned */
} CreateStmtContext;
/* State shared by transformCreateSchemaStmt and its subroutines */
@@ -229,6 +230,7 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
cxt.blist = NIL;
cxt.alist = NIL;
cxt.pkey = NULL;
+ cxt.ispartitioned = stmt->partby != NULL;
/*
* Notice that we allow OIDs here only for plain tables, even though
@@ -247,6 +249,24 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
if (stmt->ofTypename)
transformOfType(&cxt, stmt->ofTypename);
+ if (stmt->partby)
+ {
+ int partnatts = list_length(stmt->partby->partParams);
+
+ if (partnatts > PARTITION_MAX_KEYS)
+ ereport(ERROR,
+ (errcode(ERRCODE_TOO_MANY_COLUMNS),
+ errmsg("cannot use more than %d columns in partition key",
+ PARTITION_MAX_KEYS)));
+
+ if (stmt->partby->strategy == PARTITION_STRAT_LIST &&
+ partnatts > 1)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("cannot use more than one column in partition key"),
+ errdetail("Only one column allowed with list partitioning.")));
+ }
+
/*
* Run through each primary element in the table creation clause. Separate
* column defs from constraints, and do preliminary analysis. We have to
@@ -582,6 +602,12 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
errmsg("primary key constraints are not supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
+ if (cxt->ispartitioned)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("primary key constraints are not supported on partitioned tables"),
+ parser_errposition(cxt->pstate,
+ constraint->location)));
/* FALL THRU */
case CONSTR_UNIQUE:
@@ -591,6 +617,12 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
errmsg("unique constraints are not supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
+ if (cxt->ispartitioned)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("unique constraints are not supported on partitioned tables"),
+ parser_errposition(cxt->pstate,
+ constraint->location)));
if (constraint->keys == NIL)
constraint->keys = list_make1(makeString(column->colname));
cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
@@ -608,6 +640,12 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
errmsg("foreign key constraints are not supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
+ if (cxt->ispartitioned)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("foreign key constraints are not supported on partitioned tables"),
+ parser_errposition(cxt->pstate,
+ constraint->location)));
/*
* Fill in the current attribute's name and throw it into the
@@ -673,6 +711,12 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
errmsg("primary key constraints are not supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
+ if (cxt->ispartitioned)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("primary key constraints are not supported on partitioned tables"),
+ parser_errposition(cxt->pstate,
+ constraint->location)));
cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
break;
@@ -683,6 +727,12 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
errmsg("unique constraints are not supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
+ if (cxt->ispartitioned)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("unique constraints are not supported on partitioned tables"),
+ parser_errposition(cxt->pstate,
+ constraint->location)));
cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
break;
@@ -693,6 +743,12 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
errmsg("exclusion constraints are not supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
+ if (cxt->ispartitioned)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("exclusion constraints are not supported on partitioned tables"),
+ parser_errposition(cxt->pstate,
+ constraint->location)));
cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
break;
@@ -707,6 +763,12 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
errmsg("foreign key constraints are not supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
+ if (cxt->ispartitioned)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("foreign key constraints are not supported on partitioned tables"),
+ parser_errposition(cxt->pstate,
+ constraint->location)));
cxt->fkconstraints = lappend(cxt->fkconstraints, constraint);
break;
@@ -2515,6 +2577,7 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
cxt.blist = NIL;
cxt.alist = NIL;
cxt.pkey = NULL;
+ cxt.ispartitioned = false;
/*
* The only subtypes that currently require parse transformation handling
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index b064eb4..28024f8 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -42,6 +42,8 @@ extern bool CheckIndexCompatible(Oid oldId,
List *attributeList,
List *exclusionOpNames);
extern Oid GetDefaultOpClass(Oid type_id, Oid am_id);
+extern Oid GetIndexOpClass(List *opclass, Oid attrType,
+ char *accessMethodName, Oid accessMethodId);
/* commands/functioncmds.c */
extern ObjectAddress CreateFunction(CreateFunctionStmt *stmt, const char *queryString);
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index ea65f2e..9e582a5 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -452,6 +452,8 @@ typedef enum NodeTag
T_OnConflictClause,
T_CommonTableExpr,
T_RoleSpec,
+ T_PartitionElem,
+ T_PartitionBy,
/*
* TAGS FOR REPLICATION GRAMMAR PARSE NODES (replnodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 714cf15..4f697b8 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -696,6 +696,40 @@ typedef struct XmlSerialize
int location; /* token location, or -1 if unknown */
} XmlSerialize;
+/* Partitioning related definitions */
+
+/*
+ * PartitionElem - a partition key column
+ *
+ * 'name' Name of the table column included in the key
+ * 'expr' Expression node tree of expressional key column
+ * 'opclass' Operator class name associated with the column
+ */
+typedef struct PartitionElem
+{
+ NodeTag type;
+ char *name; /* name of column to partition on, or NULL */
+ Node *expr; /* expression to partition on, or NULL */
+ List *opclass; /* name of desired opclass; NIL = default */
+ int location; /* token location, or -1 if unknown */
+} PartitionElem;
+
+/*
+ * PartitionBy - partition key definition including the strategy
+ *
+ * 'strategy' partition strategy to use (one of the below defined)
+ * 'partParams' List of PartitionElems, one for each key column
+ */
+#define PARTITION_STRAT_LIST 'l'
+#define PARTITION_STRAT_RANGE 'r'
+
+typedef struct PartitionBy
+{
+ NodeTag type;
+ char strategy;
+ List *partParams;
+ int location; /* token location, or -1 if unknown */
+} PartitionBy;
/****************************************************************************
* Nodes for a Query tree
@@ -1750,6 +1784,7 @@ typedef struct CreateStmt
List *tableElts; /* column definitions (list of ColumnDef) */
List *inhRelations; /* relations to inherit from (list of
* inhRelation) */
+ PartitionBy *partby; /* PARTITION BY clause */
TypeName *ofTypename; /* OF typename */
List *constraints; /* constraints (list of Constraint nodes) */
List *options; /* options from WITH clause */
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 17ffef5..40da67a 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -227,6 +227,7 @@ PG_KEYWORD("left", LEFT, TYPE_FUNC_NAME_KEYWORD)
PG_KEYWORD("level", LEVEL, UNRESERVED_KEYWORD)
PG_KEYWORD("like", LIKE, TYPE_FUNC_NAME_KEYWORD)
PG_KEYWORD("limit", LIMIT, RESERVED_KEYWORD)
+PG_KEYWORD("list", LIST, UNRESERVED_KEYWORD)
PG_KEYWORD("listen", LISTEN, UNRESERVED_KEYWORD)
PG_KEYWORD("load", LOAD, UNRESERVED_KEYWORD)
PG_KEYWORD("local", LOCAL, UNRESERVED_KEYWORD)
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index e3e359c..a13c6fb 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -64,7 +64,8 @@ typedef enum ParseExprKind
EXPR_KIND_ALTER_COL_TRANSFORM, /* transform expr in ALTER COLUMN TYPE */
EXPR_KIND_EXECUTE_PARAMETER, /* parameter value in EXECUTE */
EXPR_KIND_TRIGGER_WHEN, /* WHEN condition in CREATE TRIGGER */
- EXPR_KIND_POLICY /* USING or WITH CHECK expr in policy */
+ EXPR_KIND_POLICY, /* USING or WITH CHECK expr in policy */
+ EXPR_KIND_PARTITION_KEY /* partition key expression */
} ParseExprKind;
diff --git a/src/include/pg_config_manual.h b/src/include/pg_config_manual.h
index ef89521..210aa4e 100644
--- a/src/include/pg_config_manual.h
+++ b/src/include/pg_config_manual.h
@@ -46,6 +46,11 @@
#define INDEX_MAX_KEYS 32
/*
+ * Maximum number of columns in a partition key
+ */
+#define PARTITION_MAX_KEYS 32
+
+/*
* Set the upper and lower bounds of sequence values.
*/
#define SEQ_MAXVALUE PG_INT64_MAX
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 41ceb87..aaae9f9 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -253,3 +253,82 @@ DROP TABLE as_select1;
-- check that the oid column is added before the primary key is checked
CREATE TABLE oid_pk (f1 INT, PRIMARY KEY(oid)) WITH OIDS;
DROP TABLE oid_pk;
+--
+-- partitioned tables
+--
+-- cannot use system columns in partition key
+CREATE TABLE fail_oid_pk (
+ a int
+) PARTITION BY RANGE (oid) WITH OIDS; -- fail
+ERROR: cannot use system column "oid" in partition key
+-- cannot use more than 1 column as partition key for list partitioned table
+CREATE TABLE two_col_tab (
+ a1 int,
+ a2 int
+) PARTITION BY LIST (a1, a2); -- fail
+ERROR: cannot use more than one column in partition key
+DETAIL: Only one column allowed with list partitioning.
+-- cannot use more than 32 columns as partition key for range partitioned table
+CREATE TABLE wide_tab (
+ a01 int, a02 int, a03 int, a04 int, a05 int, a06 int, a07 int, a08 int,
+ a09 int, a10 int, a11 int, a12 int, a13 int, a14 int, a15 int, a16 int,
+ a17 int, a18 int, a19 int, a20 int, a21 int, a22 int, a23 int, a24 int,
+ a25 int, a26 int, a27 int, a28 int, a29 int, a30 int, a31 int, a32 int,
+ a33 int
+) PARTITION BY RANGE (a01, a02, a03, a04, a05, a06, a07, a08,
+ a09, a10, a11, a12, a13, a14, a15, a16,
+ a17, a18, a19, a20, a21, a22, a23, a24,
+ a25, a26, a27, a28, a29, a30, a31, a32, a33); -- fail
+ERROR: cannot use more than 32 columns in partition key
+-- Partitioning requires to use columns with data type having a suitable
+-- btree operator class
+CREATE TABLE point_tab (
+ a point
+) PARTITION BY LIST (a); -- fail
+ERROR: data type point has no default btree operator class
+HINT: You must specify an existing btree operator class or define one for the type.
+CREATE TABLE point_tab (
+ a point
+) PARTITION BY LIST (a USING point_ops); -- fail
+ERROR: operator class "point_ops" does not exist for access method "btree"
+CREATE TABLE point_tab (
+ a point
+) PARTITION BY RANGE (a); -- fail
+ERROR: data type point has no default btree operator class
+HINT: You must specify an existing btree operator class or define one for the type.
+CREATE TABLE point_tab (
+ a point
+) PARTITION BY RANGE (a USING point_ops); -- fail
+ERROR: operator class "point_ops" does not exist for access method "btree"
+-- fail to create a partitioned table with PRIMARY KEY constraint
+CREATE TABLE pk_fail (
+ a int PRIMARY KEY
+) PARTITION BY RANGE (a);
+ERROR: primary key constraints are not supported on partitioned tables
+LINE 2: a int PRIMARY KEY
+ ^
+-- fail to create a partitioned table with foreign key constraint
+CREATE TABLE pk_rel(
+ a int PRIMARY KEY
+);
+CREATE TABLE fk_fail (
+ a int REFERENCES pk_rel(a)
+) PARTITION BY RANGE (a);
+ERROR: foreign key constraints are not supported on partitioned tables
+LINE 2: a int REFERENCES pk_rel(a)
+ ^
+DROP TABLE pk_rel;
+-- fail to create a partitioned table with prohibited expressions in key
+CREATE TABLE fail_const_key (
+ a int
+) PARTITION BY RANGE (('a'));
+ERROR: cannot use a constant expression as partition key
+CREATE TABLE fail_agg_in_key (
+ a int
+) PARTITION BY RANGE ((avg(a)));
+ERROR: aggregate functions are not allowed in partition key expressions
+CREATE TABLE fail_window_fun_in_key (
+ a int,
+ b int
+) PARTITION BY RANGE ((avg(a) OVER (PARTITION BY b)));
+ERROR: window functions are not allowed in partition key expressions
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 78bdc8b..df47656 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -269,3 +269,71 @@ DROP TABLE as_select1;
-- check that the oid column is added before the primary key is checked
CREATE TABLE oid_pk (f1 INT, PRIMARY KEY(oid)) WITH OIDS;
DROP TABLE oid_pk;
+
+--
+-- partitioned tables
+--
+
+-- cannot use system columns in partition key
+CREATE TABLE fail_oid_pk (
+ a int
+) PARTITION BY RANGE (oid) WITH OIDS; -- fail
+
+-- cannot use more than 1 column as partition key for list partitioned table
+CREATE TABLE two_col_tab (
+ a1 int,
+ a2 int
+) PARTITION BY LIST (a1, a2); -- fail
+
+-- cannot use more than 32 columns as partition key for range partitioned table
+CREATE TABLE wide_tab (
+ a01 int, a02 int, a03 int, a04 int, a05 int, a06 int, a07 int, a08 int,
+ a09 int, a10 int, a11 int, a12 int, a13 int, a14 int, a15 int, a16 int,
+ a17 int, a18 int, a19 int, a20 int, a21 int, a22 int, a23 int, a24 int,
+ a25 int, a26 int, a27 int, a28 int, a29 int, a30 int, a31 int, a32 int,
+ a33 int
+) PARTITION BY RANGE (a01, a02, a03, a04, a05, a06, a07, a08,
+ a09, a10, a11, a12, a13, a14, a15, a16,
+ a17, a18, a19, a20, a21, a22, a23, a24,
+ a25, a26, a27, a28, a29, a30, a31, a32, a33); -- fail
+
+-- Partitioning requires to use columns with data type having a suitable
+-- btree operator class
+CREATE TABLE point_tab (
+ a point
+) PARTITION BY LIST (a); -- fail
+CREATE TABLE point_tab (
+ a point
+) PARTITION BY LIST (a USING point_ops); -- fail
+CREATE TABLE point_tab (
+ a point
+) PARTITION BY RANGE (a); -- fail
+CREATE TABLE point_tab (
+ a point
+) PARTITION BY RANGE (a USING point_ops); -- fail
+
+-- fail to create a partitioned table with PRIMARY KEY constraint
+CREATE TABLE pk_fail (
+ a int PRIMARY KEY
+) PARTITION BY RANGE (a);
+
+-- fail to create a partitioned table with foreign key constraint
+CREATE TABLE pk_rel(
+ a int PRIMARY KEY
+);
+CREATE TABLE fk_fail (
+ a int REFERENCES pk_rel(a)
+) PARTITION BY RANGE (a);
+DROP TABLE pk_rel;
+
+-- fail to create a partitioned table with prohibited expressions in key
+CREATE TABLE fail_const_key (
+ a int
+) PARTITION BY RANGE (('a'));
+CREATE TABLE fail_agg_in_key (
+ a int
+) PARTITION BY RANGE ((avg(a)));
+CREATE TABLE fail_window_fun_in_key (
+ a int,
+ b int
+) PARTITION BY RANGE ((avg(a) OVER (PARTITION BY b)));
--
1.7.1
[text/x-diff] 0002-Infrastructure-for-creation-of-partitioned-tables-v1.patch (85.9K, ../../57106F9E.3000309@lab.ntt.co.jp/3-0002-Infrastructure-for-creation-of-partitioned-tables-v1.patch)
download | inline diff:
From 625b46704bd287700eae2755753f489a45744610 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Mon, 22 Feb 2016 17:38:55 +0900
Subject: [PATCH 2/5] Infrastructure for creation of partitioned tables.
First, this adds a new system catalog pg_partitioned with:
partedrelid oid
partstrat char
partnatts int16
partattrs int2vector
partclass oidvector
partexprs pg_node_tree
There is a unique index on partedrelid and associated syscache PARTEDRELID.
Second, a new relkind RELKIND_PARTITIONED_REL is added to pg_class. Tables
created with CREATE TABLE PARTITION BY are of this relkind. Some special
properties:
* Has a pg_partitioned entry
* Access to them unconditionally overrides specified inheritance option
* Dropping or altering type of columns that are part of the partition key
is disallowed
* Changing inheritance (ALTER TABLE table INHERIT parent) and linking to
a type (ALTER TABLE table OF type) is disallowed
Partition key is represented using struct PartitionKey and built during the
first relcache build of a partitioned relation. It is stored in rd_partkey
using the cache context.
---
doc/src/sgml/catalogs.sgml | 99 ++++++
src/backend/access/common/reloptions.c | 2 +
src/backend/catalog/Makefile | 4 +-
src/backend/catalog/aclchk.c | 2 +
src/backend/catalog/heap.c | 19 +-
src/backend/catalog/objectaddress.c | 10 +-
src/backend/catalog/partition.c | 451 ++++++++++++++++++++++++++++
src/backend/commands/copy.c | 6 +
src/backend/commands/indexcmds.c | 3 +-
src/backend/commands/lockcmds.c | 2 +-
src/backend/commands/policy.c | 2 +-
src/backend/commands/seclabel.c | 1 +
src/backend/commands/sequence.c | 1 +
src/backend/commands/tablecmds.c | 168 ++++++++++-
src/backend/commands/trigger.c | 7 +-
src/backend/commands/vacuum.c | 11 +-
src/backend/executor/execMain.c | 1 +
src/backend/executor/nodeModifyTable.c | 1 +
src/backend/parser/parse_agg.c | 6 +-
src/backend/parser/parse_clause.c | 8 +
src/backend/parser/parse_expr.c | 2 +-
src/backend/parser/parse_relation.c | 6 +-
src/backend/parser/parse_utilcmd.c | 4 +-
src/backend/rewrite/rewriteDefine.c | 1 +
src/backend/rewrite/rewriteHandler.c | 1 +
src/backend/tcop/utility.c | 5 +-
src/backend/utils/adt/ruleutils.c | 147 +++++++++
src/backend/utils/cache/relcache.c | 14 +-
src/backend/utils/cache/syscache.c | 12 +
src/bin/pg_dump/pg_dump.c | 36 ++-
src/bin/pg_dump/pg_dump.h | 1 +
src/bin/psql/describe.c | 61 +++-
src/bin/psql/tab-complete.c | 6 +-
src/include/catalog/indexing.h | 3 +
src/include/catalog/partition.h | 55 ++++
src/include/catalog/pg_class.h | 1 +
src/include/catalog/pg_partitioned.h | 67 ++++
src/include/catalog/pg_proc.h | 2 +
src/include/commands/defrem.h | 4 +-
src/include/utils/builtins.h | 1 +
src/include/utils/rel.h | 2 +
src/include/utils/syscache.h | 1 +
src/test/regress/expected/create_table.out | 30 ++-
src/test/regress/expected/sanity_check.out | 1 +
src/test/regress/sql/create_table.sql | 21 ++
45 files changed, 1227 insertions(+), 61 deletions(-)
create mode 100644 src/backend/catalog/partition.c
create mode 100644 src/include/catalog/partition.h
create mode 100644 src/include/catalog/pg_partitioned.h
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index d6b60db..ba658b1 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -224,6 +224,11 @@
</row>
<row>
+ <entry><link linkend="catalog-pg-partitioned"><structname>pg_partitioned</structname></link></entry>
+ <entry>information about partitioned tables, including the partition key</entry>
+ </row>
+
+ <row>
<entry><link linkend="catalog-pg-policy"><structname>pg_policy</structname></link></entry>
<entry>row-security policies</entry>
</row>
@@ -4682,6 +4687,100 @@
</sect1>
+ <sect1 id="catalog-pg-partitioned">
+ <title><structname>pg_partitioned</structname></title>
+
+ <indexterm zone="catalog-pg-partitioned">
+ <primary>pg_partitioned</primary>
+ </indexterm>
+
+ <para>
+ The catalog <structname>pg_partitioned</structname> stores information
+ about the partition key of partitioned tables.
+ </para>
+
+ <table>
+ <title><structname>pg_partitioned</> Columns</title>
+
+ <tgroup cols="4">
+ <thead>
+ <row>
+ <entry>Name</entry>
+ <entry>Type</entry>
+ <entry>References</entry>
+ <entry>Description</entry>
+ </row>
+ </thead>
+
+ <tbody>
+
+ <row>
+ <entry><structfield>partedrelid</structfield></entry>
+ <entry><type>oid</type></entry>
+ <entry><literal><link linkend="catalog-pg-class"><structname>pg_class</structname></link>.oid</literal></entry>
+ <entry>The OID of the <structname>pg_class</> entry for this partitioned table</entry>
+ </row>
+
+ <row>
+ <entry><structfield>partstrat</structfield></entry>
+ <entry><type>char</type></entry>
+ <entry></entry>
+ <entry>
+ Partitioning strategy (or method); <literal>l</> = list partitioned table,
+ <literal>r</> = range partitioned table
+ </entry>
+ </row>
+
+ <row>
+ <entry><structfield>partnatts</structfield></entry>
+ <entry><type>int2</type></entry>
+ <entry></entry>
+ <entry>The number of columns in partition key</entry>
+ </row>
+
+ <row>
+ <entry><structfield>partattrs</structfield></entry>
+ <entry><type>int2vector</type></entry>
+ <entry><literal><link linkend="catalog-pg-attribute"><structname>pg_attribute</structname></link>.attnum</literal></entry>
+ <entry>
+ This is an array of <structfield>partnatts</structfield> values that
+ indicate which table columns are used as partition key. For example,
+ a value of <literal>1 3</literal> would mean that the first and the
+ third table columns make up the partition key. A zero in this array
+ indicates that the corresponding partition key column is an expression
+ over the table columns, rather than a simple column reference.
+ </entry>
+ </row>
+
+ <row>
+ <entry><structfield>partclass</structfield></entry>
+ <entry><type>oidvector</type></entry>
+ <entry><literal><link linkend="catalog-pg-opclass"><structname>pg_opclass</structname></link>.oid</literal></entry>
+ <entry>
+ For each column in the partition key, this contains the OID of
+ the operator class to use. See
+ <link linkend="catalog-pg-opclass"><structname>pg_opclass</structname></link> for details.
+ </entry>
+ </row>
+
+ <row>
+ <entry><structfield>partexprs</structfield></entry>
+ <entry><type>pg_node_tree</type></entry>
+ <entry></entry>
+ <entry>
+ Expression trees (in <function>nodeToString()</function>
+ representation) for partition key columns that are not simple column
+ references. This is a list with one element for each zero
+ entry in <structfield>partkey</>. Null if all partition key columns
+ are simple references.
+ </entry>
+ </row>
+
+ </tbody>
+ </tgroup>
+ </table>
+ </sect1>
+
<sect1 id="catalog-pg-policy">
<title><structname>pg_policy</structname></title>
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 797be63..bfccf9b 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -925,6 +925,7 @@ extractRelOptions(HeapTuple tuple, TupleDesc tupdesc,
case RELKIND_RELATION:
case RELKIND_TOASTVALUE:
case RELKIND_MATVIEW:
+ case RELKIND_PARTITIONED_REL:
options = heap_reloptions(classForm->relkind, datum, false);
break;
case RELKIND_VIEW:
@@ -1376,6 +1377,7 @@ heap_reloptions(char relkind, Datum reloptions, bool validate)
return (bytea *) rdopts;
case RELKIND_RELATION:
case RELKIND_MATVIEW:
+ case RELKIND_PARTITIONED_REL:
return default_reloptions(reloptions, validate, RELOPT_KIND_HEAP);
default:
/* other relkinds are not supported */
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index 1ce7610..4dc6300 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -11,7 +11,7 @@ top_builddir = ../../..
include $(top_builddir)/src/Makefile.global
OBJS = catalog.o dependency.o heap.o index.o indexing.o namespace.o aclchk.o \
- objectaccess.o objectaddress.o pg_aggregate.o pg_collation.o \
+ objectaccess.o objectaddress.o partition.o pg_aggregate.o pg_collation.o \
pg_constraint.o pg_conversion.o \
pg_depend.o pg_enum.o pg_inherits.o pg_largeobject.o pg_namespace.o \
pg_operator.o pg_proc.o pg_range.o pg_db_role_setting.o pg_shdepend.o \
@@ -41,7 +41,7 @@ POSTGRES_BKI_SRCS = $(addprefix $(top_srcdir)/src/include/catalog/,\
pg_foreign_data_wrapper.h pg_foreign_server.h pg_user_mapping.h \
pg_foreign_table.h pg_policy.h pg_replication_origin.h \
pg_default_acl.h pg_init_privs.h pg_seclabel.h pg_shseclabel.h \
- pg_collation.h pg_range.h pg_transform.h \
+ pg_collation.h pg_range.h pg_transform.h pg_partitioned.h\
toasting.h indexing.h \
)
diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
index 4b49bb6..c564207 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -765,6 +765,8 @@ objectsInSchemaToOids(GrantObjectType objtype, List *nspnames)
case ACL_OBJECT_RELATION:
objs = getRelationsInNamespace(namespaceId, RELKIND_RELATION);
objects = list_concat(objects, objs);
+ objs = getRelationsInNamespace(namespaceId, RELKIND_PARTITIONED_REL);
+ objects = list_concat(objects, objs);
objs = getRelationsInNamespace(namespaceId, RELKIND_VIEW);
objects = list_concat(objects, objs);
objs = getRelationsInNamespace(namespaceId, RELKIND_MATVIEW);
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index e997b57..932a48b 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -41,6 +41,7 @@
#include "catalog/heap.h"
#include "catalog/index.h"
#include "catalog/objectaccess.h"
+#include "catalog/partition.h"
#include "catalog/pg_attrdef.h"
#include "catalog/pg_collation.h"
#include "catalog/pg_constraint.h"
@@ -1101,9 +1102,10 @@ heap_create_with_catalog(const char *relname,
{
/* Use binary-upgrade override for pg_class.oid/relfilenode? */
if (IsBinaryUpgrade &&
- (relkind == RELKIND_RELATION || relkind == RELKIND_SEQUENCE ||
- relkind == RELKIND_VIEW || relkind == RELKIND_MATVIEW ||
- relkind == RELKIND_COMPOSITE_TYPE || relkind == RELKIND_FOREIGN_TABLE))
+ (relkind == RELKIND_RELATION || relkind == RELKIND_PARTITIONED_REL ||
+ relkind == RELKIND_SEQUENCE || relkind == RELKIND_VIEW ||
+ relkind == RELKIND_MATVIEW || relkind == RELKIND_COMPOSITE_TYPE ||
+ relkind == RELKIND_FOREIGN_TABLE))
{
if (!OidIsValid(binary_upgrade_next_heap_pg_class_oid))
ereport(ERROR,
@@ -1134,6 +1136,7 @@ heap_create_with_catalog(const char *relname,
switch (relkind)
{
case RELKIND_RELATION:
+ case RELKIND_PARTITIONED_REL:
case RELKIND_VIEW:
case RELKIND_MATVIEW:
case RELKIND_FOREIGN_TABLE:
@@ -1178,6 +1181,7 @@ heap_create_with_catalog(const char *relname,
* such is an implementation detail: toast tables, sequences and indexes.
*/
if (IsUnderPostmaster && (relkind == RELKIND_RELATION ||
+ relkind == RELKIND_PARTITIONED_REL ||
relkind == RELKIND_VIEW ||
relkind == RELKIND_MATVIEW ||
relkind == RELKIND_FOREIGN_TABLE ||
@@ -1353,7 +1357,8 @@ heap_create_with_catalog(const char *relname,
if (relpersistence == RELPERSISTENCE_UNLOGGED)
{
Assert(relkind == RELKIND_RELATION || relkind == RELKIND_MATVIEW ||
- relkind == RELKIND_TOASTVALUE);
+ relkind == RELKIND_TOASTVALUE || relkind == RELKIND_PARTITIONED_REL);
+
heap_create_init_fork(new_rel_desc);
}
@@ -1800,6 +1805,12 @@ heap_drop_with_catalog(Oid relid)
}
/*
+ * If a partitioned table, delete the pg_partitioned tuples.
+ */
+ if (rel->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+ RemovePartitionKeyByRelId(relid);
+
+ /*
* Schedule unlinking of the relation's physical files at commit.
*/
if (rel->rd_rel->relkind != RELKIND_VIEW &&
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index 1324461..215375e 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -1204,7 +1204,8 @@ get_relation_by_qualified_name(ObjectType objtype, List *objname,
RelationGetRelationName(relation))));
break;
case OBJECT_TABLE:
- if (relation->rd_rel->relkind != RELKIND_RELATION)
+ if (relation->rd_rel->relkind != RELKIND_RELATION &&
+ relation->rd_rel->relkind != RELKIND_PARTITIONED_REL)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not a table",
@@ -3252,6 +3253,10 @@ getRelationDescription(StringInfo buffer, Oid relid)
appendStringInfo(buffer, _("table %s"),
relname);
break;
+ case RELKIND_PARTITIONED_REL:
+ appendStringInfo(buffer, _("partitioned table %s"),
+ relname);
+ break;
case RELKIND_INDEX:
appendStringInfo(buffer, _("index %s"),
relname);
@@ -3708,6 +3713,9 @@ getRelationTypeDescription(StringInfo buffer, Oid relid, int32 objectSubId)
case RELKIND_RELATION:
appendStringInfoString(buffer, "table");
break;
+ case RELKIND_PARTITIONED_REL:
+ appendStringInfoString(buffer, "partitioned table");
+ break;
case RELKIND_INDEX:
appendStringInfoString(buffer, "index");
break;
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
new file mode 100644
index 0000000..5e238dc
--- /dev/null
+++ b/src/backend/catalog/partition.c
@@ -0,0 +1,451 @@
+/*-------------------------------------------------------------------------
+ *
+ * partition.c
+ * Partitioning related utility functions.
+ *
+ * Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ * src/backend/utils/misc/partition.c
+ *
+ *-------------------------------------------------------------------------
+*/
+
+#include "postgres.h"
+
+#include "access/heapam.h"
+#include "access/htup_details.h"
+#include "access/nbtree.h"
+#include "catalog/dependency.h"
+#include "catalog/indexing.h"
+#include "catalog/objectaddress.h"
+#include "catalog/partition.h"
+#include "catalog/pg_collation.h"
+#include "catalog/pg_opclass.h"
+#include "catalog/pg_partitioned.h"
+#include "catalog/pg_type.h"
+#include "executor/executor.h"
+#include "miscadmin.h"
+#include "nodes/nodeFuncs.h"
+#include "optimizer/clauses.h"
+#include "optimizer/planmain.h"
+#include "storage/lmgr.h"
+#include "utils/array.h"
+#include "utils/builtins.h"
+#include "utils/datum.h"
+#include "utils/memutils.h"
+#include "utils/fmgroids.h"
+#include "utils/inval.h"
+#include "utils/lsyscache.h"
+#include "utils/syscache.h"
+
+static PartitionKey CopyPartitionKey(PartitionKey fromkey);
+static KeyTypeCollInfo *copy_key_type_coll_info(int nkeycols,
+ KeyTypeCollInfo *tcinfo);
+static void free_key_type_coll_info(KeyTypeCollInfo *tcinfo);
+
+/*
+ * StorePartitionKey
+ * Store the partition keys of rel into pg_partitioned catalog
+ */
+void
+StorePartitionKey(Relation rel,
+ char strategy,
+ int16 partnatts,
+ AttrNumber *partattrs,
+ List *partexprs,
+ Oid *partopclass)
+{
+ int i;
+ int2vector *partattrs_vec;
+ oidvector *partopclass_vec;
+ Datum partexprsDatum;
+ Relation pg_partitioned;
+ HeapTuple tuple;
+ Datum values[Natts_pg_partitioned];
+ bool nulls[Natts_pg_partitioned];
+ ObjectAddress myself;
+ ObjectAddress referenced;
+
+ Assert(rel->rd_rel->relkind == RELKIND_PARTITIONED_REL);
+
+ tuple = SearchSysCache1(PARTEDRELID,
+ ObjectIdGetDatum(RelationGetRelid(rel)));
+ /* Cannot already exist */
+ Assert(!HeapTupleIsValid(tuple));
+
+ /*
+ * Copy the partition key, opclass info into arrays (should we
+ * make the caller pass them like this to start with?)
+ */
+ partattrs_vec = buildint2vector(partattrs, partnatts);
+ partopclass_vec = buildoidvector(partopclass, partnatts);
+
+ /* Convert the partition key expressions (if any) to a text datum */
+ if (partexprs)
+ {
+ char *exprsString;
+
+ exprsString = nodeToString(partexprs);
+ partexprsDatum = CStringGetTextDatum(exprsString);
+ pfree(exprsString);
+ }
+ else
+ partexprsDatum = (Datum) 0;
+
+ pg_partitioned = heap_open(PartitionedRelationId, RowExclusiveLock);
+
+ MemSet(nulls, false, sizeof(nulls));
+
+ /* Only this can ever be NULL */
+ if (!partexprsDatum)
+ nulls[Anum_pg_partitioned_partexprs - 1] = true;
+
+ values[Anum_pg_partitioned_partedrelid - 1] = ObjectIdGetDatum(RelationGetRelid(rel));
+ values[Anum_pg_partitioned_partstrat - 1] = CharGetDatum(strategy);
+ values[Anum_pg_partitioned_partnatts - 1] = Int16GetDatum(partnatts);
+ values[Anum_pg_partitioned_partattrs - 1] = PointerGetDatum(partattrs_vec);
+ values[Anum_pg_partitioned_partclass - 1] = PointerGetDatum(partopclass_vec);
+ values[Anum_pg_partitioned_partexprs - 1] = partexprsDatum;
+
+ tuple = heap_form_tuple(RelationGetDescr(pg_partitioned), values, nulls);
+
+ simple_heap_insert(pg_partitioned, tuple);
+
+ /* Update the indexes on pg_partitioned */
+ CatalogUpdateIndexes(pg_partitioned, tuple);
+
+ /* Make this relation dependent on a few things: */
+ myself.classId = RelationRelationId;
+ myself.objectId = RelationGetRelid(rel);;
+ myself.objectSubId = 0;
+
+ /* Operator class per key column */
+ for (i = 0; i < partnatts; i++)
+ {
+ referenced.classId = OperatorClassRelationId;
+ referenced.objectId = partopclass[i];
+ referenced.objectSubId = 0;
+
+ recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+ }
+
+ /*
+ * Things inside key expressions
+ *
+ * An ugliness: normal dependencies also created on attribute references
+ * as part of the following.
+ */
+ if (partexprs)
+ recordDependencyOnSingleRelExpr(&myself,
+ (Node *) partexprs,
+ RelationGetRelid(rel),
+ DEPENDENCY_NORMAL,
+ DEPENDENCY_NORMAL);
+ /* Tell world about the key */
+ CacheInvalidateRelcache(rel);
+
+ heap_close(pg_partitioned, RowExclusiveLock);
+ heap_freetuple(tuple);
+}
+
+/*
+ * RemovePartitionKeyByRelId
+ * Remove pg_partitioned entry for a relation
+ */
+void
+RemovePartitionKeyByRelId(Oid relid)
+{
+ Relation rel;
+ HeapTuple tuple;
+
+ rel = heap_open(PartitionedRelationId, RowExclusiveLock);
+
+ tuple = SearchSysCache1(PARTEDRELID, ObjectIdGetDatum(relid));
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "cache lookup failed for partition key of relation %u", relid);
+
+ simple_heap_delete(rel, &tuple->t_self);
+
+ /* Update the indexes on pg_partitioned */
+ CatalogUpdateIndexes(rel, tuple);
+
+ ReleaseSysCache(tuple);
+ heap_close(rel, RowExclusiveLock);
+}
+
+/*
+ * RelationBuildPartitionKey
+ * Build and attach to relcache partition key data of relation
+ *
+ * Note that the partition key data attached to a relcache entry must be
+ * stored CacheMemoryContext to ensure it survives as long as the relcache
+ * entry. But we should be running in a less long-lived working context.
+ * To avoid leaking cache memory if this routine fails partway through,
+ * we build in working memory and then copy the completed structure into
+ * cache memory.
+ */
+void
+RelationBuildPartitionKey(Relation relation)
+{
+ Form_pg_partitioned form;
+ Relation catalog;
+ HeapTuple tuple;
+ bool isnull;
+ int i;
+ PartitionKey key;
+ int2vector *partattrs;
+ oidvector *opclass;
+ KeyTypeCollInfo *tcinfo;
+ ListCell *partexprs_item;
+ Datum datum;
+ MemoryContext oldcxt;
+
+ tuple = SearchSysCache1(PARTEDRELID,
+ ObjectIdGetDatum(RelationGetRelid(relation)));
+ /*
+ * Happend when relcache is built before the partition key is stored in
+ * system catalog
+ */
+ if (!HeapTupleIsValid(tuple))
+ return;
+
+ form = (Form_pg_partitioned) GETSTRUCT(tuple);
+
+ /* Allocate in the hopefully short-lived working context */
+ key = (PartitionKey) palloc0(sizeof(PartitionKeyData));
+ key->strategy = form->partstrat;
+ key->partnatts = form->partnatts;
+
+ /* Open the catalog for its tuple descriptor */
+ catalog = heap_open(PartitionedRelationId, AccessShareLock);
+ datum = fastgetattr(tuple, Anum_pg_partitioned_partattrs,
+ RelationGetDescr(catalog),
+ &isnull);
+ Assert(!isnull);
+ partattrs = (int2vector *) DatumGetPointer(datum);
+
+ datum = fastgetattr(tuple, Anum_pg_partitioned_partclass,
+ RelationGetDescr(catalog),
+ &isnull);
+ Assert(!isnull);
+ opclass = (oidvector *) DatumGetPointer(datum);
+
+ datum = heap_getattr(tuple,
+ Anum_pg_partitioned_partexprs,
+ RelationGetDescr(catalog),
+ &isnull);
+
+ if (!isnull)
+ {
+ char *exprsString;
+ Node *exprs;
+
+ exprsString = TextDatumGetCString(datum);
+ exprs = stringToNode(exprsString);
+ pfree(exprsString);
+
+ /*
+ * Run the expressions through eval_const_expressions. This is
+ * not just an optimization, but is necessary, because eventually
+ * the planner will be comparing them to similarly-processed qual
+ * clauses, and may fail to detect valid matches without this.
+ * We don't bother with canonicalize_qual, however.
+ */
+ exprs = eval_const_expressions(NULL, (Node *) exprs);
+
+ /* May as well fix opfuncids too */
+ fix_opfuncids((Node *) exprs);
+ key->partexprs = (List *) exprs;
+ }
+
+ key->partattrs = (AttrNumber *) palloc0(key->partnatts * sizeof(AttrNumber));
+ key->partopfamily = (Oid *) palloc0(key->partnatts * sizeof(Oid));
+ key->partsupfunc = (FmgrInfo *) palloc0(key->partnatts * sizeof(FmgrInfo));
+
+ /* Gather type and collation info as well */
+ key->tcinfo = tcinfo = (KeyTypeCollInfo *) palloc0(sizeof(KeyTypeCollInfo));
+ tcinfo->typid = (Oid *) palloc0(key->partnatts * sizeof(Oid));
+ tcinfo->typmod = (int32 *) palloc0(key->partnatts * sizeof(int32));
+ tcinfo->typlen = (int16 *) palloc0(key->partnatts * sizeof(int16));
+ tcinfo->typbyval = (bool *) palloc0(key->partnatts * sizeof(bool));
+ tcinfo->typalign = (char *) palloc0(key->partnatts * sizeof(char));
+ tcinfo->typcoll = (Oid *) palloc0(key->partnatts * sizeof(Oid));
+
+ /*
+ * Copy partattrs. Further, determine and store the opfamily and
+ * btree support function per attribute.
+ */
+ partexprs_item = list_head(key->partexprs);
+ for (i = 0; i < key->partnatts; i++)
+ {
+ HeapTuple tuple;
+ AttrNumber attno;
+ Form_pg_opclass form;
+ Oid funcid;
+
+ key->partattrs[i] = attno = partattrs->values[i];
+
+ /* Collect type information */
+ if (attno != InvalidAttrNumber)
+ {
+ tcinfo->typid[i] = relation->rd_att->attrs[attno - 1]->atttypid;
+ tcinfo->typmod[i] = relation->rd_att->attrs[attno - 1]->atttypmod;
+ tcinfo->typcoll[i] = relation->rd_att->attrs[attno - 1]->attcollation;
+ }
+ else
+ {
+ tcinfo->typid[i] = exprType(lfirst(partexprs_item));
+ tcinfo->typmod[i] = exprTypmod(lfirst(partexprs_item));
+ tcinfo->typcoll[i] = exprCollation(lfirst(partexprs_item));
+ partexprs_item = lnext(partexprs_item);
+ }
+ get_typlenbyvalalign(tcinfo->typid[i],
+ &tcinfo->typlen[i],
+ &tcinfo->typbyval[i],
+ &tcinfo->typalign[i]);
+
+ tuple = SearchSysCache1(CLAOID, ObjectIdGetDatum(opclass->values[i]));
+
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "cache lookup failed for opclass %u", opclass->values[i]);
+
+ form = (Form_pg_opclass) GETSTRUCT(tuple);
+ key->partopfamily[i] = form->opcfamily;
+
+ /*
+ * Btree support function is good to cover the list and range
+ * partitionig related needs.
+ */
+ funcid = get_opfamily_proc(form->opcfamily,
+ form->opcintype, form->opcintype,
+ BTORDER_PROC);
+
+ fmgr_info(funcid, &key->partsupfunc[i]);
+ ReleaseSysCache(tuple);
+ }
+
+ ReleaseSysCache(tuple);
+ heap_close(catalog, AccessShareLock);
+
+ /* Success --- now copy to the cache memory */
+ oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
+ relation->rd_partkey = CopyPartitionKey(key);
+ MemoryContextSwitchTo(oldcxt);
+
+ FreePartitionKey(key);
+}
+
+/*
+ * CopyPartitionKey
+ *
+ * The copy is allocated in the current memory context.
+ */
+static PartitionKey
+CopyPartitionKey(PartitionKey fromkey)
+{
+ PartitionKey newkey;
+
+ newkey = (PartitionKey) palloc0(sizeof(PartitionKeyData));
+
+ newkey->strategy = fromkey->strategy;
+ newkey->partnatts = fromkey->partnatts;
+
+ newkey->partattrs = (AttrNumber *)
+ palloc(newkey->partnatts * sizeof(AttrNumber));
+ memcpy(newkey->partattrs, fromkey->partattrs,
+ newkey->partnatts * sizeof(AttrNumber));
+
+ newkey->partopfamily = (Oid *) palloc(newkey->partnatts * sizeof(Oid));
+ memcpy(newkey->partopfamily, fromkey->partopfamily,
+ newkey->partnatts * sizeof(Oid));
+
+ newkey->partsupfunc = (FmgrInfo *)
+ palloc(newkey->partnatts * sizeof(FmgrInfo));
+ memcpy(newkey->partsupfunc, fromkey->partsupfunc,
+ newkey->partnatts * sizeof(FmgrInfo));
+
+ newkey->partexprs = copyObject(fromkey->partexprs);
+ newkey->tcinfo = copy_key_type_coll_info(newkey->partnatts,
+ fromkey->tcinfo);
+
+ return newkey;
+}
+
+/*
+ * copy_key_type_coll_info
+ *
+ * The copy is allocated in the current memory context.
+ */
+static KeyTypeCollInfo *
+copy_key_type_coll_info(int nkeycols, KeyTypeCollInfo *tcinfo)
+{
+ KeyTypeCollInfo *result = (KeyTypeCollInfo *)
+ palloc0(sizeof(KeyTypeCollInfo));
+
+ result->typid = (Oid *) palloc0(nkeycols * sizeof(Oid));
+ memcpy(result->typid, tcinfo->typid, nkeycols * sizeof(Oid));
+
+ result->typmod = (int32 *) palloc0(nkeycols * sizeof(int32));
+ memcpy(result->typmod, tcinfo->typmod, nkeycols * sizeof(int32));
+
+ result->typlen = (int16 *) palloc0(nkeycols * sizeof(int16));
+ memcpy(result->typlen, tcinfo->typlen, nkeycols * sizeof(int16));
+
+ result->typbyval = (bool *) palloc0(nkeycols * sizeof(bool));
+ memcpy(result->typbyval, tcinfo->typbyval, nkeycols * sizeof(bool));
+
+ result->typalign = (char *) palloc0(nkeycols * sizeof(bool));
+ memcpy(result->typalign, tcinfo->typalign, nkeycols * sizeof(char));
+
+ result->typcoll = (Oid *) palloc0(nkeycols * sizeof(Oid));
+ memcpy(result->typcoll, tcinfo->typcoll, nkeycols * sizeof(Oid));
+
+ return result;
+}
+
+/*
+ * free_key_type_info
+ */
+static void
+free_key_type_coll_info(KeyTypeCollInfo *tcinfo)
+{
+ Assert(tcinfo != NULL);
+
+ pfree(tcinfo->typid);
+ pfree(tcinfo->typmod);
+ pfree(tcinfo->typlen);
+ pfree(tcinfo->typbyval);
+ pfree(tcinfo->typalign);
+ pfree(tcinfo->typcoll);
+ pfree(tcinfo);
+}
+
+/*
+ * Does relid have a pg_partitioned entry?
+ */
+bool
+relid_is_partitioned(Oid relid)
+{
+ return SearchSysCacheExists1(PARTEDRELID, ObjectIdGetDatum(relid));
+}
+
+/*
+ * FreePartitionKeys
+ */
+void
+FreePartitionKey(PartitionKey key)
+{
+ if (key == NULL)
+ return;
+
+ pfree(key->partattrs);
+ pfree(key->partopfamily);
+ pfree(key->partsupfunc);
+ if (key->partexprs)
+ pfree(key->partexprs);
+ free_key_type_coll_info(key->tcinfo);
+ pfree(key);
+}
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 3201476..403738d 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -1718,6 +1718,12 @@ BeginCopyTo(Relation rel,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot copy from sequence \"%s\"",
RelationGetRelationName(rel))));
+ else if (rel->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot copy from partitioned table \"%s\"",
+ RelationGetRelationName(rel)),
+ errhint("Try the COPY (SELECT ...) TO variant.")));
else
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index fd1d7b2..ea232b1 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -367,7 +367,8 @@ DefineIndex(Oid relationId,
namespaceId = RelationGetNamespace(rel);
if (rel->rd_rel->relkind != RELKIND_RELATION &&
- rel->rd_rel->relkind != RELKIND_MATVIEW)
+ rel->rd_rel->relkind != RELKIND_MATVIEW &&
+ rel->rd_rel->relkind != RELKIND_PARTITIONED_REL)
{
if (rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
diff --git a/src/backend/commands/lockcmds.c b/src/backend/commands/lockcmds.c
index 175d1f3..62671a8 100644
--- a/src/backend/commands/lockcmds.c
+++ b/src/backend/commands/lockcmds.c
@@ -88,7 +88,7 @@ RangeVarCallbackForLockTable(const RangeVar *rv, Oid relid, Oid oldrelid,
* check */
/* Currently, we only allow plain tables to be locked */
- if (relkind != RELKIND_RELATION)
+ if (relkind != RELKIND_RELATION && relkind != RELKIND_PARTITIONED_REL)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not a table",
diff --git a/src/backend/commands/policy.c b/src/backend/commands/policy.c
index 146b36c..7449273 100644
--- a/src/backend/commands/policy.c
+++ b/src/backend/commands/policy.c
@@ -88,7 +88,7 @@ RangeVarCallbackForPolicy(const RangeVar *rv, Oid relid, Oid oldrelid,
rv->relname)));
/* Relation type MUST be a table. */
- if (relkind != RELKIND_RELATION)
+ if (relkind != RELKIND_RELATION && relkind != RELKIND_PARTITIONED_REL)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not a table", rv->relname)));
diff --git a/src/backend/commands/seclabel.c b/src/backend/commands/seclabel.c
index 5bd7e12..8bc8638 100644
--- a/src/backend/commands/seclabel.c
+++ b/src/backend/commands/seclabel.c
@@ -107,6 +107,7 @@ ExecSecLabelStmt(SecLabelStmt *stmt)
* are the only relkinds for which pg_dump will dump labels).
*/
if (relation->rd_rel->relkind != RELKIND_RELATION &&
+ relation->rd_rel->relkind != RELKIND_PARTITIONED_REL &&
relation->rd_rel->relkind != RELKIND_VIEW &&
relation->rd_rel->relkind != RELKIND_MATVIEW &&
relation->rd_rel->relkind != RELKIND_COMPOSITE_TYPE &&
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index f38126f..5ecad82 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -1467,6 +1467,7 @@ process_owned_by(Relation seqrel, List *owned_by)
/* Must be a regular or foreign table */
if (!(tablerel->rd_rel->relkind == RELKIND_RELATION ||
+ tablerel->rd_rel->relkind == RELKIND_PARTITIONED_REL ||
tablerel->rd_rel->relkind == RELKIND_FOREIGN_TABLE))
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 17f4807..4976bbe 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -29,6 +29,7 @@
#include "catalog/indexing.h"
#include "catalog/namespace.h"
#include "catalog/objectaccess.h"
+#include "catalog/partition.h"
#include "catalog/pg_am.h"
#include "catalog/pg_collation.h"
#include "catalog/pg_constraint.h"
@@ -262,6 +263,12 @@ struct DropRelationCallbackState
bool concurrent;
};
+/* for find_attr_reference_walker */
+typedef struct
+{
+ AttrNumber attnum;
+} find_attr_reference_context;
+
/* Alter table target-type flags for ATSimplePermissions */
#define ATT_TABLE 0x0001
#define ATT_VIEW 0x0002
@@ -438,6 +445,9 @@ static void ComputePartitionAttrs(Oid relid, List *partParams,
AttrNumber *partattrs,
List **partexprs,
Oid *partoplass);
+static bool find_attr_reference_walker(Node *node,
+ find_attr_reference_context *context);
+static bool is_partition_attr(Relation rel, AttrNumber attnum, bool *is_expr);
/* ----------------------------------------------------------------
@@ -601,7 +611,8 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
* affect other relkinds, but it would complicate interpretOidsOption().
*/
localHasOids = interpretOidsOption(stmt->options,
- (relkind == RELKIND_RELATION));
+ (relkind == RELKIND_RELATION ||
+ relkind == RELKIND_PARTITIONED_REL));
descriptor->tdhasoid = (localHasOids || parentOidCount > 0);
/*
@@ -715,7 +726,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
AddRelationNewConstraints(rel, rawDefaults, stmt->constraints,
true, true, false);
- /* Process partition key, if any */
+ /* Process and store partition key, if any */
if (stmt->partby)
{
PartitionBy *partby;
@@ -728,6 +739,8 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
partby = transformPartitionBy(rel, stmt->partby);
ComputePartitionAttrs(relationId, stmt->partby->partParams,
partattrs, &partexprs, partopclass);
+ StorePartitionKey(rel, partby->strategy, partnatts, partattrs,
+ partexprs, partopclass);
}
ObjectAddressSet(address, RelationRelationId, relationId);
@@ -975,7 +988,15 @@ RangeVarCallbackForDropRelation(const RangeVar *rel, Oid relOid, Oid oldRelOid,
return; /* concurrently dropped, so nothing to do */
classform = (Form_pg_class) GETSTRUCT(tuple);
- if (classform->relkind != relkind)
+ /*
+ * Both normal and partitioned tables are dropped using DROP TABLE.
+ * RemoveRelations however never passes RELKIND_PARTITIONED_REL as
+ * relkind for OBJECT_TABLE relations. A mismatch below may have
+ * to do with that. So, check.
+ */
+ if (classform->relkind != relkind &&
+ (relkind == RELKIND_RELATION &&
+ classform->relkind != RELKIND_PARTITIONED_REL))
DropErrorMsgWrongType(rel->relname, classform->relkind, relkind);
/* Allow DROP to either table owner or schema owner */
@@ -1313,7 +1334,8 @@ truncate_check_rel(Relation rel)
AclResult aclresult;
/* Only allow truncate on regular tables */
- if (rel->rd_rel->relkind != RELKIND_RELATION)
+ if (rel->rd_rel->relkind != RELKIND_RELATION &&
+ rel->rd_rel->relkind != RELKIND_PARTITIONED_REL)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not a table",
@@ -1541,8 +1563,16 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
*/
relation = heap_openrv(parent, ShareUpdateExclusiveLock);
+ /* Cannot inherit from partitioned tables */
+ if (relation->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot inherit from partitioned table \"%s\"",
+ parent->relname)));
+
if (relation->rd_rel->relkind != RELKIND_RELATION &&
- relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
+ relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE &&
+ relation->rd_rel->relkind != RELKIND_PARTITIONED_REL)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("inherited relation \"%s\" is not a table or foreign table",
@@ -2182,6 +2212,7 @@ renameatt_check(Oid myrelid, Form_pg_class classform, bool recursing)
* restriction.
*/
if (relkind != RELKIND_RELATION &&
+ relkind != RELKIND_PARTITIONED_REL &&
relkind != RELKIND_VIEW &&
relkind != RELKIND_MATVIEW &&
relkind != RELKIND_COMPOSITE_TYPE &&
@@ -4313,6 +4344,7 @@ ATSimplePermissions(Relation rel, int allowed_targets)
switch (rel->rd_rel->relkind)
{
case RELKIND_RELATION:
+ case RELKIND_PARTITIONED_REL:
actual_target = ATT_TABLE;
break;
case RELKIND_VIEW:
@@ -4549,6 +4581,7 @@ find_composite_type_dependencies(Oid typeOid, Relation origRelation,
att = rel->rd_att->attrs[pg_depend->objsubid - 1];
if (rel->rd_rel->relkind == RELKIND_RELATION ||
+ rel->rd_rel->relkind == RELKIND_PARTITIONED_REL ||
rel->rd_rel->relkind == RELKIND_MATVIEW)
{
if (origTypeName)
@@ -5439,6 +5472,7 @@ ATPrepSetStatistics(Relation rel, const char *colName, Node *newValue, LOCKMODE
* allowSystemTableMods to be turned on.
*/
if (rel->rd_rel->relkind != RELKIND_RELATION &&
+ rel->rd_rel->relkind != RELKIND_PARTITIONED_REL &&
rel->rd_rel->relkind != RELKIND_MATVIEW &&
rel->rd_rel->relkind != RELKIND_INDEX &&
rel->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
@@ -5713,6 +5747,73 @@ ATPrepDropColumn(List **wqueue, Relation rel, bool recurse, bool recursing,
cmd->subtype = AT_DropColumnRecurse;
}
+/* Checks if a Var node is for a given attnum */
+static bool
+find_attr_reference_walker(Node *node, find_attr_reference_context *context)
+{
+ if (node == NULL)
+ return false;
+
+ if (IsA(node, Var))
+ {
+ Var *variable = (Var *) node;
+ AttrNumber attnum = variable->varattno;
+
+ if (attnum == context->attnum)
+ return true;
+ }
+
+ return expression_tree_walker(node, find_attr_reference_walker, context);
+}
+
+/*
+ * Checks if attnum is a partition attribute for rel
+ *
+ * Sets *is_expr if attnum is found to be referenced in some partition key
+ * expression.
+ */
+static bool
+is_partition_attr(Relation rel, AttrNumber attnum, bool *is_expr)
+{
+ PartitionKey key;
+ ListCell *partexpr_item;
+ int i;
+
+ if (rel->rd_rel->relkind != RELKIND_PARTITIONED_REL)
+ {
+ *is_expr = false;
+ return false;
+ }
+
+ key = rel->rd_partkey;
+
+ partexpr_item = list_head(key->partexprs);
+ for (i = 0; i < key->partnatts; i++)
+ {
+ AttrNumber partatt = key->partattrs[i];
+
+ if(partatt != 0)
+ {
+ *is_expr = false;
+ if (attnum == partatt)
+ return true;
+ }
+ else
+ {
+ find_attr_reference_context context;
+
+ *is_expr = true;
+ context.attnum = attnum;
+ if (find_attr_reference_walker(lfirst(partexpr_item), &context))
+ return true;
+
+ partexpr_item = lnext(partexpr_item);
+ }
+ }
+
+ return false;
+}
+
/*
* Return value is the address of the dropped column.
*/
@@ -5727,6 +5828,7 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
AttrNumber attnum;
List *children;
ObjectAddress object;
+ bool is_expr;
/* At top level, permission check was done in ATPrepCmd, else do it */
if (recursing)
@@ -5771,6 +5873,19 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
errmsg("cannot drop inherited column \"%s\"",
colName)));
+ /* Don't drop columns used in partition key */
+ if (is_partition_attr(rel, attnum, &is_expr))
+ {
+ if (!is_expr)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("cannot drop column named in partition key")));
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("cannot drop column referenced in partition key expression")));
+ }
+
ReleaseSysCache(tuple);
/*
@@ -6289,6 +6404,12 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
* Validity checks (permission checks wait till we have the column
* numbers)
*/
+ if (pkrel->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot reference relation \"%s\"", RelationGetRelationName(pkrel)),
+ errdetail("Referencing partitioned tables in foreign key constraints is not supported.")));
+
if (pkrel->rd_rel->relkind != RELKIND_RELATION)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
@@ -7883,6 +8004,7 @@ ATPrepAlterColumnType(List **wqueue,
NewColumnValue *newval;
ParseState *pstate = make_parsestate(NULL);
AclResult aclresult;
+ bool is_expr;
if (rel->rd_rel->reloftype && !recursing)
ereport(ERROR,
@@ -7913,6 +8035,19 @@ ATPrepAlterColumnType(List **wqueue,
errmsg("cannot alter inherited column \"%s\"",
colName)));
+ /* Don't alter columns used in partition key */
+ if (is_partition_attr(rel, attnum, &is_expr))
+ {
+ if (!is_expr)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("cannot alter column named in partition key")));
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("cannot alter column referenced in partition key expression")));
+ }
+
/* Look up the target type */
typenameTypeIdAndMod(NULL, typeName, &targettype, &targettypmod);
@@ -7928,7 +8063,8 @@ ATPrepAlterColumnType(List **wqueue,
list_make1_oid(rel->rd_rel->reltype),
false);
- if (tab->relkind == RELKIND_RELATION)
+ if (tab->relkind == RELKIND_RELATION ||
+ tab->relkind == RELKIND_PARTITIONED_REL)
{
/*
* Set up an expression to transform the old data value to the new
@@ -8955,6 +9091,7 @@ ATExecChangeOwner(Oid relationOid, Oid newOwnerId, bool recursing, LOCKMODE lock
switch (tuple_class->relkind)
{
case RELKIND_RELATION:
+ case RELKIND_PARTITIONED_REL:
case RELKIND_VIEW:
case RELKIND_MATVIEW:
case RELKIND_FOREIGN_TABLE:
@@ -9417,6 +9554,7 @@ ATExecSetRelOptions(Relation rel, List *defList, AlterTableType operation,
switch (rel->rd_rel->relkind)
{
case RELKIND_RELATION:
+ case RELKIND_PARTITIONED_REL:
case RELKIND_TOASTVALUE:
case RELKIND_MATVIEW:
(void) heap_reloptions(rel->rd_rel->relkind, newOptions, true);
@@ -9839,7 +9977,8 @@ AlterTableMoveAll(AlterTableMoveAllStmt *stmt)
/* Only move the object type requested */
if ((stmt->objtype == OBJECT_TABLE &&
- relForm->relkind != RELKIND_RELATION) ||
+ relForm->relkind != RELKIND_RELATION &&
+ relForm->relkind != RELKIND_PARTITIONED_REL) ||
(stmt->objtype == OBJECT_INDEX &&
relForm->relkind != RELKIND_INDEX) ||
(stmt->objtype == OBJECT_MATVIEW &&
@@ -10038,6 +10177,11 @@ ATPrepAddInherit(Relation child_rel)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot change inheritance of typed table")));
+
+ if (child_rel->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot change inheritance of partitioned table")));
}
/*
@@ -10089,6 +10233,12 @@ ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode)
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot inherit to temporary relation of another session")));
+ /* Prevent partitioned tables from becoming inheritance parents */
+ if (parent_rel->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot inherit from partitioned table \"%s\"", parent->relname)));
+
/*
* Check for duplicates in the list of parents, and determine the highest
* inhseqno already present; we'll use the next one for the new parent.
@@ -11457,6 +11607,7 @@ AlterTableNamespaceInternal(Relation rel, Oid oldNspOid, Oid nspOid,
/* Fix other dependent stuff */
if (rel->rd_rel->relkind == RELKIND_RELATION ||
+ rel->rd_rel->relkind == RELKIND_PARTITIONED_REL ||
rel->rd_rel->relkind == RELKIND_MATVIEW)
{
AlterIndexNamespaces(classRel, rel, oldNspOid, nspOid, objsMoved);
@@ -11906,7 +12057,7 @@ RangeVarCallbackOwnsTable(const RangeVar *relation,
if (!relkind)
return;
if (relkind != RELKIND_RELATION && relkind != RELKIND_TOASTVALUE &&
- relkind != RELKIND_MATVIEW)
+ relkind != RELKIND_MATVIEW && relkind != RELKIND_PARTITIONED_REL)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not a table or materialized view", relation->relname)));
@@ -12060,6 +12211,7 @@ RangeVarCallbackForAlterRelation(const RangeVar *rv, Oid relid, Oid oldrelid,
*/
if (IsA(stmt, AlterObjectSchemaStmt) &&
relkind != RELKIND_RELATION &&
+ relkind != RELKIND_PARTITIONED_REL &&
relkind != RELKIND_VIEW &&
relkind != RELKIND_MATVIEW &&
relkind != RELKIND_SEQUENCE &&
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index 33107e0..a02db8b 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -174,7 +174,8 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
* Triggers must be on tables or views, and there are additional
* relation-type-specific restrictions.
*/
- if (rel->rd_rel->relkind == RELKIND_RELATION)
+ if (rel->rd_rel->relkind == RELKIND_RELATION ||
+ rel->rd_rel->relkind == RELKIND_PARTITIONED_REL)
{
/* Tables can't have INSTEAD OF triggers */
if (stmt->timing != TRIGGER_TYPE_BEFORE &&
@@ -1112,6 +1113,7 @@ RemoveTriggerById(Oid trigOid)
rel = heap_open(relid, AccessExclusiveLock);
if (rel->rd_rel->relkind != RELKIND_RELATION &&
+ rel->rd_rel->relkind != RELKIND_PARTITIONED_REL &&
rel->rd_rel->relkind != RELKIND_VIEW &&
rel->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
ereport(ERROR,
@@ -1218,7 +1220,8 @@ RangeVarCallbackForRenameTrigger(const RangeVar *rv, Oid relid, Oid oldrelid,
/* only tables and views can have triggers */
if (form->relkind != RELKIND_RELATION && form->relkind != RELKIND_VIEW &&
- form->relkind != RELKIND_FOREIGN_TABLE)
+ form->relkind != RELKIND_FOREIGN_TABLE &&
+ form->relkind != RELKIND_PARTITIONED_REL)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not a table, view, or foreign table",
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 93361a0..003a30f 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -1301,9 +1301,14 @@ vacuum_rel(Oid relid, RangeVar *relation, int options, VacuumParams *params)
onerel->rd_rel->relkind != RELKIND_MATVIEW &&
onerel->rd_rel->relkind != RELKIND_TOASTVALUE)
{
- ereport(WARNING,
- (errmsg("skipping \"%s\" --- cannot vacuum non-tables or special system tables",
- RelationGetRelationName(onerel))));
+ if (onerel->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+ ereport(WARNING,
+ (errmsg("skipping \"%s\" --- cannot vacuum partitioned tables",
+ RelationGetRelationName(onerel))));
+ else
+ ereport(WARNING,
+ (errmsg("skipping \"%s\" --- cannot vacuum non-tables or special system tables",
+ RelationGetRelationName(onerel))));
relation_close(onerel, lmode);
PopActiveSnapshot();
CommitTransactionCommand();
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index ac02304..67492a5 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -1019,6 +1019,7 @@ CheckValidResultRel(Relation resultRel, CmdType operation)
switch (resultRel->rd_rel->relkind)
{
case RELKIND_RELATION:
+ case RELKIND_PARTITIONED_REL:
/* OK */
break;
case RELKIND_SEQUENCE:
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index e62c8aa..7ad1140 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1871,6 +1871,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
relkind = resultRelInfo->ri_RelationDesc->rd_rel->relkind;
if (relkind == RELKIND_RELATION ||
+ relkind == RELKIND_PARTITIONED_REL ||
relkind == RELKIND_MATVIEW)
{
j->jf_junkAttNo = ExecFindJunkAttribute(j, "ctid");
diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c
index b56fd4f..77ffd84 100644
--- a/src/backend/parser/parse_agg.c
+++ b/src/backend/parser/parse_agg.c
@@ -488,9 +488,9 @@ check_agglevels_and_constraints(ParseState *pstate, Node *expr)
break;
case EXPR_KIND_PARTITION_KEY:
if (isAgg)
- err = _("aggregate functions are not allowed in partition key expressions");
+ err = _("aggregate functions are not allowed in partition key expression");
else
- err = _("grouping operations are not allowed in partition key expressions");
+ err = _("grouping operations are not allowed in partition key expression");
break;
@@ -852,7 +852,7 @@ transformWindowFuncCall(ParseState *pstate, WindowFunc *wfunc,
err = _("window functions are not allowed in trigger WHEN conditions");
break;
case EXPR_KIND_PARTITION_KEY:
- err = _("window functions are not allowed in partition key expressions");
+ err = _("window functions are not allowed in partition key expression");
break;
/*
diff --git a/src/backend/parser/parse_clause.c b/src/backend/parser/parse_clause.c
index 751de4b..307e383 100644
--- a/src/backend/parser/parse_clause.c
+++ b/src/backend/parser/parse_clause.c
@@ -21,6 +21,7 @@
#include "access/tsmapi.h"
#include "catalog/catalog.h"
#include "catalog/heap.h"
+#include "catalog/partition.h"
#include "catalog/pg_am.h"
#include "catalog/pg_constraint_fn.h"
#include "catalog/pg_type.h"
@@ -200,6 +201,13 @@ setTargetTable(ParseState *pstate, RangeVar *relation,
relation->alias, inh, false);
pstate->p_target_rangetblentry = rte;
+ /*
+ * Override specified inheritance option, if relation is a partitioned
+ * table and it's a target of INSERT (ie, alsoSource == false).
+ */
+ if (alsoSource)
+ rte->inh |= relid_is_partitioned(rte->relid);
+
/* assume new rte is at end */
rtindex = list_length(pstate->p_rtable);
Assert(rte == rt_fetch(rtindex, pstate->p_rtable));
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 70a663c..6a74c28 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -1716,7 +1716,7 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
err = _("cannot use subquery in trigger WHEN condition");
break;
case EXPR_KIND_PARTITION_KEY:
- err = _("cannot use subquery in partition key expressions");
+ err = _("cannot use subquery in partition key expression");
break;
/*
diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c
index 81332b5..abcc1d6 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -20,6 +20,7 @@
#include "access/sysattr.h"
#include "catalog/heap.h"
#include "catalog/namespace.h"
+#include "catalog/partition.h"
#include "catalog/pg_type.h"
#include "funcapi.h"
#include "nodes/makefuncs.h"
@@ -1216,9 +1217,12 @@ addRangeTableEntry(ParseState *pstate,
*
* The initial default on access checks is always check-for-READ-access,
* which is the right thing for all except target tables.
+ *
+ * Note: we override the specified inheritance option, if relation is a
+ * partitioned table.
*/
rte->lateral = false;
- rte->inh = inh;
+ rte->inh = inh || relid_is_partitioned(RelationGetRelid(rel));
rte->inFromCl = inFromCl;
rte->requiredPerms = ACL_SELECT;
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e267f74..465597d 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -33,6 +33,7 @@
#include "catalog/heap.h"
#include "catalog/index.h"
#include "catalog/namespace.h"
+#include "catalog/partition.h"
#include "catalog/pg_am.h"
#include "catalog/pg_collation.h"
#include "catalog/pg_constraint.h"
@@ -821,6 +822,7 @@ transformTableLikeClause(CreateStmtContext *cxt, TableLikeClause *table_like_cla
relation = relation_openrv(table_like_clause->relation, AccessShareLock);
if (relation->rd_rel->relkind != RELKIND_RELATION &&
+ relation->rd_rel->relkind != RELKIND_PARTITIONED_REL &&
relation->rd_rel->relkind != RELKIND_VIEW &&
relation->rd_rel->relkind != RELKIND_MATVIEW &&
relation->rd_rel->relkind != RELKIND_COMPOSITE_TYPE &&
@@ -2577,7 +2579,7 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
cxt.blist = NIL;
cxt.alist = NIL;
cxt.pkey = NULL;
- cxt.ispartitioned = false;
+ cxt.ispartitioned = relid_is_partitioned(relid);
/*
* The only subtypes that currently require parse transformation handling
diff --git a/src/backend/rewrite/rewriteDefine.c b/src/backend/rewrite/rewriteDefine.c
index 96f3f37..db80c90 100644
--- a/src/backend/rewrite/rewriteDefine.c
+++ b/src/backend/rewrite/rewriteDefine.c
@@ -260,6 +260,7 @@ DefineQueryRewrite(char *rulename,
* blocks them for users. Don't mention them in the error message.
*/
if (event_relation->rd_rel->relkind != RELKIND_RELATION &&
+ event_relation->rd_rel->relkind != RELKIND_PARTITIONED_REL &&
event_relation->rd_rel->relkind != RELKIND_MATVIEW &&
event_relation->rd_rel->relkind != RELKIND_VIEW)
ereport(ERROR,
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index a22a11e..f429dd6 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -1222,6 +1222,7 @@ rewriteTargetListUD(Query *parsetree, RangeTblEntry *target_rte,
TargetEntry *tle;
if (target_relation->rd_rel->relkind == RELKIND_RELATION ||
+ target_relation->rd_rel->relkind == RELKIND_PARTITIONED_REL ||
target_relation->rd_rel->relkind == RELKIND_MATVIEW)
{
/*
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index ac50c2a..220cbfc 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -975,10 +975,13 @@ ProcessUtilitySlow(Node *parsetree,
{
Datum toast_options;
static char *validnsps[] = HEAP_RELOPT_NAMESPACES;
+ char relkind = ((CreateStmt *) stmt)->partby != NULL
+ ? RELKIND_PARTITIONED_REL
+ : RELKIND_RELATION;
/* Create the table itself */
address = DefineRelation((CreateStmt *) stmt,
- RELKIND_RELATION,
+ relkind,
InvalidOid, NULL);
EventTriggerCollectSimpleCommand(address,
secondaryObject,
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 2b47e95..49f6fad 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -33,6 +33,7 @@
#include "catalog/pg_language.h"
#include "catalog/pg_opclass.h"
#include "catalog/pg_operator.h"
+#include "catalog/pg_partitioned.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_trigger.h"
#include "catalog/pg_type.h"
@@ -315,6 +316,7 @@ static char *pg_get_indexdef_worker(Oid indexrelid, int colno,
const Oid *excludeOps,
bool attrsOnly, bool showTblSpc,
int prettyFlags);
+static char *pg_get_partkeydef_worker(Oid relId, int prettyFlags);
static char *pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
int prettyFlags);
static text *pg_get_expr_worker(text *expr, Oid relid, const char *relname,
@@ -1289,6 +1291,151 @@ pg_get_indexdef_worker(Oid indexrelid, int colno,
return buf.data;
}
+/*
+ * pg_get_partkeydef
+ *
+ * Returns the partition key specification, ie, the following:
+ *
+ * PARTITION BY { RANGE | LIST } (column [USING opclass_name] [, ...])
+ */
+Datum
+pg_get_partkeydef(PG_FUNCTION_ARGS)
+{
+ Oid relationId = PG_GETARG_OID(0);
+ int prettyFlags;
+
+ prettyFlags = PRETTYFLAG_INDENT;
+ PG_RETURN_TEXT_P(string_to_text(pg_get_partkeydef_worker(relationId,
+ prettyFlags)));
+}
+
+/*
+ * Internal workhorse to decompile a partition key definition.
+ */
+static char *
+pg_get_partkeydef_worker(Oid relId, int prettyFlags)
+{
+ Form_pg_partitioned form;
+ HeapTuple tuple;
+ oidvector *partclass;
+ List *partexprs;
+ ListCell *partexpr_item;
+ List *context;
+ Datum datum;
+ bool isnull;
+ StringInfoData buf;
+ int keyno;
+ char *str;
+ char *sep;
+
+ tuple = SearchSysCache1(PARTEDRELID, ObjectIdGetDatum(relId));
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "cache lookup failed for partition key of %u", relId);
+
+ form = (Form_pg_partitioned) GETSTRUCT(tuple);
+
+ Assert(form->partedrelid == relId);
+
+ /* Must get partclass, and partexprs the hard way */
+ datum = SysCacheGetAttr(PARTEDRELID, tuple,
+ Anum_pg_partitioned_partclass, &isnull);
+ Assert(!isnull);
+ partclass = (oidvector *) DatumGetPointer(datum);
+
+ /*
+ * Get the partition key expressions, if any. (NOTE: we do not use the
+ * relcache versions of the expressions, because we want to display
+ * non-const-folded expressions.)
+ */
+ if (!heap_attisnull(tuple, Anum_pg_partitioned_partexprs))
+ {
+ Datum exprsDatum;
+ bool isnull;
+ char *exprsString;
+
+ exprsDatum = SysCacheGetAttr(PARTEDRELID, tuple,
+ Anum_pg_partitioned_partexprs, &isnull);
+ Assert(!isnull);
+ exprsString = TextDatumGetCString(exprsDatum);
+ partexprs = (List *) stringToNode(exprsString);
+ pfree(exprsString);
+ }
+ else
+ partexprs = NIL;
+
+ partexpr_item = list_head(partexprs);
+ context = deparse_context_for(get_relation_name(relId), relId);
+
+ /*
+ * Start the partition key definition.
+ */
+ initStringInfo(&buf);
+
+ appendStringInfo(&buf, "PARTITION BY ");
+
+ switch (form->partstrat)
+ {
+ case 'l':
+ appendStringInfo(&buf, "LIST");
+ break;
+ case 'r':
+ appendStringInfo(&buf, "RANGE");
+ break;
+ }
+
+ /*
+ * Report the partition key columns
+ */
+ appendStringInfo(&buf, " (");
+ sep = "";
+ for (keyno = 0; keyno < form->partnatts; keyno++)
+ {
+ AttrNumber attnum = form->partattrs.values[keyno];
+ Oid keycoltype;
+
+ appendStringInfoString(&buf, sep);
+ sep = ", ";
+ if (attnum != 0)
+ {
+ /* Simple partition key column */
+ char *attname;
+
+ attname = get_relid_attribute_name(relId, attnum);
+ appendStringInfoString(&buf, quote_identifier(attname));
+ keycoltype = get_atttype(relId, attnum);
+ }
+ else
+ {
+ /* partition key expression */
+ Node *partkey;
+
+ if (partexpr_item == NULL)
+ elog(ERROR, "too few entries in partexprs list");
+ partkey = (Node *) lfirst(partexpr_item);
+ partexpr_item = lnext(partexpr_item);
+ /* Deparse */
+ str = deparse_expression_pretty(partkey, context, false, false,
+ prettyFlags, 0);
+
+ /* Need parens if it's not a bare function call */
+ if (partkey && IsA(partkey, FuncExpr) &&
+ ((FuncExpr *) partkey)->funcformat == COERCE_EXPLICIT_CALL)
+ appendStringInfoString(&buf, str);
+ else
+ appendStringInfo(&buf, "(%s)", str);
+ keycoltype = exprType(partkey);
+ }
+
+ /* Add the operator class name, if not default */
+ get_opclass_name(partclass->values[keyno], keycoltype, &buf);
+ }
+ appendStringInfoChar(&buf, ')');
+
+ /* Clean up */
+ ReleaseSysCache(tuple);
+
+ return buf.data;
+}
/*
* pg_get_constraintdef
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 432feef..2841745 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -40,6 +40,7 @@
#include "catalog/index.h"
#include "catalog/indexing.h"
#include "catalog/namespace.h"
+#include "catalog/partition.h"
#include "catalog/pg_am.h"
#include "catalog/pg_amproc.h"
#include "catalog/pg_attrdef.h"
@@ -431,6 +432,7 @@ RelationParseRelOptions(Relation relation, HeapTuple tuple)
switch (relation->rd_rel->relkind)
{
case RELKIND_RELATION:
+ case RELKIND_PARTITIONED_REL:
case RELKIND_TOASTVALUE:
case RELKIND_INDEX:
case RELKIND_VIEW:
@@ -1049,6 +1051,12 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
else
relation->rd_rsdesc = NULL;
+ /* if it's a partitioned table, initialize key info */
+ if (relation->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+ RelationBuildPartitionKey(relation);
+ else
+ relation->rd_partkey = NULL;
+
/*
* if it's an index, initialize index-related information
*/
@@ -2046,6 +2054,7 @@ RelationDestroyRelation(Relation relation, bool remember_tupdesc)
MemoryContextDelete(relation->rd_rulescxt);
if (relation->rd_rsdesc)
MemoryContextDelete(relation->rd_rsdesc->rscxt);
+ FreePartitionKey(relation->rd_partkey);
if (relation->rd_fdwroutine)
pfree(relation->rd_fdwroutine);
pfree(relation);
@@ -2987,7 +2996,9 @@ RelationBuildLocalRelation(const char *relname,
/* system relations and non-table objects don't have one */
if (!IsSystemNamespace(relnamespace) &&
- (relkind == RELKIND_RELATION || relkind == RELKIND_MATVIEW))
+ (relkind == RELKIND_RELATION ||
+ relkind == RELKIND_PARTITIONED_REL ||
+ relkind == RELKIND_MATVIEW))
rel->rd_rel->relreplident = REPLICA_IDENTITY_DEFAULT;
else
rel->rd_rel->relreplident = REPLICA_IDENTITY_NOTHING;
@@ -4972,6 +4983,7 @@ load_relcache_init_file(bool shared)
rel->rd_rulescxt = NULL;
rel->trigdesc = NULL;
rel->rd_rsdesc = NULL;
+ rel->rd_partkey = NULL;
rel->rd_indexprs = NIL;
rel->rd_indpred = NIL;
rel->rd_exclops = NULL;
diff --git a/src/backend/utils/cache/syscache.c b/src/backend/utils/cache/syscache.c
index 65ffe84..a6563fe 100644
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -48,6 +48,7 @@
#include "catalog/pg_opclass.h"
#include "catalog/pg_operator.h"
#include "catalog/pg_opfamily.h"
+#include "catalog/pg_partitioned.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_range.h"
#include "catalog/pg_rewrite.h"
@@ -568,6 +569,17 @@ static const struct cachedesc cacheinfo[] = {
},
8
},
+ {PartitionedRelationId, /* PARTEDRELID */
+ PartitionedRelidIndexId,
+ 1,
+ {
+ Anum_pg_partitioned_partedrelid,
+ 0,
+ 0,
+ 0
+ },
+ 128
+ },
{ProcedureRelationId, /* PROCNAMEARGSNSP */
ProcedureNameArgsNspIndexId,
3,
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index e1e5bee..4c95aca 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -1234,9 +1234,10 @@ expand_table_name_patterns(Archive *fout,
"SELECT c.oid"
"\nFROM pg_catalog.pg_class c"
"\n LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace"
- "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c')\n",
+ "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c')\n",
RELKIND_RELATION, RELKIND_SEQUENCE, RELKIND_VIEW,
- RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+ RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+ RELKIND_PARTITIONED_REL);
processSQLNamePattern(GetConnection(fout), query, cell->val, true,
false, "n.nspname", "c.relname", NULL,
"pg_catalog.pg_table_is_visible(c.oid)");
@@ -2064,6 +2065,9 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo, bool oids)
/* Skip FOREIGN TABLEs (no data to dump) */
if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
return;
+ /* Skip partitioned tables (data in partitions) */
+ if (tbinfo->relkind == RELKIND_PARTITIONED_REL)
+ return;
/* Don't dump data in unlogged tables, if so requested */
if (tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED &&
@@ -5131,6 +5135,7 @@ getTables(Archive *fout, int *numTables)
int i_toastreloptions;
int i_reloftype;
int i_relpages;
+ int i_partkeydef;
/* Make sure we are in proper schema */
selectSourceSchema(fout, "pg_catalog");
@@ -5196,7 +5201,8 @@ getTables(Archive *fout, int *numTables)
"array_remove(array_remove(c.reloptions,'check_option=local'),'check_option=cascaded') AS reloptions, "
"CASE WHEN 'check_option=local' = ANY (c.reloptions) THEN 'LOCAL'::text "
"WHEN 'check_option=cascaded' = ANY (c.reloptions) THEN 'CASCADED'::text ELSE NULL END AS checkoption, "
- "tc.reloptions AS toast_reloptions "
+ "tc.reloptions AS toast_reloptions, "
+ "CASE WHEN c.relkind = 'P' THEN pg_catalog.pg_get_partkeydef(c.oid) ELSE NULL END AS partkeydef "
"FROM pg_class c "
"LEFT JOIN pg_depend d ON "
"(c.relkind = '%c' AND "
@@ -5208,7 +5214,7 @@ getTables(Archive *fout, int *numTables)
"(c.oid = pip.objoid AND pip.classoid = "
"(SELECT oid FROM pg_class "
"WHERE relname = 'pg_class') AND pip.objsubid = 0) "
- "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c') "
+ "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c', '%c') "
"ORDER BY c.oid",
acl_subquery->data,
racl_subquery->data,
@@ -5218,7 +5224,8 @@ getTables(Archive *fout, int *numTables)
RELKIND_SEQUENCE,
RELKIND_RELATION, RELKIND_SEQUENCE,
RELKIND_VIEW, RELKIND_COMPOSITE_TYPE,
- RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+ RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+ RELKIND_PARTITIONED_REL);
destroyPQExpBuffer(acl_subquery);
destroyPQExpBuffer(racl_subquery);
@@ -5762,6 +5769,7 @@ getTables(Archive *fout, int *numTables)
i_checkoption = PQfnumber(res, "checkoption");
i_toastreloptions = PQfnumber(res, "toast_reloptions");
i_reloftype = PQfnumber(res, "reloftype");
+ i_partkeydef = PQfnumber(res, "partkeydef");
if (dopt->lockWaitTimeout && fout->remoteVersion >= 70300)
{
@@ -5832,6 +5840,7 @@ getTables(Archive *fout, int *numTables)
else
tblinfo[i].checkoption = pg_strdup(PQgetvalue(res, i, i_checkoption));
tblinfo[i].toast_reloptions = pg_strdup(PQgetvalue(res, i, i_toastreloptions));
+ tblinfo[i].partkeydef = pg_strdup(PQgetvalue(res, i, i_partkeydef));
/* other fields were zeroed above */
@@ -5858,7 +5867,9 @@ getTables(Archive *fout, int *numTables)
* NOTE: it'd be kinda nice to lock other relations too, not only
* plain tables, but the backend doesn't presently allow that.
*/
- if (tblinfo[i].dobj.dump && tblinfo[i].relkind == RELKIND_RELATION)
+ if (tblinfo[i].dobj.dump &&
+ (tblinfo[i].relkind == RELKIND_RELATION ||
+ tblinfo->relkind == RELKIND_PARTITIONED_REL))
{
resetPQExpBuffer(query);
appendPQExpBuffer(query,
@@ -5945,7 +5956,10 @@ getInherits(Archive *fout, int *numInherits)
/* find all the inheritance information */
- appendPQExpBufferStr(query, "SELECT inhrelid, inhparent FROM pg_inherits");
+ appendPQExpBufferStr(query,
+ "SELECT inhrelid, inhparent "
+ "FROM pg_inherits "
+ "WHERE inhparent NOT IN (SELECT oid FROM pg_class WHERE relkind = 'P')");
res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
@@ -15268,6 +15282,9 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
appendPQExpBufferChar(q, ')');
}
+ if (tbinfo->relkind == RELKIND_PARTITIONED_REL)
+ appendPQExpBuffer(q, "\n%s", tbinfo->partkeydef);
+
if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
appendPQExpBuffer(q, "\nSERVER %s", fmtId(srvname));
}
@@ -15327,6 +15344,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
*/
if (dopt->binary_upgrade &&
(tbinfo->relkind == RELKIND_RELATION ||
+ tbinfo->relkind == RELKIND_PARTITIONED_REL ||
tbinfo->relkind == RELKIND_FOREIGN_TABLE))
{
for (j = 0; j < tbinfo->numatts; j++)
@@ -15345,7 +15363,8 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
appendStringLiteralAH(q, fmtId(tbinfo->dobj.name), fout);
appendPQExpBufferStr(q, "::pg_catalog.regclass;\n");
- if (tbinfo->relkind == RELKIND_RELATION)
+ if (tbinfo->relkind == RELKIND_RELATION ||
+ tbinfo->relkind == RELKIND_PARTITIONED_REL)
appendPQExpBuffer(q, "ALTER TABLE ONLY %s ",
fmtId(tbinfo->dobj.name));
else
@@ -15562,6 +15581,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
* dump properties we only have ALTER TABLE syntax for
*/
if ((tbinfo->relkind == RELKIND_RELATION ||
+ tbinfo->relkind == RELKIND_PARTITIONED_REL ||
tbinfo->relkind == RELKIND_MATVIEW) &&
tbinfo->relreplident != REPLICA_IDENTITY_DEFAULT)
{
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 7314cbe..3094425 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -283,6 +283,7 @@ typedef struct _tableInfo
bool *inhNotNull; /* true if NOT NULL is inherited */
struct _attrDefInfo **attrdefs; /* DEFAULT expressions */
struct _constraintInfo *checkexprs; /* CHECK constraints */
+ char *partkeydef; /* partition key definition */
/*
* Stuff computed only for dumpable tables.
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 0a771bd..78a1fdb 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -764,6 +764,7 @@ permissionsList(const char *pattern)
" WHEN 'm' THEN '%s'"
" WHEN 'S' THEN '%s'"
" WHEN 'f' THEN '%s'"
+ " WHEN 'P' THEN '%s'"
" END as \"%s\",\n"
" ",
gettext_noop("Schema"),
@@ -773,6 +774,7 @@ permissionsList(const char *pattern)
gettext_noop("materialized view"),
gettext_noop("sequence"),
gettext_noop("foreign table"),
+ gettext_noop("partitioned table"),
gettext_noop("Type"));
printACLColumn(&buf, "c.relacl");
@@ -819,7 +821,7 @@ permissionsList(const char *pattern)
appendPQExpBufferStr(&buf, "\nFROM pg_catalog.pg_class c\n"
" LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace\n"
- "WHERE c.relkind IN ('r', 'v', 'm', 'S', 'f')\n");
+ "WHERE c.relkind IN ('r', 'v', 'm', 'S', 'f', 'P')\n");
/*
* Unless a schema pattern is specified, we suppress system and temp
@@ -1463,8 +1465,8 @@ describeOneTableDetails(const char *schemaname,
* types, and foreign tables (c.f. CommentObject() in comment.c).
*/
if (tableinfo.relkind == 'r' || tableinfo.relkind == 'v' ||
- tableinfo.relkind == 'm' ||
- tableinfo.relkind == 'f' || tableinfo.relkind == 'c')
+ tableinfo.relkind == 'm' || tableinfo.relkind == 'f' ||
+ tableinfo.relkind == 'c' || tableinfo.relkind == 'P')
appendPQExpBufferStr(&buf, ", pg_catalog.col_description(a.attrelid, a.attnum)");
}
@@ -1529,6 +1531,14 @@ describeOneTableDetails(const char *schemaname,
printfPQExpBuffer(&title, _("Foreign table \"%s.%s\""),
schemaname, relationname);
break;
+ case 'P':
+ if (tableinfo.relpersistence == 'u')
+ printfPQExpBuffer(&title, _("Unlogged partitioned table \"%s.%s\""),
+ schemaname, relationname);
+ else
+ printfPQExpBuffer(&title, _("Partitioned table \"%s.%s\""),
+ schemaname, relationname);
+ break;
default:
/* untranslated unknown relkind */
printfPQExpBuffer(&title, "?%c? \"%s.%s\"",
@@ -1542,8 +1552,8 @@ describeOneTableDetails(const char *schemaname,
cols = 2;
if (tableinfo.relkind == 'r' || tableinfo.relkind == 'v' ||
- tableinfo.relkind == 'm' ||
- tableinfo.relkind == 'f' || tableinfo.relkind == 'c')
+ tableinfo.relkind == 'm' || tableinfo.relkind == 'f' ||
+ tableinfo.relkind == 'c' || tableinfo.relkind == 'P')
{
show_modifiers = true;
headers[cols++] = gettext_noop("Modifiers");
@@ -1563,12 +1573,12 @@ describeOneTableDetails(const char *schemaname,
{
headers[cols++] = gettext_noop("Storage");
if (tableinfo.relkind == 'r' || tableinfo.relkind == 'm' ||
- tableinfo.relkind == 'f')
+ tableinfo.relkind == 'f' || tableinfo.relkind == 'P')
headers[cols++] = gettext_noop("Stats target");
/* Column comments, if the relkind supports this feature. */
if (tableinfo.relkind == 'r' || tableinfo.relkind == 'v' ||
- tableinfo.relkind == 'm' ||
- tableinfo.relkind == 'c' || tableinfo.relkind == 'f')
+ tableinfo.relkind == 'm' || tableinfo.relkind == 'c' ||
+ tableinfo.relkind == 'f' || tableinfo.relkind == 'P')
headers[cols++] = gettext_noop("Description");
}
@@ -1668,7 +1678,7 @@ describeOneTableDetails(const char *schemaname,
/* Statistics target, if the relkind supports this feature */
if (tableinfo.relkind == 'r' || tableinfo.relkind == 'm' ||
- tableinfo.relkind == 'f')
+ tableinfo.relkind == 'f' || tableinfo.relkind == 'P')
{
printTableAddCell(&cont, PQgetvalue(res, i, firstvcol + 1),
false, false);
@@ -1676,14 +1686,33 @@ describeOneTableDetails(const char *schemaname,
/* Column comments, if the relkind supports this feature. */
if (tableinfo.relkind == 'r' || tableinfo.relkind == 'v' ||
- tableinfo.relkind == 'm' ||
- tableinfo.relkind == 'c' || tableinfo.relkind == 'f')
+ tableinfo.relkind == 'm' || tableinfo.relkind == 'c' ||
+ tableinfo.relkind == 'f' || tableinfo.relkind == 'P')
printTableAddCell(&cont, PQgetvalue(res, i, firstvcol + 2),
false, false);
}
}
/* Make footers */
+ if (pset.sversion >= 90600 && tableinfo.relkind == 'P')
+ {
+ /* Get the partition key information */
+ PGresult *result;
+ char *partkeydef;
+
+ printfPQExpBuffer(&buf,
+ "SELECT pg_catalog.pg_get_partkeydef('%s'::pg_catalog.oid);",
+ oid);
+ result = PSQLexec(buf.data);
+ if (!result || PQntuples(result) != 1)
+ goto error_return;
+
+ partkeydef = PQgetvalue(result, 0, 0);
+ printfPQExpBuffer(&tmpbuf, _("Partition Key: %s"), partkeydef);
+ printTableAddFooter(&cont, tmpbuf.data);
+ PQclear(result);
+ }
+
if (tableinfo.relkind == 'i')
{
/* Footer information about an index */
@@ -1822,7 +1851,7 @@ describeOneTableDetails(const char *schemaname,
PQclear(result);
}
else if (tableinfo.relkind == 'r' || tableinfo.relkind == 'm' ||
- tableinfo.relkind == 'f')
+ tableinfo.relkind == 'f' || tableinfo.relkind == 'P')
{
/* Footer information about a table */
PGresult *result = NULL;
@@ -2381,7 +2410,7 @@ describeOneTableDetails(const char *schemaname,
* Finish printing the footer information about a table.
*/
if (tableinfo.relkind == 'r' || tableinfo.relkind == 'm' ||
- tableinfo.relkind == 'f')
+ tableinfo.relkind == 'f' || tableinfo.relkind == 'P')
{
PGresult *result;
int tuples;
@@ -2592,7 +2621,7 @@ add_tablespace_footer(printTableContent *const cont, char relkind,
Oid tablespace, const bool newline)
{
/* relkinds for which we support tablespaces */
- if (relkind == 'r' || relkind == 'm' || relkind == 'i')
+ if (relkind == 'r' || relkind == 'm' || relkind == 'i' || relkind == 'P')
{
/*
* We ignore the database default tablespace so that users not using
@@ -2926,6 +2955,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
" WHEN 'S' THEN '%s'"
" WHEN 's' THEN '%s'"
" WHEN 'f' THEN '%s'"
+ " WHEN 'P' THEN '%s'"
" END as \"%s\",\n"
" pg_catalog.pg_get_userbyid(c.relowner) as \"%s\"",
gettext_noop("Schema"),
@@ -2937,6 +2967,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
gettext_noop("sequence"),
gettext_noop("special"),
gettext_noop("foreign table"),
+ gettext_noop("partitioned table"),
gettext_noop("Type"),
gettext_noop("Owner"));
@@ -2975,7 +3006,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
appendPQExpBufferStr(&buf, "\nWHERE c.relkind IN (");
if (showTables)
- appendPQExpBufferStr(&buf, "'r',");
+ appendPQExpBufferStr(&buf, "'r', 'P',");
if (showViews)
appendPQExpBufferStr(&buf, "'v',");
if (showMatViews)
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index a62ffe6..741ac46 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -427,7 +427,7 @@ static const SchemaQuery Query_for_list_of_tables = {
/* catname */
"pg_catalog.pg_class c",
/* selcondition */
- "c.relkind IN ('r')",
+ "c.relkind IN ('r', 'P')",
/* viscondition */
"pg_catalog.pg_table_is_visible(c.oid)",
/* namespace */
@@ -458,7 +458,7 @@ static const SchemaQuery Query_for_list_of_updatables = {
/* catname */
"pg_catalog.pg_class c",
/* selcondition */
- "c.relkind IN ('r', 'f', 'v')",
+ "c.relkind IN ('r', 'f', 'v', 'P')",
/* viscondition */
"pg_catalog.pg_table_is_visible(c.oid)",
/* namespace */
@@ -488,7 +488,7 @@ static const SchemaQuery Query_for_list_of_tsvmf = {
/* catname */
"pg_catalog.pg_class c",
/* selcondition */
- "c.relkind IN ('r', 'S', 'v', 'm', 'f')",
+ "c.relkind IN ('r', 'S', 'v', 'm', 'f', 'P')",
/* viscondition */
"pg_catalog.pg_table_is_visible(c.oid)",
/* namespace */
diff --git a/src/include/catalog/indexing.h b/src/include/catalog/indexing.h
index ca5eb3d..cadb741 100644
--- a/src/include/catalog/indexing.h
+++ b/src/include/catalog/indexing.h
@@ -319,6 +319,9 @@ DECLARE_UNIQUE_INDEX(pg_replication_origin_roiident_index, 6001, on pg_replicati
DECLARE_UNIQUE_INDEX(pg_replication_origin_roname_index, 6002, on pg_replication_origin using btree(roname text_pattern_ops));
#define ReplicationOriginNameIndex 6002
+DECLARE_UNIQUE_INDEX(pg_partitioned_partedrelid_index, 3351, on pg_partitioned using btree(partedrelid oid_ops));
+#define PartitionedRelidIndexId 3351
+
/* last step of initialization script: build the indexes declared above */
BUILD_INDICES
diff --git a/src/include/catalog/partition.h b/src/include/catalog/partition.h
new file mode 100644
index 0000000..2c876c4
--- /dev/null
+++ b/src/include/catalog/partition.h
@@ -0,0 +1,55 @@
+/*-------------------------------------------------------------------------
+ *
+ * partition.h
+ * Header file for structures and utility functions related to
+ * partitioning
+ *
+ * Copyright (c) 2007-2016, PostgreSQL Global Development Group
+ *
+ * src/include/utils/partition.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PARTITION_H
+#define PARTITION_H
+
+#include "fmgr.h"
+#include "utils/relcache.h"
+
+/* Type and collation information for partition key columns */
+typedef struct
+{
+ Oid *typid;
+ int32 *typmod;
+ int16 *typlen;
+ bool *typbyval;
+ char *typalign;
+ Oid *typcoll;
+} KeyTypeCollInfo;
+
+/* Partition key information for one level */
+typedef struct PartitionKeyData
+{
+ char strategy; /* partition strategy */
+ int16 partnatts; /* number of partition attributes */
+ AttrNumber *partattrs; /* partition attnums */
+ Oid *partopfamily; /* OIDs of opfamily per col */
+ FmgrInfo *partsupfunc; /* lookup info for support funcs */
+ List *partexprs; /* partition key expressions, if any */
+ KeyTypeCollInfo *tcinfo; /* type and collation info (all columns) */
+} PartitionKeyData;
+
+typedef struct PartitionKeyData *PartitionKey;
+
+extern void StorePartitionKey(Relation rel,
+ char strategy,
+ int16 partnatts,
+ AttrNumber *partattrs,
+ List *partexprs,
+ Oid *partopclass);
+extern void RemovePartitionKeyByRelId(Oid relid);
+extern void RelationBuildPartitionKey(Relation relation);
+extern void FreePartitionKey(PartitionKey key);
+extern bool relid_is_partitioned(Oid relid);
+
+#endif /* PARTITION_H */
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index fdc67c2..149f619 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -153,6 +153,7 @@ DESCR("");
#define RELKIND_RELATION 'r' /* ordinary table */
+#define RELKIND_PARTITIONED_REL 'P' /* partitioned table */
#define RELKIND_INDEX 'i' /* secondary index */
#define RELKIND_SEQUENCE 'S' /* sequence object */
#define RELKIND_TOASTVALUE 't' /* for out-of-line values */
diff --git a/src/include/catalog/pg_partitioned.h b/src/include/catalog/pg_partitioned.h
new file mode 100644
index 0000000..edc52db
--- /dev/null
+++ b/src/include/catalog/pg_partitioned.h
@@ -0,0 +1,67 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_partitioned.h
+ * definition of the system "partitioned" relation (pg_partitioned)
+ * along with the relation's initial contents.
+ *
+ *
+ * Portions Copyright (c) 1996-2015, PostgreSQL Global Development Group
+ *
+ * $PostgreSQL: pgsql/src/include/catalog/pg_partitioned.h $
+ *
+ * NOTES
+ * the genbki.sh script reads this file and generates .bki
+ * information from the DATA() statements.
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_PARTITIONED_REL_H
+#define PG_PARTITIONED_REL_H
+
+#include "catalog/genbki.h"
+
+/* ----------------
+ * pg_partitioned definition. cpp turns this into
+ * typedef struct FormData_pg_partitioned
+ * ----------------
+ */
+#define PartitionedRelationId 3350
+
+CATALOG(pg_partitioned,3350) BKI_WITHOUT_OIDS
+{
+ Oid partedrelid; /* partitioned table oid */
+ char partstrat; /* partition key strategy */
+ int16 partnatts; /* number of partition key columns */
+
+ /* variable-length fields start here, but we allow direct access to partattrs */
+ int2vector partattrs; /* attribute numbers of partition key
+ * columns */
+
+#ifdef CATALOG_VARLEN
+ oidvector partclass; /* operator class to compare keys */
+ pg_node_tree partexprs; /* expression trees for partition key members
+ * that are not simple column references; one
+ * for each zero entry in partkey[] */
+#endif
+} FormData_pg_partitioned;
+
+/* ----------------
+ * Form_pg_partitioned corresponds to a pointer to a tuple with
+ * the format of pg_partitioned relation.
+ * ----------------
+ */
+typedef FormData_pg_partitioned *Form_pg_partitioned;
+
+/* ----------------
+ * compiler constants for pg_partitioned
+ * ----------------
+ */
+#define Natts_pg_partitioned 6
+#define Anum_pg_partitioned_partedrelid 1
+#define Anum_pg_partitioned_partstrat 2
+#define Anum_pg_partitioned_partnatts 3
+#define Anum_pg_partitioned_partattrs 4
+#define Anum_pg_partitioned_partclass 5
+#define Anum_pg_partitioned_partexprs 6
+
+#endif /* PG_PARTITIONED_REL_H */
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index bb539d4..92a7260 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -1969,6 +1969,8 @@ DATA(insert OID = 1642 ( pg_get_userbyid PGNSP PGUID 12 1 0 0 0 f f f f t f
DESCR("role name by OID (with fallback)");
DATA(insert OID = 1643 ( pg_get_indexdef PGNSP PGUID 12 1 0 0 0 f f f f t f s s 1 0 25 "26" _null_ _null_ _null_ _null_ _null_ pg_get_indexdef _null_ _null_ _null_ ));
DESCR("index description");
+DATA(insert OID = 3352 ( pg_get_partkeydef PGNSP PGUID 12 1 0 0 0 f f f f t f s s 1 0 25 "26" _null_ _null_ _null_ _null_ _null_ pg_get_partkeydef _null_ _null_ _null_ ));
+DESCR("partition key description");
DATA(insert OID = 1662 ( pg_get_triggerdef PGNSP PGUID 12 1 0 0 0 f f f f t f s s 1 0 25 "26" _null_ _null_ _null_ _null_ _null_ pg_get_triggerdef _null_ _null_ _null_ ));
DESCR("trigger description");
DATA(insert OID = 1387 ( pg_get_constraintdef PGNSP PGUID 12 1 0 0 0 f f f f t f s s 1 0 25 "26" _null_ _null_ _null_ _null_ _null_ pg_get_constraintdef _null_ _null_ _null_ ));
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index 28024f8..ceeb41f 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -42,8 +42,8 @@ extern bool CheckIndexCompatible(Oid oldId,
List *attributeList,
List *exclusionOpNames);
extern Oid GetDefaultOpClass(Oid type_id, Oid am_id);
-extern Oid GetIndexOpClass(List *opclass, Oid attrType,
- char *accessMethodName, Oid accessMethodId);
+extern Oid GetIndexOpClass(List *opclass, Oid attrType,
+ char *accessMethodName, Oid accessMethodId);
/* commands/functioncmds.c */
extern ObjectAddress CreateFunction(CreateFunctionStmt *stmt, const char *queryString);
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
index 01976a1..027665c 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -720,6 +720,7 @@ extern Datum pg_get_viewdef_wrap(PG_FUNCTION_ARGS);
extern Datum pg_get_viewdef_name(PG_FUNCTION_ARGS);
extern Datum pg_get_viewdef_name_ext(PG_FUNCTION_ARGS);
extern Datum pg_get_indexdef(PG_FUNCTION_ARGS);
+extern Datum pg_get_partkeydef(PG_FUNCTION_ARGS);
extern Datum pg_get_indexdef_ext(PG_FUNCTION_ARGS);
extern Datum pg_get_triggerdef(PG_FUNCTION_ARGS);
extern Datum pg_get_triggerdef_ext(PG_FUNCTION_ARGS);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b5d82d6..cf91ef8 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -90,6 +90,8 @@ typedef struct RelationData
/* use "struct" here to avoid needing to include rowsecurity.h: */
struct RowSecurityDesc *rd_rsdesc; /* row security policies, or NULL */
+ struct PartitionKeyData *rd_partkey; /* partition key, or NULL */
+
/* data managed by RelationGetIndexList: */
List *rd_indexlist; /* list of OIDs of indexes on relation */
Oid rd_oidindex; /* OID of unique index on OID, if any */
diff --git a/src/include/utils/syscache.h b/src/include/utils/syscache.h
index 256615b..e727842 100644
--- a/src/include/utils/syscache.h
+++ b/src/include/utils/syscache.h
@@ -72,6 +72,7 @@ enum SysCacheIdentifier
OPEROID,
OPFAMILYAMNAMENSP,
OPFAMILYOID,
+ PARTEDRELID,
PROCNAMEARGSNSP,
PROCOID,
RANGETYPE,
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index aaae9f9..a4cc948 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -326,9 +326,35 @@ ERROR: cannot use a constant expression as partition key
CREATE TABLE fail_agg_in_key (
a int
) PARTITION BY RANGE ((avg(a)));
-ERROR: aggregate functions are not allowed in partition key expressions
+ERROR: aggregate functions are not allowed in partition key expression
CREATE TABLE fail_window_fun_in_key (
a int,
b int
) PARTITION BY RANGE ((avg(a) OVER (PARTITION BY b)));
-ERROR: window functions are not allowed in partition key expressions
+ERROR: window functions are not allowed in partition key expression
+-- check relkind
+CREATE TABLE check_relkind (
+ a int
+) PARTITION BY RANGE (a);
+SELECT relkind FROM pg_class WHERE relname = 'check_relkind';
+ relkind
+---------
+ P
+(1 row)
+
+-- cannot drop column that is part of the partition key
+CREATE TABLE no_drop_alter_partcol (
+ a int
+) PARTITION BY RANGE (a);
+ALTER TABLE no_drop_alter_partcol DROP COLUMN a;
+ERROR: cannot drop column named in partition key
+ALTER TABLE no_drop_alter_partcol ALTER COLUMN a TYPE char(5);
+ERROR: cannot alter column named in partition key
+CREATE TABLE no_drop_alter_partexpr (
+ a text
+) PARTITION BY RANGE ((substring(a from 1 for 1)));
+ALTER TABLE no_drop_alter_partexpr DROP COLUMN a;
+ERROR: cannot drop column referenced in partition key expression
+ALTER TABLE no_drop_alter_partcol ALTER COLUMN a TYPE char(5);
+ERROR: cannot alter column named in partition key
+DROP TABLE check_relkind, no_drop_alter_partcol, no_drop_alter_partexpr;
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index 1c087a3..b1e473d 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -118,6 +118,7 @@ pg_namespace|t
pg_opclass|t
pg_operator|t
pg_opfamily|t
+pg_partitioned|t
pg_pltemplate|t
pg_policy|t
pg_proc|t
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index df47656..022f38b 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -337,3 +337,24 @@ CREATE TABLE fail_window_fun_in_key (
a int,
b int
) PARTITION BY RANGE ((avg(a) OVER (PARTITION BY b)));
+
+-- check relkind
+CREATE TABLE check_relkind (
+ a int
+) PARTITION BY RANGE (a);
+SELECT relkind FROM pg_class WHERE relname = 'check_relkind';
+
+-- cannot drop column that is part of the partition key
+CREATE TABLE no_drop_alter_partcol (
+ a int
+) PARTITION BY RANGE (a);
+ALTER TABLE no_drop_alter_partcol DROP COLUMN a;
+ALTER TABLE no_drop_alter_partcol ALTER COLUMN a TYPE char(5);
+
+CREATE TABLE no_drop_alter_partexpr (
+ a text
+) PARTITION BY RANGE ((substring(a from 1 for 1)));
+ALTER TABLE no_drop_alter_partexpr DROP COLUMN a;
+ALTER TABLE no_drop_alter_partcol ALTER COLUMN a TYPE char(5);
+
+DROP TABLE check_relkind, no_drop_alter_partcol, no_drop_alter_partexpr;
--
1.7.1
[text/x-diff] 0003-Add-syntax-to-create-partitions-v1.patch (78.0K, ../../57106F9E.3000309@lab.ntt.co.jp/4-0003-Add-syntax-to-create-partitions-v1.patch)
download | inline diff:
From 558d1f9514997084de3c4c551bb2738b71c0c23c Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Thu, 3 Mar 2016 18:29:15 +0900
Subject: [PATCH 3/5] Add syntax to create partitions.
As follows:
CREATE TABLE name PARTITION OF parent
[ ( { column_name WITH OPTION column_constraint | table_constraint } [, ...] ) ]
partition_bound_spec [ PARTITION BY partition_key_spec ]
ALTER TABLE parent ATTACH PARTITION name
partition_bound_spec [ VALIDATE | NO VALIDATE ]
ALTER TABLE parent DETACH PARTITION name
partition_bound_spec refers to either list partition bound of form:
FOR VALUES IN (list-of-expressions)
or range partition bound of form:
FOR VALUES range_spec
where range_spec is of one of:
START (lower-bound) [ INCLUSIVE | EXCLUSIVE ]
END (upper-bound) [ INCLUSIVE | EXCLUSIVE ]
START (lower-bound) [ INCLUSIVE | EXCLUSIVE ] END (upper-bound) [ INCLUSIVE | EXCLUSIVE ]
In the above, each of lower-bound and upper-bound is either a single
expression for a single column key or list of expressions for a multi-
column key or empty. Note that in the absence of explicit specification,
lower-bound is inclusive and upper-bound is exclusive.
A partition is created as inheritance child of the parent with certain
differences from regular inheritance behavior. For example, when a new
partition is created, it cannot contain its own column definitions or
constraints (unlike CREATE TABLE child(...) INHERITS(parent)). When
"attaching" a table as partition of parent, it cannot contain columns
that are not contained in the parent (unlike ALTER TABLE child INHERIT
parent). No dependency is created between a partition and the parent.
One cannot create regular inheritance children of a partitioned table.
Whereas this commit enables inheritance, it does not yet store partition
bound info anywhere, IOW, the fact that a given inheritance child is a
"partition" is not persisted.
---
doc/src/sgml/ref/alter_table.sgml | 57 +++++
doc/src/sgml/ref/create_table.sgml | 77 ++++++-
src/backend/commands/createas.c | 1 +
src/backend/commands/sequence.c | 1 +
src/backend/commands/tablecmds.c | 490 +++++++++++++++++++++++++++---------
src/backend/commands/view.c | 1 +
src/backend/nodes/copyfuncs.c | 48 ++++
src/backend/nodes/equalfuncs.c | 42 +++
src/backend/nodes/outfuncs.c | 27 ++
src/backend/parser/gram.y | 167 ++++++++++++-
src/backend/parser/parse_agg.c | 7 +-
src/backend/parser/parse_expr.c | 23 ++
src/backend/parser/parse_utilcmd.c | 318 +++++++++++++++++++++++
src/include/catalog/partition.h | 1 +
src/include/nodes/nodes.h | 3 +
src/include/nodes/parsenodes.h | 43 +++-
src/include/parser/kwlist.h | 3 +
src/include/parser/parse_node.h | 3 +-
18 files changed, 1182 insertions(+), 130 deletions(-)
diff --git a/doc/src/sgml/ref/alter_table.sgml b/doc/src/sgml/ref/alter_table.sgml
index 5ca211e..97744c9 100644
--- a/doc/src/sgml/ref/alter_table.sgml
+++ b/doc/src/sgml/ref/alter_table.sgml
@@ -77,12 +77,15 @@ ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable>
NOT OF
OWNER TO { <replaceable class="PARAMETER">new_owner</replaceable> | CURRENT_USER | SESSION_USER }
REPLICA IDENTITY { DEFAULT | USING INDEX <replaceable class="PARAMETER">index_name</replaceable> | FULL | NOTHING }
+ ATTACH PARTITION <replaceable class="PARAMETER">name</replaceable> <replaceable class="PARAMETER">partition_bound_spec</replaceable> [ VALIDATE | NO VALIDATE ]
+ DETACH PARTITION <replaceable class="PARAMETER">name</replaceable>
<phrase>and <replaceable class="PARAMETER">table_constraint_using_index</replaceable> is:</phrase>
[ CONSTRAINT <replaceable class="PARAMETER">constraint_name</replaceable> ]
{ UNIQUE | PRIMARY KEY } USING INDEX <replaceable class="PARAMETER">index_name</replaceable>
[ DEFERRABLE | NOT DEFERRABLE ] [ INITIALLY DEFERRED | INITIALLY IMMEDIATE ]
+
</synopsis>
</refsynopsisdiv>
@@ -704,6 +707,51 @@ ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable>
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>ATTACH PARTITION</literal></term>
+ <listitem>
+ <para>
+ This form attaches an existing table as partition of the target table,
+ provided the target table is partitioned. The partition bound
+ specification must correspond with partition method and key of the
+ partitioned table. The table being attached must have all the columns
+ as the target table and no more. Also, it must have all the matching
+ constraints as the target table. That includes <literal>NOT NULL</>
+ and <literal>CHECK</> constraints. <literal>UNIQUE</literal>,
+ <literal>PRIMARY KEY</literal>, and <literal>FOREIGN KEY</literal>
+ constraints are not considered, but this might change in the future.
+ </para>
+
+ <para>
+ If <literal>NO VALIDATE</literal> clause is specified, existing rows in
+ source table are are not checked to see if they violate the partition
+ bound specification. This allows to quickly load a large amount of data
+ into the partitioned table. The default behavior is to perform the
+ check, as if <literal>VALIDATE</literal> clause were specified.
+ </para>
+
+ <para>
+ Note that once the table becomes a partition, it will be dropped or
+ truncated when the parent table is dropped or truncated. Dropping
+ it directly using <literal>DROP TABLE</literal> will fail; it must
+ first be detached from the parent. However, truncating a partition
+ directly works.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><literal>DETACH PARTITION</literal></term>
+ <listitem>
+ <para>
+ This form detaches specified partition of the target table, provided the
+ target table is partitioned and the specified partition exists. The
+ resulting table continues to exist as a standalone table with no ties
+ remaining with the parent partitioned table.
+ </para>
+ </listitem>
+ </varlistentry>
+
</variablelist>
</para>
@@ -936,6 +984,15 @@ ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable>
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><replaceable class="PARAMETER">partition_bound_spec</replaceable></term>
+ <listitem>
+ <para>
+ The partition bound specification for a new partition.
+ </para>
+ </listitem>
+ </varlistentry>
+
</variablelist>
</refsect1>
diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml
index 3da79a8..5e6a758 100644
--- a/doc/src/sgml/ref/create_table.sgml
+++ b/doc/src/sgml/ref/create_table.sgml
@@ -44,6 +44,17 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
[ ON COMMIT { PRESERVE ROWS | DELETE ROWS | DROP } ]
[ TABLESPACE <replaceable class="PARAMETER">tablespace_name</replaceable> ]
+CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name</replaceable>
+ PARTITION OF <replaceable class="PARAMETER">parent_table</replaceable> [ (
+ { <replaceable class="PARAMETER">column_name</replaceable> WITH OPTIONS [ <replaceable class="PARAMETER">column_constraint</replaceable> [ ... ] ]
+ | <replaceable>table_constraint</replaceable> }
+ [, ... ]
+) ] <replaceable class="PARAMETER">partition_bound_spec</replaceable>
+[ PARTITION BY {RANGE | LIST} ( { <replaceable class="parameter">column_name</replaceable> | ( <replaceable class="parameter">expression</replaceable> ) } [ <replaceable class="parameter">opclass</replaceable> ] [, ...] )
+[ WITH ( <replaceable class="PARAMETER">storage_parameter</replaceable> [= <replaceable class="PARAMETER">value</replaceable>] [, ... ] ) | WITH OIDS | WITHOUT OIDS ]
+[ ON COMMIT { PRESERVE ROWS | DELETE ROWS | DROP } ]
+[ TABLESPACE <replaceable class="PARAMETER">tablespace_name</replaceable> ]
+
<phrase>where <replaceable class="PARAMETER">column_constraint</replaceable> is:</phrase>
[ CONSTRAINT <replaceable class="PARAMETER">constraint_name</replaceable> ]
@@ -72,6 +83,10 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
{ INCLUDING | EXCLUDING } { DEFAULTS | CONSTRAINTS | INDEXES | STORAGE | COMMENTS | ALL }
+<phrase>and <replaceable class="PARAMETER">partition_bound_spec</replaceable> is:</phrase>
+
+FOR VALUES { <replaceable class="PARAMETER">list_spec</replaceable> | <replaceable class="PARAMETER">range_spec</replaceable> }
+
<phrase><replaceable class="PARAMETER">index_parameters</replaceable> in <literal>UNIQUE</literal>, <literal>PRIMARY KEY</literal>, and <literal>EXCLUDE</literal> constraints are:</phrase>
[ WITH ( <replaceable class="PARAMETER">storage_parameter</replaceable> [= <replaceable class="PARAMETER">value</replaceable>] [, ... ] ) ]
@@ -80,8 +95,20 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
<phrase><replaceable class="PARAMETER">exclude_element</replaceable> in an <literal>EXCLUDE</literal> constraint is:</phrase>
{ <replaceable class="parameter">column_name</replaceable> | ( <replaceable class="parameter">expression</replaceable> ) } [ <replaceable class="parameter">opclass</replaceable> ] [ ASC | DESC ] [ NULLS { FIRST | LAST } ]
-</synopsis>
+<phrase><replaceable class="PARAMETER">list_spec</replaceable> in <literal>FOR VALUES</literal> is:</phrase>
+
+{ IN ( <replaceable class="PARAMETER">expression</replaceable> [, ...] ) }
+
+<phrase><replaceable class="PARAMETER">range_spec</replaceable> in <literal>FOR VALUES</literal> is:</phrase>
+
+{ START (<replaceable class="PARAMETER">lower-bound</replaceable>) [ INCLUSIVE | EXCLUSIVE ] | END (<replaceable class="PARAMETER">upper-bound</replaceable>) [ INCLUSIVE | EXCLUSIVE ] | START (<replaceable class="PARAMETER">lower-bound</replaceable>) [ INCLUSIVE | EXCLUSIVE ] END (<replaceable class="PARAMETER">upper-bound</replaceable>) [ INCLUSIVE | EXCLUSIVE ] }
+
+<phrase>where <replaceable class="PARAMETER">lower-bound</replaceable> and <replaceable class="PARAMETER">upper-bound</replaceable> are:</phrase>
+
+{ <replaceable class="PARAMETER">expression</replaceable> [, ...] }
+
+</synopsis>
</refsynopsisdiv>
<refsect1 id="SQL-CREATETABLE-description">
@@ -232,6 +259,49 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
</varlistentry>
<varlistentry>
+ <term><literal>PARTITION OF <replaceable class="PARAMETER">parent_table</replaceable></literal></term>
+ <listitem>
+ <para>
+ Creates the table as <firstterm>partition</firstterm> of the specified
+ parent table (name optionally schema-qualified).
+ </para>
+
+ <para>
+ A partition bound specification must be present and must correspond with
+ partition method and key of the parent table. It is checked using the
+ specification that the new partition does not overlap with any existing
+ partitions of the parent.
+ </para>
+
+ <para>
+ A partition cannot have columns other than those inherited from the
+ parent. That includes the <structfield>oid</> column, which can be
+ specified using the <literal>WITH (OIDS)</literal> clause. On the other
+ hand, if parent has the <structfield>oid</> column, the partition
+ inherits the same, overriding the <literal>WITH (OIDS=FALSE)</literal>
+ clause, if any. Defaults and constraints can optionally be specified
+ for each of the inherited columns, which override those in the parent.
+ One can also specify table constraints, in addition to those inherited
+ from the parent. Note that all subsequent schema modifications to the
+ parent propagate to partition.
+ </para>
+
+ <para>
+ Any data row subsequently inserted into the parent table is mapped to
+ and stored in the partition, provided partition key of the row falls
+ within the partition bounds.
+ </para>
+
+ <para>
+ A partition is dropped or truncated when the parent table is dropped or
+ truncated. Dropping it directly using <literal>DROP TABLE</literal>
+ will fail; it must first be <firstterm>detached</> from the parent.
+ However, truncating a partition directly works.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><replaceable class="PARAMETER">column_name</replaceable></term>
<listitem>
<para>
@@ -340,6 +410,11 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
</para>
<para>
+ When specifying for a table being created as partition, one needs to
+ use column names from the parent table as part of the key.
+ </para>
+
+ <para>
Currently, there are following limitations on definition of partitioned
tables: one cannot specify any UNIQUE, PRIMARY KEY, EXCLUDE and/or
FOREIGN KEY constraints.
diff --git a/src/backend/commands/createas.c b/src/backend/commands/createas.c
index cb7a145..427470c 100644
--- a/src/backend/commands/createas.c
+++ b/src/backend/commands/createas.c
@@ -342,6 +342,7 @@ intorel_startup(DestReceiver *self, int operation, TupleDesc typeinfo)
col->is_local = true;
col->is_not_null = false;
col->is_from_type = false;
+ col->is_for_partition = false;
col->storage = 0;
col->raw_default = NULL;
col->cooked_default = NULL;
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index 5ecad82..d039dbd 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -159,6 +159,7 @@ DefineSequence(CreateSeqStmt *seq)
coldef->is_local = true;
coldef->is_not_null = true;
coldef->is_from_type = false;
+ coldef->is_for_partition = false;
coldef->storage = 0;
coldef->raw_default = NULL;
coldef->cooked_default = NULL;
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 4976bbe..b7d230c 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -279,7 +279,8 @@ typedef struct
static void truncate_check_rel(Relation rel);
static List *MergeAttributes(List *schema, List *supers, char relpersistence,
- List **supOids, List **supconstr, int *supOidCount);
+ List **supOids, List **supconstr, int *supOidCount,
+ bool is_partition);
static bool MergeCheckConstraint(List *constraints, char *name, Node *expr);
static void MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel);
static void MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel);
@@ -448,6 +449,11 @@ static void ComputePartitionAttrs(Oid relid, List *partParams,
static bool find_attr_reference_walker(Node *node,
find_attr_reference_context *context);
static bool is_partition_attr(Relation rel, AttrNumber attnum, bool *is_expr);
+static void CreateInheritance(Relation child_rel, Relation parent_rel);
+static void RemoveInheritance(Relation child_rel, Relation parent_rel);
+static ObjectAddress ATExecAttachPartition(List **wqueue, Relation rel,
+ PartitionCmd *cmd);
+static ObjectAddress ATExecDetachPartition(Relation rel, RangeVar *name);
/* ----------------------------------------------------------------
@@ -590,10 +596,16 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
/*
* Look up inheritance ancestors and generate relation schema, including
* inherited attributes.
+ *
+ * The last parameter implicitly specifies that the table is being created
+ * as partition and schema consists of columns definitions corresponding
+ * to non-dropped columns of the parent constructed such that each
+ * attribute of the table is created as inherited and non-local.
*/
schema = MergeAttributes(schema, stmt->inhRelations,
stmt->relation->relpersistence,
- &inheritOids, &old_constraints, &parentOidCount);
+ &inheritOids, &old_constraints, &parentOidCount,
+ stmt->partbound != NULL);
/*
* Create a tuple descriptor from the relation schema. Note that this
@@ -1452,7 +1464,8 @@ storage_name(char c)
*/
static List *
MergeAttributes(List *schema, List *supers, char relpersistence,
- List **supOids, List **supconstr, int *supOidCount)
+ List **supOids, List **supconstr, int *supOidCount,
+ bool is_partition)
{
ListCell *entry;
List *inhSchema = NIL;
@@ -1497,8 +1510,8 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
/*
* Typed table column option that does not belong to a column from
- * the type. This works because the columns from the type come
- * first in the list.
+ * the type or the partition parent. This works because the columns
+ * from the type or the partition parent come first in the list.
*/
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_COLUMN),
@@ -1525,6 +1538,18 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
coldef->is_from_type = false;
list_delete_cell(schema, rest, prev);
}
+ else if (coldef->is_for_partition)
+ {
+ /*
+ * merge the column options into the column from the parent
+ */
+ coldef->is_not_null = restdef->is_not_null;
+ coldef->raw_default = restdef->raw_default;
+ coldef->cooked_default = restdef->cooked_default;
+ coldef->constraints = restdef->constraints;
+ coldef->is_for_partition = false; /* job is done */
+ list_delete_cell(schema, rest, prev);
+ }
else
ereport(ERROR,
(errcode(ERRCODE_DUPLICATE_COLUMN),
@@ -1563,8 +1588,12 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
*/
relation = heap_openrv(parent, ShareUpdateExclusiveLock);
- /* Cannot inherit from partitioned tables */
- if (relation->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+ /*
+ * Cannot inherit from a partitioned table unless being created as
+ * partition.
+ */
+ if (relation->rd_rel->relkind == RELKIND_PARTITIONED_REL &&
+ !is_partition)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot inherit from partitioned table \"%s\"",
@@ -1710,6 +1739,7 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
def->is_local = false;
def->is_not_null = attribute->attnotnull;
def->is_from_type = false;
+ def->is_for_partition = false;
def->storage = attribute->attstorage;
def->raw_default = NULL;
def->cooked_default = NULL;
@@ -1838,6 +1868,9 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
* If we had no inherited attributes, the result schema is just the
* explicitly declared columns. Otherwise, we need to merge the declared
* columns into the inherited schema list.
+ *
+ * Note: In case of a partition, there are only inherited attributes that
+ * we copied from the parent as so, in transformPartitionOf().
*/
if (inhSchema != NIL)
{
@@ -1867,16 +1900,20 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
/*
* Yes, try to merge the two column definitions. They must
- * have the same type, typmod, and collation.
+ * have the same type, typmod, and collation. Don't output
+ * the notices, if partition, per note above.
*/
- if (exist_attno == schema_attno)
- ereport(NOTICE,
- (errmsg("merging column \"%s\" with inherited definition",
- attributeName)));
- else
- ereport(NOTICE,
- (errmsg("moving and merging column \"%s\" with inherited definition", attributeName),
- errdetail("User-specified column moved to the position of the inherited column.")));
+ if (!is_partition)
+ {
+ if (exist_attno == schema_attno)
+ ereport(NOTICE,
+ (errmsg("merging column \"%s\" with inherited definition",
+ attributeName)));
+ else
+ ereport(NOTICE,
+ (errmsg("moving and merging column \"%s\" with inherited definition", attributeName),
+ errdetail("User-specified column moved to the position of the inherited column.")));
+ }
def = (ColumnDef *) list_nth(inhSchema, exist_attno - 1);
typenameTypeIdAndMod(NULL, def->typeName, &defTypeId, &deftypmod);
typenameTypeIdAndMod(NULL, newdef->typeName, &newTypeId, &newtypmod);
@@ -1913,8 +1950,9 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
storage_name(def->storage),
storage_name(newdef->storage))));
- /* Mark the column as locally defined */
- def->is_local = true;
+ /* Mark the column as locally defined, unless partition */
+ if (!is_partition)
+ def->is_local = true;
/* Merge of NOT NULL constraints = OR 'em together */
def->is_not_null |= newdef->is_not_null;
/* If new def has a default, override previous default */
@@ -2093,16 +2131,20 @@ StoreCatalogInheritance1(Oid relationId, Oid parentOid,
heap_freetuple(tuple);
/*
- * Store a dependency too
+ * Store a dependency too, iff a regular inheritance relationship, which
+ * it's not, if parent is a partitioned table.
*/
- parentobject.classId = RelationRelationId;
- parentobject.objectId = parentOid;
- parentobject.objectSubId = 0;
- childobject.classId = RelationRelationId;
- childobject.objectId = relationId;
- childobject.objectSubId = 0;
+ if (!relid_is_partitioned(parentOid))
+ {
+ parentobject.classId = RelationRelationId;
+ parentobject.objectId = parentOid;
+ parentobject.objectSubId = 0;
+ childobject.classId = RelationRelationId;
+ childobject.objectId = relationId;
+ childobject.objectSubId = 0;
- recordDependencyOn(&childobject, &parentobject, DEPENDENCY_NORMAL);
+ recordDependencyOn(&childobject, &parentobject, DEPENDENCY_NORMAL);
+ }
/*
* Post creation hook of this inheritance. Since object_access_hook
@@ -3108,6 +3150,11 @@ AlterTableGetLockLevel(List *cmds)
cmd_lockmode = AlterTableGetRelOptionsLockLevel((List *) cmd->def);
break;
+ case AT_AttachPartition:
+ case AT_DetachPartition:
+ cmd_lockmode = AccessExclusiveLock;
+ break;
+
default: /* oops */
elog(ERROR, "unrecognized alter table type: %d",
(int) cmd->subtype);
@@ -3425,6 +3472,12 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
/* No command-specific prep needed */
pass = AT_PASS_MISC;
break;
+ case AT_AttachPartition:
+ case AT_DetachPartition:
+ ATSimplePermissions(rel, ATT_TABLE);
+ /* No command-specific prep needed */
+ pass = AT_PASS_MISC;
+ break;
default: /* oops */
elog(ERROR, "unrecognized alter table type: %d",
(int) cmd->subtype);
@@ -3746,6 +3799,12 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
case AT_GenericOptions:
ATExecGenericOptions(rel, (List *) cmd->def);
break;
+ case AT_AttachPartition:
+ ATExecAttachPartition(wqueue, rel, (PartitionCmd *) cmd->def);
+ break;
+ case AT_DetachPartition:
+ ATExecDetachPartition(rel, ((PartitionCmd *) cmd->def)->name);
+ break;
default: /* oops */
elog(ERROR, "unrecognized alter table type: %d",
(int) cmd->subtype);
@@ -10190,12 +10249,7 @@ ATPrepAddInherit(Relation child_rel)
static ObjectAddress
ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode)
{
- Relation parent_rel,
- catalogRelation;
- SysScanDesc scan;
- ScanKeyData key;
- HeapTuple inheritsTuple;
- int32 inhseqno;
+ Relation parent_rel;
List *children;
ObjectAddress address;
@@ -10240,38 +10294,6 @@ ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode)
errmsg("cannot inherit from partitioned table \"%s\"", parent->relname)));
/*
- * Check for duplicates in the list of parents, and determine the highest
- * inhseqno already present; we'll use the next one for the new parent.
- * (Note: get RowExclusiveLock because we will write pg_inherits below.)
- *
- * Note: we do not reject the case where the child already inherits from
- * the parent indirectly; CREATE TABLE doesn't reject comparable cases.
- */
- catalogRelation = heap_open(InheritsRelationId, RowExclusiveLock);
- ScanKeyInit(&key,
- Anum_pg_inherits_inhrelid,
- BTEqualStrategyNumber, F_OIDEQ,
- ObjectIdGetDatum(RelationGetRelid(child_rel)));
- scan = systable_beginscan(catalogRelation, InheritsRelidSeqnoIndexId,
- true, NULL, 1, &key);
-
- /* inhseqno sequences start at 1 */
- inhseqno = 0;
- while (HeapTupleIsValid(inheritsTuple = systable_getnext(scan)))
- {
- Form_pg_inherits inh = (Form_pg_inherits) GETSTRUCT(inheritsTuple);
-
- if (inh->inhparent == RelationGetRelid(parent_rel))
- ereport(ERROR,
- (errcode(ERRCODE_DUPLICATE_TABLE),
- errmsg("relation \"%s\" would be inherited from more than once",
- RelationGetRelationName(parent_rel))));
- if (inh->inhseqno > inhseqno)
- inhseqno = inh->inhseqno;
- }
- systable_endscan(scan);
-
- /*
* Prevent circularity by seeing if proposed parent inherits from child.
* (In particular, this disallows making a rel inherit from itself.)
*
@@ -10304,6 +10326,69 @@ ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode)
RelationGetRelationName(child_rel),
RelationGetRelationName(parent_rel))));
+ /* OK to create inheritance */
+ CreateInheritance(child_rel, parent_rel);
+
+ ObjectAddressSet(address, RelationRelationId,
+ RelationGetRelid(parent_rel));
+
+ /* keep our lock on the parent relation until commit */
+ heap_close(parent_rel, NoLock);
+
+ return address;
+}
+
+/*
+ * CreateInheritance
+ * Catalog manipulation portion of creating inheritance between a child
+ * table and a parent table.
+ *
+ * Common to ATExecAddInherit() and ATExecAttachPartition().
+ */
+static void
+CreateInheritance(Relation child_rel, Relation parent_rel)
+{
+ Relation catalogRelation;
+ SysScanDesc scan;
+ ScanKeyData key;
+ HeapTuple inheritsTuple;
+ int32 inhseqno;
+
+ /* Note: get RowExclusiveLock because we will write pg_inherits below. */
+ catalogRelation = heap_open(InheritsRelationId, RowExclusiveLock);
+
+ /*
+ * Check for duplicates in the list of parents, and determine the highest
+ * inhseqno already present; we'll use the next one for the new parent.
+ * Also, if proposed child is a partition, it cannot already be inheriting.
+ *
+ * Note: we do not reject the case where the child already inherits from
+ * the parent indirectly; CREATE TABLE doesn't reject comparable cases.
+ */
+ ScanKeyInit(&key,
+ Anum_pg_inherits_inhrelid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(RelationGetRelid(child_rel)));
+ scan = systable_beginscan(catalogRelation, InheritsRelidSeqnoIndexId,
+ true, NULL, 1, &key);
+
+ /* inhseqno sequences start at 1 */
+ inhseqno = 0;
+ while (HeapTupleIsValid(inheritsTuple = systable_getnext(scan)))
+ {
+ Form_pg_inherits inh = (Form_pg_inherits) GETSTRUCT(inheritsTuple);
+
+ if (inh->inhparent == RelationGetRelid(parent_rel))
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_TABLE),
+ errmsg("relation \"%s\" would be inherited from more than once",
+ RelationGetRelationName(parent_rel))));
+
+ if (inh->inhseqno > inhseqno)
+ inhseqno = inh->inhseqno;
+ }
+ systable_endscan(scan);
+
/* Match up the columns and bump attinhcount as needed */
MergeAttributesIntoExisting(child_rel, parent_rel);
@@ -10318,16 +10403,8 @@ ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode)
inhseqno + 1,
catalogRelation);
- ObjectAddressSet(address, RelationRelationId,
- RelationGetRelid(parent_rel));
-
/* Now we're done with pg_inherits */
heap_close(catalogRelation, RowExclusiveLock);
-
- /* keep our lock on the parent relation until commit */
- heap_close(parent_rel, NoLock);
-
- return address;
}
/*
@@ -10378,7 +10455,7 @@ constraints_equivalent(HeapTuple a, HeapTuple b, TupleDesc tupleDesc)
* Check columns in child table match up with columns in parent, and increment
* their attinhcount.
*
- * Called by ATExecAddInherit
+ * Called by CreateInheritance
*
* Currently all parent columns must be found in child. Missing columns are an
* error. One day we might consider creating new columns like CREATE TABLE
@@ -10396,11 +10473,13 @@ MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel)
int parent_natts;
TupleDesc tupleDesc;
HeapTuple tuple;
+ bool is_attach_partition;
attrrel = heap_open(AttributeRelationId, RowExclusiveLock);
tupleDesc = RelationGetDescr(parent_rel);
parent_natts = tupleDesc->natts;
+ is_attach_partition = relid_is_partitioned(RelationGetRelid(parent_rel));
for (parent_attno = 1; parent_attno <= parent_natts; parent_attno++)
{
@@ -10423,14 +10502,16 @@ MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel)
attribute->atttypmod != childatt->atttypmod)
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
- errmsg("child table \"%s\" has different type for column \"%s\"",
+ errmsg("%s table \"%s\" has different type for column \"%s\"",
+ is_attach_partition ? "source" : "child",
RelationGetRelationName(child_rel),
attributeName)));
if (attribute->attcollation != childatt->attcollation)
ereport(ERROR,
(errcode(ERRCODE_COLLATION_MISMATCH),
- errmsg("child table \"%s\" has different collation for column \"%s\"",
+ errmsg("%s table \"%s\" has different collation for column \"%s\"",
+ is_attach_partition ? "source" : "child",
RelationGetRelationName(child_rel),
attributeName)));
@@ -10441,8 +10522,9 @@ MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel)
if (attribute->attnotnull && !childatt->attnotnull)
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
- errmsg("column \"%s\" in child table must be marked NOT NULL",
- attributeName)));
+ errmsg("column \"%s\" in %s table must be marked NOT NULL",
+ attributeName,
+ is_attach_partition ? "source" : "child")));
/*
* OK, bump the child column's inheritance count. (If we fail
@@ -10457,7 +10539,8 @@ MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel)
{
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
- errmsg("child table is missing column \"%s\"",
+ errmsg("%s table is missing column \"%s\"",
+ is_attach_partition ? "source" : "child",
attributeName)));
}
}
@@ -10471,7 +10554,7 @@ MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel)
*
* Constraints that are marked ONLY in the parent are ignored.
*
- * Called by ATExecAddInherit
+ * Called by CreateInheritance
*
* Currently all constraints in parent must be present in the child. One day we
* may consider adding new constraints like CREATE TABLE does.
@@ -10490,9 +10573,11 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
SysScanDesc parent_scan;
ScanKeyData parent_key;
HeapTuple parent_tuple;
+ bool is_attach_partition;
catalog_relation = heap_open(ConstraintRelationId, RowExclusiveLock);
tuple_desc = RelationGetDescr(catalog_relation);
+ is_attach_partition = relid_is_partitioned(RelationGetRelid(parent_rel));
/* Outer loop scans through the parent's constraint definitions */
ScanKeyInit(&parent_key,
@@ -10540,7 +10625,8 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
if (!constraints_equivalent(parent_tuple, child_tuple, tuple_desc))
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
- errmsg("child table \"%s\" has different definition for check constraint \"%s\"",
+ errmsg("%s table \"%s\" has different definition for check constraint \"%s\"",
+ is_attach_partition ? "source" : "child",
RelationGetRelationName(child_rel),
NameStr(parent_con->conname))));
@@ -10548,8 +10634,9 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
if (child_con->connoinherit)
ereport(ERROR,
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
- errmsg("constraint \"%s\" conflicts with non-inherited constraint on child table \"%s\"",
+ errmsg("constraint \"%s\" conflicts with non-inherited constraint on %s table \"%s\"",
NameStr(child_con->conname),
+ is_attach_partition ? "source" : "child",
RelationGetRelationName(child_rel))));
/*
@@ -10572,7 +10659,8 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
if (!found)
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
- errmsg("child table is missing constraint \"%s\"",
+ errmsg("%s table is missing constraint \"%s\"",
+ is_attach_partition ? "source" : "child",
NameStr(parent_con->conname))));
}
@@ -10583,6 +10671,41 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
/*
* ALTER TABLE NO INHERIT
*
+ * Return value is the address of the relation that is no longer parent.
+ */
+static ObjectAddress
+ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
+{
+ ObjectAddress address;
+ Relation parent_rel;
+
+ /*
+ * AccessShareLock on the parent is probably enough, seeing that DROP
+ * TABLE doesn't lock parent tables at all. We need some lock since we'll
+ * be inspecting the parent's schema.
+ */
+ parent_rel = heap_openrv(parent, AccessShareLock);
+
+ /*
+ * We don't bother to check ownership of the parent table --- ownership of
+ * the child is presumed enough rights.
+ */
+
+ /* Off to RemoveInheritance() where most of the work happens */
+ RemoveInheritance(rel, parent_rel);
+
+ /* keep our lock on the parent relation until commit */
+ heap_close(parent_rel, NoLock);
+
+ ObjectAddressSet(address, RelationRelationId,
+ RelationGetRelid(parent_rel));
+
+ return address;
+}
+
+/*
+ * RemoveInheritance
+ *
* Drop a parent from the child's parents. This just adjusts the attinhcount
* and attislocal of the columns and removes the pg_inherit and pg_depend
* entries.
@@ -10596,13 +10719,11 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
* coninhcount and conislocal for inherited constraints are adjusted in
* exactly the same way.
*
- * Return value is the address of the relation that is no longer parent.
+ * Common to ATExecDropInherit() and ATExecDetachPartition().
*/
-static ObjectAddress
-ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
+static void
+RemoveInheritance(Relation child_rel, Relation parent_rel)
{
- Relation parent_rel;
- Oid parent_oid;
Relation catalogRelation;
SysScanDesc scan;
ScanKeyData key[3];
@@ -10611,19 +10732,9 @@ ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
constraintTuple;
List *connames;
bool found = false;
- ObjectAddress address;
+ bool is_detach_partition;
- /*
- * AccessShareLock on the parent is probably enough, seeing that DROP
- * TABLE doesn't lock parent tables at all. We need some lock since we'll
- * be inspecting the parent's schema.
- */
- parent_rel = heap_openrv(parent, AccessShareLock);
-
- /*
- * We don't bother to check ownership of the parent table --- ownership of
- * the child is presumed enough rights.
- */
+ is_detach_partition = relid_is_partitioned(RelationGetRelid(parent_rel));
/*
* Find and destroy the pg_inherits entry linking the two, or error out if
@@ -10633,7 +10744,7 @@ ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
ScanKeyInit(&key[0],
Anum_pg_inherits_inhrelid,
BTEqualStrategyNumber, F_OIDEQ,
- ObjectIdGetDatum(RelationGetRelid(rel)));
+ ObjectIdGetDatum(RelationGetRelid(child_rel)));
scan = systable_beginscan(catalogRelation, InheritsRelidSeqnoIndexId,
true, NULL, 1, key);
@@ -10654,11 +10765,20 @@ ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
heap_close(catalogRelation, RowExclusiveLock);
if (!found)
- ereport(ERROR,
- (errcode(ERRCODE_UNDEFINED_TABLE),
- errmsg("relation \"%s\" is not a parent of relation \"%s\"",
- RelationGetRelationName(parent_rel),
- RelationGetRelationName(rel))));
+ {
+ if (!is_detach_partition)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_TABLE),
+ errmsg("relation \"%s\" is not a parent of relation \"%s\"",
+ RelationGetRelationName(parent_rel),
+ RelationGetRelationName(child_rel))));
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_TABLE),
+ errmsg("relation \"%s\" is not a partition of relation \"%s\"",
+ RelationGetRelationName(child_rel),
+ RelationGetRelationName(parent_rel))));
+ }
/*
* Search through child columns looking for ones matching parent rel
@@ -10667,7 +10787,7 @@ ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
ScanKeyInit(&key[0],
Anum_pg_attribute_attrelid,
BTEqualStrategyNumber, F_OIDEQ,
- ObjectIdGetDatum(RelationGetRelid(rel)));
+ ObjectIdGetDatum(RelationGetRelid(child_rel)));
scan = systable_beginscan(catalogRelation, AttributeRelidNumIndexId,
true, NULL, 1, key);
while (HeapTupleIsValid(attributeTuple = systable_getnext(scan)))
@@ -10729,7 +10849,7 @@ ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
ScanKeyInit(&key[0],
Anum_pg_constraint_conrelid,
BTEqualStrategyNumber, F_OIDEQ,
- ObjectIdGetDatum(RelationGetRelid(rel)));
+ ObjectIdGetDatum(RelationGetRelid(child_rel)));
scan = systable_beginscan(catalogRelation, ConstraintRelidIndexId,
true, NULL, 1, key);
@@ -10760,7 +10880,7 @@ ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
if (copy_con->coninhcount <= 0) /* shouldn't happen */
elog(ERROR, "relation %u has non-inherited constraint \"%s\"",
- RelationGetRelid(rel), NameStr(copy_con->conname));
+ RelationGetRelid(child_rel), NameStr(copy_con->conname));
copy_con->coninhcount--;
if (copy_con->coninhcount == 0)
@@ -10772,30 +10892,22 @@ ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
}
}
- parent_oid = RelationGetRelid(parent_rel);
-
systable_endscan(scan);
heap_close(catalogRelation, RowExclusiveLock);
- drop_parent_dependency(RelationGetRelid(rel),
- RelationRelationId,
- RelationGetRelid(parent_rel));
-
+ /* Remember that dependency exists iff regular inheritance. */
+ if (!is_detach_partition)
+ drop_parent_dependency(RelationGetRelid(child_rel),
+ RelationRelationId,
+ RelationGetRelid(parent_rel));
/*
* Post alter hook of this inherits. Since object_access_hook doesn't take
* multiple object identifiers, we relay oid of parent relation using
* auxiliary_id argument.
*/
InvokeObjectPostAlterHookArg(InheritsRelationId,
- RelationGetRelid(rel), 0,
+ RelationGetRelid(child_rel), 0,
RelationGetRelid(parent_rel), false);
-
- /* keep our lock on the parent relation until commit */
- heap_close(parent_rel, NoLock);
-
- ObjectAddressSet(address, RelationRelationId, parent_oid);
-
- return address;
}
/*
@@ -12418,3 +12530,137 @@ ComputePartitionAttrs(Oid relid, List *partParams, AttrNumber *partattrs,
partopclass[attn++] = opclassOid;
}
}
+
+
+/*
+ * ALTER TABLE <name> ATTACH PARTITION <partition-name> FOR VALUES
+ *
+ * Return the address of the newly attached partition.
+ */
+static ObjectAddress
+ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
+{
+ Relation attachRel,
+ inheritsRelation;
+ SysScanDesc scan;
+ ScanKeyData key;
+ HeapTuple tuple;
+ AttrNumber attno;
+ int natts;
+ TupleDesc tupleDesc;
+ ObjectAddress address;
+
+ attachRel = heap_openrv(cmd->name, AccessShareLock);
+
+ /*
+ * Must be owner of both parent and source table -- parent was checked by
+ * ATSimplePermissions call in ATPrepCmd
+ */
+ ATSimplePermissions(attachRel, ATT_TABLE);
+
+ if (attachRel->rd_rel->reloftype)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot attach a typed table as partition")));
+
+ if (attachRel->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot attach a partitioned table as partition")));
+
+ /* attachRel should not already be a inheritance child of some relation */
+ inheritsRelation = heap_open(InheritsRelationId, AccessShareLock);
+ ScanKeyInit(&key,
+ Anum_pg_inherits_inhrelid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(RelationGetRelid(attachRel)));
+ scan = systable_beginscan(inheritsRelation, InheritsRelidSeqnoIndexId,
+ true, NULL, 1, &key);
+ if (HeapTupleIsValid(systable_getnext(scan)))
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot attach table that inherits as partition")));
+ systable_endscan(scan);
+ heap_close(inheritsRelation, AccessShareLock);
+
+ /* If attachRel is temp, it must belong to this session */
+ if (attachRel->rd_rel->relpersistence == RELPERSISTENCE_TEMP &&
+ !attachRel->rd_islocaltemp)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot attach a temporary relation of another session as partition ")));
+
+ /* If parent has OIDs, attachRel must have OIDs */
+ if (rel->rd_rel->relhasoids && !attachRel->rd_rel->relhasoids)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot attach table \"%s\" without OIDs as partition of"
+ " table \"%s\" with OIDs", RelationGetRelationName(attachRel),
+ RelationGetRelationName(rel))));
+
+ /* OTOH, if parent doesn't have them, do not allow in attachRel either */
+ if (attachRel->rd_rel->relhasoids && !rel->rd_rel->relhasoids)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot attach table \"%s\" with OIDs as partition of table"
+ " \"%s\" without OIDs", RelationGetRelationName(attachRel),
+ RelationGetRelationName(rel))));
+
+ /* Check if there are any columns in attachRel that aren't in the parent */
+ tupleDesc = RelationGetDescr(attachRel);
+ natts = tupleDesc->natts;
+ for (attno = 1; attno <= natts; attno++)
+ {
+ Form_pg_attribute attribute = tupleDesc->attrs[attno - 1];
+ char *attributeName = NameStr(attribute->attname);
+
+ /* Ignore dropped */
+ if (attribute->attisdropped)
+ continue;
+
+ /* Find same column in parent (matching on column name). */
+ tuple = SearchSysCacheCopyAttName(RelationGetRelid(rel), attributeName);
+ if (!HeapTupleIsValid(tuple))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("table \"%s\" contains column \"%s\" not found in parent \"%s\"",
+ RelationGetRelationName(attachRel), attributeName,
+ RelationGetRelationName(rel)),
+ errdetail("Table being attached should contain only the columns"
+ " present in parent.")));
+ }
+
+ /* OK to create inheritance. Rest of the checks performed there */
+ CreateInheritance(attachRel, rel);
+
+ ObjectAddressSet(address, RelationRelationId, RelationGetRelid(attachRel));
+
+ /* keep our lock until commit */
+ heap_close(attachRel, NoLock);
+
+ return address;
+}
+
+/*
+ * ALTER TABLE DETACH PARTITION
+ *
+ * Return the address of the relation that is no longer a partition of rel.
+ */
+static ObjectAddress
+ATExecDetachPartition(Relation rel, RangeVar *name)
+{
+ Relation partRel;
+ ObjectAddress address;
+
+ partRel = heap_openrv(name, AccessShareLock);
+
+ /* All inheritance related checks are performed within the function */
+ RemoveInheritance(partRel, rel);
+
+ ObjectAddressSet(address, RelationRelationId, RelationGetRelid(partRel));
+
+ /* keep our lock until commit */
+ heap_close(partRel, NoLock);
+
+ return address;
+}
diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c
index e9d9ba2..c9c72d9 100644
--- a/src/backend/commands/view.c
+++ b/src/backend/commands/view.c
@@ -95,6 +95,7 @@ DefineVirtualRelation(RangeVar *relation, List *tlist, bool replace,
def->is_local = true;
def->is_not_null = false;
def->is_from_type = false;
+ def->is_for_partition = false;
def->storage = 0;
def->raw_default = NULL;
def->cooked_default = NULL;
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 5f4f66f..e924ddb 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2604,6 +2604,7 @@ _copyColumnDef(const ColumnDef *from)
COPY_SCALAR_FIELD(is_local);
COPY_SCALAR_FIELD(is_not_null);
COPY_SCALAR_FIELD(is_from_type);
+ COPY_SCALAR_FIELD(is_for_partition);
COPY_SCALAR_FIELD(storage);
COPY_NODE_FIELD(raw_default);
COPY_NODE_FIELD(cooked_default);
@@ -2999,6 +3000,7 @@ CopyCreateStmtFields(const CreateStmt *from, CreateStmt *newnode)
COPY_NODE_FIELD(relation);
COPY_NODE_FIELD(tableElts);
COPY_NODE_FIELD(inhRelations);
+ COPY_NODE_FIELD(partbound);
COPY_NODE_FIELD(partby);
COPY_NODE_FIELD(ofTypename);
COPY_NODE_FIELD(constraints);
@@ -4181,6 +4183,43 @@ _copyPartitionElem(const PartitionElem *from)
return newnode;
}
+static PartitionListSpec *
+_copyPartitionListSpec(const PartitionListSpec *from)
+{
+ PartitionListSpec *newnode = makeNode(PartitionListSpec);
+
+ COPY_NODE_FIELD(values);
+ COPY_LOCATION_FIELD(location);
+
+ return newnode;
+}
+
+static PartitionRangeSpec *
+_copyPartitionRangeSpec(const PartitionRangeSpec *from)
+{
+ PartitionRangeSpec *newnode = makeNode(PartitionRangeSpec);
+
+ COPY_SCALAR_FIELD(lowerinc);
+ COPY_NODE_FIELD(lower);
+ COPY_SCALAR_FIELD(upperinc);
+ COPY_NODE_FIELD(upper);
+ COPY_LOCATION_FIELD(location);
+
+ return newnode;
+}
+
+static PartitionCmd *
+_copyPartitionCmd(const PartitionCmd *from)
+{
+ PartitionCmd *newnode = makeNode(PartitionCmd);
+
+ COPY_NODE_FIELD(name);
+ COPY_NODE_FIELD(bound);
+ COPY_SCALAR_FIELD(skip_validate);
+
+ return newnode;
+}
+
/* ****************************************************************
* pg_list.h copy functions
* ****************************************************************
@@ -5080,6 +5119,15 @@ copyObject(const void *from)
case T_PartitionElem:
retval = _copyPartitionElem(from);
break;
+ case T_PartitionListSpec:
+ retval = _copyPartitionListSpec(from);
+ break;
+ case T_PartitionRangeSpec:
+ retval = _copyPartitionRangeSpec(from);
+ break;
+ case T_PartitionCmd:
+ retval = _copyPartitionCmd(from);
+ break;
default:
elog(ERROR, "unrecognized node type: %d", (int) nodeTag(from));
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 001a468..51b1e86 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1154,6 +1154,7 @@ _equalCreateStmt(const CreateStmt *a, const CreateStmt *b)
COMPARE_NODE_FIELD(relation);
COMPARE_NODE_FIELD(tableElts);
COMPARE_NODE_FIELD(inhRelations);
+ COMPARE_NODE_FIELD(partbound);
COMPARE_NODE_FIELD(partby);
COMPARE_NODE_FIELD(ofTypename);
COMPARE_NODE_FIELD(constraints);
@@ -2361,6 +2362,7 @@ _equalColumnDef(const ColumnDef *a, const ColumnDef *b)
COMPARE_SCALAR_FIELD(is_local);
COMPARE_SCALAR_FIELD(is_not_null);
COMPARE_SCALAR_FIELD(is_from_type);
+ COMPARE_SCALAR_FIELD(is_for_partition);
COMPARE_SCALAR_FIELD(storage);
COMPARE_NODE_FIELD(raw_default);
COMPARE_NODE_FIELD(cooked_default);
@@ -2639,6 +2641,37 @@ _equalPartitionElem(const PartitionElem *a, const PartitionElem *b)
return true;
}
+static bool
+_equalPartitionListSpec(const PartitionListSpec *a, const PartitionListSpec *b)
+{
+ COMPARE_NODE_FIELD(values);
+ COMPARE_LOCATION_FIELD(location);
+
+ return true;
+}
+
+static bool
+_equalPartitionRangeSpec(const PartitionRangeSpec *a, const PartitionRangeSpec *b)
+{
+ COMPARE_SCALAR_FIELD(lowerinc);
+ COMPARE_NODE_FIELD(lower);
+ COMPARE_SCALAR_FIELD(upperinc);
+ COMPARE_NODE_FIELD(upper);
+ COMPARE_LOCATION_FIELD(location);
+
+ return true;
+}
+
+static bool
+_equalPartitionCmd(const PartitionCmd *a, const PartitionCmd *b)
+{
+ COMPARE_NODE_FIELD(name);
+ COMPARE_NODE_FIELD(bound);
+ COMPARE_SCALAR_FIELD(skip_validate);
+
+ return true;
+}
+
/*
* Stuff from pg_list.h
*/
@@ -3395,6 +3428,15 @@ equal(const void *a, const void *b)
case T_PartitionElem:
retval = _equalPartitionElem(a, b);
break;
+ case T_PartitionListSpec:
+ retval = _equalPartitionListSpec(a, b);
+ break;
+ case T_PartitionRangeSpec:
+ retval = _equalPartitionRangeSpec(a, b);
+ break;
+ case T_PartitionCmd:
+ retval = _equalPartitionCmd(a, b);
+ break;
default:
elog(ERROR, "unrecognized node type: %d",
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 4e70a69..b531af3 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2367,6 +2367,7 @@ _outCreateStmtInfo(StringInfo str, const CreateStmt *node)
WRITE_NODE_FIELD(relation);
WRITE_NODE_FIELD(tableElts);
WRITE_NODE_FIELD(inhRelations);
+ WRITE_NODE_FIELD(partbound);
WRITE_NODE_FIELD(partby);
WRITE_NODE_FIELD(ofTypename);
WRITE_NODE_FIELD(constraints);
@@ -2547,6 +2548,7 @@ _outColumnDef(StringInfo str, const ColumnDef *node)
WRITE_BOOL_FIELD(is_local);
WRITE_BOOL_FIELD(is_not_null);
WRITE_BOOL_FIELD(is_from_type);
+ WRITE_BOOL_FIELD(is_for_partition);
WRITE_CHAR_FIELD(storage);
WRITE_NODE_FIELD(raw_default);
WRITE_NODE_FIELD(cooked_default);
@@ -3237,6 +3239,25 @@ _outPartitionElem(StringInfo str, const PartitionElem *node)
WRITE_LOCATION_FIELD(location);
}
+static void
+_outPartitionListSpec(StringInfo str, const PartitionListSpec *node)
+{
+ WRITE_NODE_TYPE("PARTITIONLISTVALUES");
+
+ WRITE_NODE_FIELD(values);
+}
+
+static void
+_outPartitionRangeSpec(StringInfo str, const PartitionRangeSpec *node)
+{
+ WRITE_NODE_TYPE("PARTITIONRANGE");
+
+ WRITE_BOOL_FIELD(lowerinc);
+ WRITE_NODE_FIELD(lower);
+ WRITE_BOOL_FIELD(upperinc);
+ WRITE_NODE_FIELD(upper);
+}
+
/*
* outNode -
* converts a Node into ascii string and append it to 'str'
@@ -3821,6 +3842,12 @@ outNode(StringInfo str, const void *obj)
case T_PartitionElem:
_outPartitionElem(str, obj);
break;
+ case T_PartitionListSpec:
+ _outPartitionListSpec(str, obj);
+ break;
+ case T_PartitionRangeSpec:
+ _outPartitionRangeSpec(str, obj);
+ break;
default:
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 0607439..79fbb32 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -229,6 +229,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
VariableSetStmt *vsetstmt;
PartitionElem *partelem;
PartitionBy *partby;
+ PartitionRangeSpec *partrange;
}
%type <node> stmt schema_stmt
@@ -544,6 +545,14 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <partby> PartitionBy OptPartitionBy
%type <partelem> part_elem
%type <list> part_params
+%type <list> OptPartitionElementList PartitionElementList
+%type <node> PartitionElement
+%type <node> ForValues
+%type <node> partvalue
+%type <list> partvalue_list
+%type <boolean> lb_inc ub_inc
+%type <list> RangeBound
+%type <boolean> opt_validate_spec
/*
* Non-keyword token types. These are hard-wired into the "flex" lexer.
@@ -569,7 +578,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
/* ordinary key words in alphabetical order */
%token <keyword> ABORT_P ABSOLUTE_P ACCESS ACTION ADD_P ADMIN AFTER
AGGREGATE ALL ALSO ALTER ALWAYS ANALYSE ANALYZE AND ANY ARRAY AS ASC
- ASSERTION ASSIGNMENT ASYMMETRIC AT ATTRIBUTE AUTHORIZATION
+ ASSERTION ASSIGNMENT ASYMMETRIC AT ATTACH ATTRIBUTE AUTHORIZATION
BACKWARD BEFORE BEGIN_P BETWEEN BIGINT BINARY BIT
BOOLEAN_P BOTH BY
@@ -585,7 +594,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
DATA_P DATABASE DAY_P DEALLOCATE DEC DECIMAL_P DECLARE DEFAULT DEFAULTS
DEFERRABLE DEFERRED DEFINER DELETE_P DELIMITER DELIMITERS DEPENDS DESC
- DICTIONARY DISABLE_P DISCARD DISTINCT DO DOCUMENT_P DOMAIN_P DOUBLE_P DROP
+ DETACH DICTIONARY DISABLE_P DISCARD DISTINCT DO DOCUMENT_P DOMAIN_P
+ DOUBLE_P DROP
EACH ELSE ENABLE_P ENCODING ENCRYPTED END_P ENUM_P ESCAPE EVENT EXCEPT
EXCLUDE EXCLUDING EXCLUSIVE EXECUTE EXISTS EXPLAIN
@@ -599,7 +609,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
HANDLER HAVING HEADER_P HOLD HOUR_P
IDENTITY_P IF_P ILIKE IMMEDIATE IMMUTABLE IMPLICIT_P IMPORT_P IN_P
- INCLUDING INCREMENT INDEX INDEXES INHERIT INHERITS INITIALLY INLINE_P
+ INCLUDING INCLUSIVE INCREMENT INDEX INDEXES INHERIT INHERITS INITIALLY INLINE_P
INNER_P INOUT INPUT_P INSENSITIVE INSERT INSTEAD INT_P INTEGER
INTERSECT INTERVAL INTO INVOKER IS ISNULL ISOLATION
@@ -2373,6 +2383,38 @@ alter_table_cmd:
n->def = (Node *)$1;
$$ = (Node *) n;
}
+ /* ALTER TABLE <name> ATTACH PARTITION <table_name> FOR VALUES */
+ | ATTACH PARTITION qualified_name ForValues opt_validate_spec
+ {
+ AlterTableCmd *n = makeNode(AlterTableCmd);
+ PartitionCmd *cmd = makeNode(PartitionCmd);
+
+ n->subtype = AT_AttachPartition;
+ cmd->name = $3;
+ cmd->bound = (Node *) $4;
+ cmd->skip_validate = $5;
+ n->def = (Node *) cmd;
+
+ $$ = (Node *) n;
+ }
+ /* ALTER TABLE <name> DETACH PARTITION <partition_name> */
+ | DETACH PARTITION qualified_name
+ {
+ AlterTableCmd *n = makeNode(AlterTableCmd);
+ PartitionCmd *cmd = makeNode(PartitionCmd);
+
+ n->subtype = AT_DetachPartition;
+ cmd->name = $3;
+ n->def = (Node *) cmd;
+
+ $$ = (Node *) n;
+ }
+ ;
+
+opt_validate_spec:
+ NO VALIDATE { $$ = true; }
+ | VALIDATE { $$ = false; }
+ | /* EMPTY */ { $$ = false; }
;
alter_column_default:
@@ -2468,6 +2510,60 @@ reloption_elem:
}
;
+ForValues:
+ /* a LIST partition */
+ FOR VALUES IN_P '(' partvalue_list ')'
+ {
+ PartitionListSpec *n = makeNode(PartitionListSpec);
+
+ n->values = $5;
+ n->location = @1;
+
+ $$ = (Node *) n;
+ }
+
+ /* a RANGE partition */
+ | FOR VALUES START RangeBound lb_inc END_P RangeBound ub_inc
+ {
+ PartitionRangeSpec *n = makeNode(PartitionRangeSpec);
+
+ n->lowerinc = $5;
+ n->lower = $4;
+ n->upperinc = $8;
+ n->upper = $7;
+ n->location = @1;
+
+ $$ = (Node *) n;
+ }
+ ;
+
+RangeBound:
+ UNBOUNDED { $$ = NIL; }
+ | '(' partvalue_list ')' { $$ = $2; }
+ ;
+
+lb_inc:
+ EXCLUSIVE { $$ = false;}
+ | INCLUSIVE { $$ = true; }
+ | /* EMPTY */ { $$ = true; }
+ ;
+
+ub_inc:
+ INCLUSIVE { $$ = true; }
+ | EXCLUSIVE { $$ = false; }
+ | /* EMPTY */ { $$ = false; }
+ ;
+
+partvalue:
+ Sconst { $$ = makeStringConst($1, @1); }
+ | NumericOnly { $$ = makeAConst($1, @1); }
+ | NULL_P { $$ = makeNullAConst(@1); }
+ ;
+
+partvalue_list:
+ partvalue { $$ = list_make1($1); }
+ | partvalue_list ',' partvalue { $$ = lappend($1, $3); }
+ ;
/*****************************************************************************
*
@@ -2885,6 +2981,44 @@ CreateStmt: CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
n->if_not_exists = true;
$$ = (Node *)n;
}
+ | CREATE OptTemp TABLE qualified_name PARTITION OF qualified_name
+ OptPartitionElementList ForValues OptPartitionBy OptWith
+ OnCommitOption OptTableSpace
+ {
+ CreateStmt *n = makeNode(CreateStmt);
+ $4->relpersistence = $2;
+ n->relation = $4;
+ n->tableElts = $8;
+ n->inhRelations = list_make1($7);
+ n->partbound = (Node *) $9;
+ n->partby = $10;
+ n->ofTypename = NULL;
+ n->constraints = NIL;
+ n->options = $11;
+ n->oncommit = $12;
+ n->tablespacename = $13;
+ n->if_not_exists = false;
+ $$ = (Node *)n;
+ }
+ | CREATE OptTemp TABLE IF_P NOT EXISTS qualified_name PARTITION OF
+ qualified_name OptPartitionElementList ForValues OptPartitionBy
+ OptWith OnCommitOption OptTableSpace
+ {
+ CreateStmt *n = makeNode(CreateStmt);
+ $7->relpersistence = $2;
+ n->relation = $7;
+ n->tableElts = $11;
+ n->inhRelations = list_make1($10);
+ n->partbound = (Node *) $12;
+ n->partby = $13;
+ n->ofTypename = NULL;
+ n->constraints = NIL;
+ n->options = $14;
+ n->oncommit = $15;
+ n->tablespacename = $16;
+ n->if_not_exists = true;
+ $$ = (Node *)n;
+ }
;
/*
@@ -2930,6 +3064,11 @@ OptTypedTableElementList:
| /*EMPTY*/ { $$ = NIL; }
;
+OptPartitionElementList:
+ '(' PartitionElementList ')' { $$ = $2; }
+ | /*EMPTY*/ { $$ = NIL; }
+ ;
+
TableElementList:
TableElement
{
@@ -2952,6 +3091,17 @@ TypedTableElementList:
}
;
+PartitionElementList:
+ PartitionElement
+ {
+ $$ = list_make1($1);
+ }
+ | PartitionElementList ',' PartitionElement
+ {
+ $$ = lappend($1, $3);
+ }
+ ;
+
TableElement:
columnDef { $$ = $1; }
| TableLikeClause { $$ = $1; }
@@ -2963,6 +3113,11 @@ TypedTableElement:
| TableConstraint { $$ = $1; }
;
+PartitionElement:
+ columnOptions { $$ = $1; }
+ | TableConstraint { $$ = $1; }
+ ;
+
columnDef: ColId Typename create_generic_options ColQualList
{
ColumnDef *n = makeNode(ColumnDef);
@@ -2972,6 +3127,7 @@ columnDef: ColId Typename create_generic_options ColQualList
n->is_local = true;
n->is_not_null = false;
n->is_from_type = false;
+ n->is_for_partition = false;
n->storage = 0;
n->raw_default = NULL;
n->cooked_default = NULL;
@@ -2993,6 +3149,7 @@ columnOptions: ColId WITH OPTIONS ColQualList
n->is_local = true;
n->is_not_null = false;
n->is_from_type = false;
+ n->is_for_partition = false;
n->storage = 0;
n->raw_default = NULL;
n->cooked_default = NULL;
@@ -11122,6 +11279,7 @@ TableFuncElement: ColId Typename opt_collate_clause
n->is_local = true;
n->is_not_null = false;
n->is_from_type = false;
+ n->is_for_partition = false;
n->storage = 0;
n->raw_default = NULL;
n->cooked_default = NULL;
@@ -13805,6 +13963,7 @@ unreserved_keyword:
| ASSERTION
| ASSIGNMENT
| AT
+ | ATTACH
| ATTRIBUTE
| BACKWARD
| BEFORE
@@ -13851,6 +14010,7 @@ unreserved_keyword:
| DELIMITER
| DELIMITERS
| DEPENDS
+ | DETACH
| DICTIONARY
| DISABLE_P
| DISCARD
@@ -13893,6 +14053,7 @@ unreserved_keyword:
| IMPLICIT_P
| IMPORT_P
| INCLUDING
+ | INCLUSIVE
| INCREMENT
| INDEX
| INDEXES
diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c
index 77ffd84..d71fdfe 100644
--- a/src/backend/parser/parse_agg.c
+++ b/src/backend/parser/parse_agg.c
@@ -493,7 +493,9 @@ check_agglevels_and_constraints(ParseState *pstate, Node *expr)
err = _("grouping operations are not allowed in partition key expression");
break;
-
+ case EXPR_KIND_PARTITION_FOR_VALUES:
+ errkind = true;
+ break;
/*
* There is intentionally no default: case here, so that the
@@ -854,6 +856,9 @@ transformWindowFuncCall(ParseState *pstate, WindowFunc *wfunc,
case EXPR_KIND_PARTITION_KEY:
err = _("window functions are not allowed in partition key expression");
break;
+ case EXPR_KIND_PARTITION_FOR_VALUES:
+ errkind = true;
+ break;
/*
* There is intentionally no default: case here, so that the
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 6a74c28..85d7828 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -352,6 +352,26 @@ transformExprRecurse(ParseState *pstate, Node *expr)
result = (Node *) expr;
break;
}
+ case T_PartitionListSpec:
+ {
+ PartitionListSpec *list = (PartitionListSpec *) expr;
+
+ list->values = transformExpressionList(pstate, list->values,
+ pstate->p_expr_kind);
+ result = expr;
+ break;
+ }
+ case T_PartitionRangeSpec:
+ {
+ PartitionRangeSpec *range = (PartitionRangeSpec *) expr;
+
+ range->lower = transformExpressionList(pstate, range->lower,
+ pstate->p_expr_kind);
+ range->upper = transformExpressionList(pstate, range->upper,
+ pstate->p_expr_kind);
+ result = expr;
+ break;
+ }
default:
/* should not reach here */
@@ -1690,6 +1710,7 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
case EXPR_KIND_OFFSET:
case EXPR_KIND_RETURNING:
case EXPR_KIND_VALUES:
+ case EXPR_KIND_PARTITION_FOR_VALUES:
/* okay */
break;
case EXPR_KIND_CHECK_CONSTRAINT:
@@ -3230,6 +3251,8 @@ ParseExprKindName(ParseExprKind exprKind)
return "WHEN";
case EXPR_KIND_PARTITION_KEY:
return "partition key expression";
+ case EXPR_KIND_PARTITION_FOR_VALUES:
+ return "FOR VALUES";
/*
* There is intentionally no default: case here, so that the
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 465597d..e75add7 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -48,8 +48,10 @@
#include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "nodes/nodeFuncs.h"
+#include "optimizer/planner.h"
#include "parser/analyze.h"
#include "parser/parse_clause.h"
+#include "parser/parse_coerce.h"
#include "parser/parse_collate.h"
#include "parser/parse_expr.h"
#include "parser/parse_relation.h"
@@ -89,6 +91,7 @@ typedef struct
* the table */
IndexStmt *pkey; /* PRIMARY KEY index, if any */
bool ispartitioned; /* true if table is partitioned */
+ Node *partbound; /* transformed FOR VALUES */
} CreateStmtContext;
/* State shared by transformCreateSchemaStmt and its subroutines */
@@ -131,6 +134,10 @@ static void transformConstraintAttrs(CreateStmtContext *cxt,
List *constraintList);
static void transformColumnType(CreateStmtContext *cxt, ColumnDef *column);
static void setSchemaName(char *context_schema, char **stmt_schema_name);
+static void transformPartitionOf(CreateStmtContext *cxt, Node *bound);
+static void transformAttachPartition(CreateStmtContext *cxt, PartitionCmd *cmd);
+static Node *transformPartitionBound(CreateStmtContext *cxt, Relation parent,
+ Node *bound);
/*
@@ -232,6 +239,7 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
cxt.alist = NIL;
cxt.pkey = NULL;
cxt.ispartitioned = stmt->partby != NULL;
+ cxt.partbound = NULL;
/*
* Notice that we allow OIDs here only for plain tables, even though
@@ -250,6 +258,9 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
if (stmt->ofTypename)
transformOfType(&cxt, stmt->ofTypename);
+ if (stmt->partbound)
+ transformPartitionOf(&cxt, stmt->partbound);
+
if (stmt->partby)
{
int partnatts = list_length(stmt->partby->partParams);
@@ -355,6 +366,7 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
*/
stmt->tableElts = cxt.columns;
stmt->constraints = cxt.ckconstraints;
+ stmt->partbound = cxt.partbound;
result = lappend(cxt.blist, stmt);
result = list_concat(result, cxt.alist);
@@ -894,6 +906,7 @@ transformTableLikeClause(CreateStmtContext *cxt, TableLikeClause *table_like_cla
def->is_local = true;
def->is_not_null = attribute->attnotnull;
def->is_from_type = false;
+ def->is_for_partition = false;
def->storage = 0;
def->raw_default = NULL;
def->cooked_default = NULL;
@@ -1113,6 +1126,7 @@ transformOfType(CreateStmtContext *cxt, TypeName *ofTypename)
n->is_local = true;
n->is_not_null = false;
n->is_from_type = true;
+ n->is_for_partition = false;
n->storage = 0;
n->raw_default = NULL;
n->cooked_default = NULL;
@@ -2580,6 +2594,7 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
cxt.alist = NIL;
cxt.pkey = NULL;
cxt.ispartitioned = relid_is_partitioned(relid);
+ cxt.partbound = NULL;
/*
* The only subtypes that currently require parse transformation handling
@@ -2668,6 +2683,22 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
break;
}
+ case AT_AttachPartition:
+ {
+ PartitionCmd *partcmd = (PartitionCmd *) cmd->def;
+
+ transformAttachPartition(&cxt, partcmd);
+
+ /* assign transformed values */
+ partcmd->bound = cxt.partbound;
+ }
+
+ newcmds = lappend(newcmds, cmd);
+ break;
+ case AT_DetachPartition:
+ newcmds = lappend(newcmds, cmd);
+ break;
+
default:
newcmds = lappend(newcmds, cmd);
break;
@@ -3032,3 +3063,290 @@ setSchemaName(char *context_schema, char **stmt_schema_name)
"different from the one being created (%s)",
*stmt_schema_name, context_schema)));
}
+
+/*
+ * transformPartitionOf
+ * Analyze PARTITION OF ... FOR VALUES ...
+ */
+static void
+transformPartitionOf(CreateStmtContext *cxt, Node *bound)
+{
+ TupleDesc tupdesc;
+ int i;
+ Relation parentRel;
+ RangeVar *partof = linitial(cxt->inhRelations);
+
+ parentRel = heap_openrv(partof, AccessShareLock);
+
+ /* Check if the target table is partitioned at all */
+ if (parentRel->rd_rel->relkind != RELKIND_PARTITIONED_REL)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("\"%s\" is not partitioned",
+ RelationGetRelationName(parentRel))));
+ Assert(parentRel->rd_partkey != NULL);
+
+ /* If parent rel is temp, it must belong to this session */
+ if (parentRel->rd_rel->relpersistence == RELPERSISTENCE_TEMP &&
+ !parentRel->rd_islocaltemp)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot create as partition of temporary relation of another session")));
+
+ /*
+ * Do not allow OIDs in a partition, if not present in the parent. But
+ * force them in partition, if they are present in the parent.
+ */
+ if (cxt->hasoids && !parentRel->rd_rel->relhasoids)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot create table with OIDs as partition of table without OIDs")));
+
+ if (parentRel->rd_rel->relhasoids);
+ cxt->hasoids = true;
+
+ tupdesc = RelationGetDescr(parentRel);
+ for (i = 0; i < tupdesc->natts; i++)
+ {
+ Form_pg_attribute attribute = tupdesc->attrs[i];
+ ColumnDef *def;
+
+ if (attribute->attisdropped)
+ continue;
+
+ def = makeNode(ColumnDef);
+ def->colname = pstrdup(NameStr(attribute->attname));
+ def->typeName = makeTypeNameFromOid(attribute->atttypid,
+ attribute->atttypmod);
+ def->inhcount = 1;
+ def->is_local = false;
+ def->is_not_null = attribute->attnotnull;
+ def->is_from_type = false;
+ def->is_for_partition = true;
+ def->storage = attribute->attstorage;
+ def->raw_default = NULL;
+ def->cooked_default = NULL;
+ def->collClause = NULL;
+ def->collOid = attribute->attcollation;
+ def->constraints = NIL;
+ def->location = -1;
+
+ cxt->columns = lappend(cxt->columns, def);
+ }
+
+ /* tranform the values */
+ cxt->partbound = transformPartitionBound(cxt, parentRel, bound);
+
+ heap_close(parentRel, AccessShareLock);
+}
+
+/*
+ * transformAttachPartition
+ * Analyze ATTACH PARTITION ... FOR VALUES ...
+ */
+static void
+transformAttachPartition(CreateStmtContext *cxt, PartitionCmd *cmd)
+{
+ Relation parentRel = cxt->rel;
+
+ /* Check if the target table is partitioned at all */
+ if (parentRel->rd_rel->relkind != RELKIND_PARTITIONED_REL)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("\"%s\" is not partitioned", cxt->relation->relname)));
+ Assert(parentRel->rd_partkey != NULL);
+
+ /* tranform the values */
+ cxt->partbound = transformPartitionBound(cxt, parentRel, cmd->bound);
+}
+
+/*
+ * transformPartitionBound
+ *
+ * Transform partition bound per the partition key
+ */
+static Node *
+transformPartitionBound(CreateStmtContext *cxt, Relation parent, Node *bound)
+{
+ int i;
+ ListCell *cell;
+ Oid parentId = RelationGetRelid(parent);
+ PartitionKey key = parent->rd_partkey;
+ PartitionListSpec *list, *result_list;
+ PartitionRangeSpec *range, *result_range;
+
+ switch (key->strategy)
+ {
+ case PARTITION_STRAT_LIST:
+ if (!IsA(bound, PartitionListSpec))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("invalid bound specification for a list partition"),
+ parser_errposition(cxt->pstate, exprLocation(bound))));
+
+ list = (PartitionListSpec *) transformExpr(cxt->pstate, bound,
+ EXPR_KIND_PARTITION_FOR_VALUES);
+
+ result_list = makeNode(PartitionListSpec);
+
+ foreach(cell, list->values)
+ {
+ Node *value = (Node *) lfirst(cell);
+ Node *orig_value = value;
+ Oid valuetype;
+
+ valuetype = exprType(value);
+ value = coerce_to_target_type(cxt->pstate,
+ value, valuetype,
+ key->tcinfo->typid[0],
+ key->tcinfo->typmod[0],
+ COERCION_ASSIGNMENT,
+ COERCE_IMPLICIT_CAST,
+ -1);
+ if (value == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("specified value cannot be cast to type \"%s\""
+ " of key column \"%s\"",
+ format_type_be(key->tcinfo->typid[0]),
+ get_relid_attribute_name(parentId, key->partattrs[0])),
+ parser_errposition(cxt->pstate, exprLocation(orig_value))));
+
+ /* Simplify the expression */
+ value = (Node *) expression_planner((Expr *) value);
+
+ result_list->values = lappend(result_list->values, value);
+ }
+ return (Node *) result_list;
+
+ case PARTITION_STRAT_RANGE:
+ if (!IsA(bound, PartitionRangeSpec))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("invalid bound specification for a range partition"),
+ parser_errposition(cxt->pstate, exprLocation(bound))));
+
+ range = (PartitionRangeSpec *) transformExpr(cxt->pstate, bound,
+ EXPR_KIND_PARTITION_FOR_VALUES);
+
+ if (!range->lower && !range->upper)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("both START and END cannot be UNBOUNDED"),
+ parser_errposition(cxt->pstate, range->location)));
+
+ result_range = makeNode(PartitionRangeSpec);
+ result_range->lowerinc = range->lowerinc;
+ result_range->upperinc = range->upperinc;
+
+ if (range->lower)
+ {
+ if (list_length(range->lower) > key->partnatts)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("START has more values specified than number"
+ " of columns in the partition key"),
+ parser_errposition(cxt->pstate,
+ exprLocation(list_nth(range->lower,
+ list_length(range->lower) - 1)))));
+ else if (list_length(range->lower) < key->partnatts)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("START has fewer values specified than number"
+ " of columns in the partition key"),
+ parser_errposition(cxt->pstate,
+ exprLocation(list_nth(range->lower,
+ list_length(range->lower) - 1)))));
+
+ i = 0;
+ foreach (cell, range->lower)
+ {
+ Node *value = (Node *) lfirst(cell);
+ Node *orig_value = value;
+ Oid valuetype;
+
+ valuetype = exprType(value);
+ value = coerce_to_target_type(cxt->pstate,
+ value, valuetype,
+ key->tcinfo->typid[i],
+ key->tcinfo->typmod[i],
+ COERCION_ASSIGNMENT,
+ COERCE_IMPLICIT_CAST,
+ -1);
+ if (value == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("specified value cannot be cast to type"
+ " \"%s\" of key column \"%s\"",
+ format_type_be(key->tcinfo->typid[i]),
+ get_relid_attribute_name(parentId, key->partattrs[i])),
+ parser_errposition(cxt->pstate, exprLocation(orig_value))));
+
+ /* Simplify the expression */
+ value = (Node *) expression_planner((Expr *) value);
+
+ result_range->lower = lappend(result_range->lower, value);
+ ++i;
+ }
+ }
+ else
+ result_range->lowerinc = false;
+
+ if (range->upper)
+ {
+ if (list_length(range->upper) > key->partnatts)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("END has more values specified than number"
+ " of columns in the partition key"),
+ parser_errposition(cxt->pstate,
+ exprLocation(list_nth(range->upper,
+ list_length(range->upper) - 1)))));
+ else if (list_length(range->upper) < key->partnatts)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("END has fewer values specified than number"
+ " of columns in the partition key"),
+ parser_errposition(cxt->pstate,
+ exprLocation(list_nth(range->upper,
+ list_length(range->upper) - 1)))));
+
+ i = 0;
+ foreach (cell, range->upper)
+ {
+ Node *value = (Node *) lfirst(cell);
+ Node *orig_value = value;
+ Oid valuetype;
+
+ valuetype = exprType(value);
+ value = coerce_to_target_type(cxt->pstate,
+ value, valuetype,
+ key->tcinfo->typid[i],
+ key->tcinfo->typmod[i],
+ COERCION_ASSIGNMENT,
+ COERCE_IMPLICIT_CAST,
+ -1);
+ if (value == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("specified value cannot be cast to type"
+ " \"%s\" of key column \"%s\"",
+ format_type_be(key->tcinfo->typid[i]),
+ get_relid_attribute_name(parentId, key->partattrs[i])),
+ parser_errposition(cxt->pstate, exprLocation(orig_value))));
+
+ /* Simplify the expression */
+ value = (Node *) expression_planner((Expr *) value);
+
+ result_range->upper = lappend(result_range->upper, value);
+ ++i;
+ }
+ }
+ else
+ result_range->upperinc = false;
+
+ return (Node *) result_range;
+ }
+
+ return NULL;
+}
diff --git a/src/include/catalog/partition.h b/src/include/catalog/partition.h
index 2c876c4..31dbe17 100644
--- a/src/include/catalog/partition.h
+++ b/src/include/catalog/partition.h
@@ -14,6 +14,7 @@
#define PARTITION_H
#include "fmgr.h"
+#include "parser/parse_node.h"
#include "utils/relcache.h"
/* Type and collation information for partition key columns */
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 9e582a5..56bbe43 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -405,6 +405,7 @@ typedef enum NodeTag
T_AlterPolicyStmt,
T_CreateTransformStmt,
T_CreateAmStmt,
+ T_PartitionCmd,
/*
* TAGS FOR PARSE TREE NODES (parsenodes.h)
@@ -454,6 +455,8 @@ typedef enum NodeTag
T_RoleSpec,
T_PartitionElem,
T_PartitionBy,
+ T_PartitionListSpec,
+ T_PartitionRangeSpec,
/*
* TAGS FOR REPLICATION GRAMMAR PARSE NODES (replnodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 4f697b8..d9dff96 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -591,6 +591,7 @@ typedef struct ColumnDef
bool is_local; /* column has local (non-inherited) def'n */
bool is_not_null; /* NOT NULL constraint specified? */
bool is_from_type; /* column definition came from table type */
+ bool is_for_partition; /* column definition is for a partition */
char storage; /* attstorage setting, or 0 for default */
Node *raw_default; /* default value (untransformed parse tree) */
Node *cooked_default; /* default value (transformed expr tree) */
@@ -731,6 +732,40 @@ typedef struct PartitionBy
int location; /* token location, or -1 if unknown */
} PartitionBy;
+/*
+ * PartitionRangeSpec - range type syntax for range partition bound
+ */
+typedef struct PartitionListSpec
+{
+ NodeTag type;
+ List *values;
+ int location;
+} PartitionListSpec;
+
+/*
+ * PartitionRangeSpec - range type syntax for range partition bound
+ */
+typedef struct PartitionRangeSpec
+{
+ NodeTag type;
+ bool lowerinc;
+ List *lower;
+ bool upperinc;
+ List *upper;
+ int location; /* token location, or -1 if unknown */
+} PartitionRangeSpec;
+
+/*
+ * PartitionCmd - ALTER TABLE partition commands
+ */
+typedef struct PartitionCmd
+{
+ NodeTag type;
+ RangeVar *name;
+ Node *bound;
+ bool skip_validate;
+} PartitionCmd;
+
/****************************************************************************
* Nodes for a Query tree
****************************************************************************/
@@ -1558,7 +1593,9 @@ typedef enum AlterTableType
AT_DisableRowSecurity, /* DISABLE ROW SECURITY */
AT_ForceRowSecurity, /* FORCE ROW SECURITY */
AT_NoForceRowSecurity, /* NO FORCE ROW SECURITY */
- AT_GenericOptions /* OPTIONS (...) */
+ AT_GenericOptions, /* OPTIONS (...) */
+ AT_AttachPartition, /* ATTACH PARTITION */
+ AT_DetachPartition /* DETACH PARTITION */
} AlterTableType;
typedef struct ReplicaIdentityStmt
@@ -1783,7 +1820,9 @@ typedef struct CreateStmt
RangeVar *relation; /* relation to create */
List *tableElts; /* column definitions (list of ColumnDef) */
List *inhRelations; /* relations to inherit from (list of
- * inhRelation) */
+ * inhRelation); (ab)used also as partition
+ * parent */
+ Node *partbound; /* FOR VALUES clause */
PartitionBy *partby; /* PARTITION BY clause */
TypeName *ofTypename; /* OF typename */
List *constraints; /* constraints (list of Constraint nodes) */
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 40da67a..70c264c 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -49,6 +49,7 @@ PG_KEYWORD("assertion", ASSERTION, UNRESERVED_KEYWORD)
PG_KEYWORD("assignment", ASSIGNMENT, UNRESERVED_KEYWORD)
PG_KEYWORD("asymmetric", ASYMMETRIC, RESERVED_KEYWORD)
PG_KEYWORD("at", AT, UNRESERVED_KEYWORD)
+PG_KEYWORD("attach", ATTACH, UNRESERVED_KEYWORD)
PG_KEYWORD("attribute", ATTRIBUTE, UNRESERVED_KEYWORD)
PG_KEYWORD("authorization", AUTHORIZATION, TYPE_FUNC_NAME_KEYWORD)
PG_KEYWORD("backward", BACKWARD, UNRESERVED_KEYWORD)
@@ -127,6 +128,7 @@ PG_KEYWORD("delimiter", DELIMITER, UNRESERVED_KEYWORD)
PG_KEYWORD("delimiters", DELIMITERS, UNRESERVED_KEYWORD)
PG_KEYWORD("depends", DEPENDS, UNRESERVED_KEYWORD)
PG_KEYWORD("desc", DESC, RESERVED_KEYWORD)
+PG_KEYWORD("detach", DETACH, UNRESERVED_KEYWORD)
PG_KEYWORD("dictionary", DICTIONARY, UNRESERVED_KEYWORD)
PG_KEYWORD("disable", DISABLE_P, UNRESERVED_KEYWORD)
PG_KEYWORD("discard", DISCARD, UNRESERVED_KEYWORD)
@@ -191,6 +193,7 @@ PG_KEYWORD("implicit", IMPLICIT_P, UNRESERVED_KEYWORD)
PG_KEYWORD("import", IMPORT_P, UNRESERVED_KEYWORD)
PG_KEYWORD("in", IN_P, RESERVED_KEYWORD)
PG_KEYWORD("including", INCLUDING, UNRESERVED_KEYWORD)
+PG_KEYWORD("inclusive", INCLUSIVE, UNRESERVED_KEYWORD)
PG_KEYWORD("increment", INCREMENT, UNRESERVED_KEYWORD)
PG_KEYWORD("index", INDEX, UNRESERVED_KEYWORD)
PG_KEYWORD("indexes", INDEXES, UNRESERVED_KEYWORD)
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index a13c6fb..3d45663 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -65,7 +65,8 @@ typedef enum ParseExprKind
EXPR_KIND_EXECUTE_PARAMETER, /* parameter value in EXECUTE */
EXPR_KIND_TRIGGER_WHEN, /* WHEN condition in CREATE TRIGGER */
EXPR_KIND_POLICY, /* USING or WITH CHECK expr in policy */
- EXPR_KIND_PARTITION_KEY /* partition key expression */
+ EXPR_KIND_PARTITION_KEY, /* partition key expression */
+ EXPR_KIND_PARTITION_FOR_VALUES /* partition bound */
} ParseExprKind;
--
1.7.1
[text/x-diff] 0004-Infrastructure-for-partition-metadata-storage-and-ma-v1.patch (94.5K, ../../57106F9E.3000309@lab.ntt.co.jp/5-0004-Infrastructure-for-partition-metadata-storage-and-ma-v1.patch)
download | inline diff:
From f8677357a025a47a60ff63415a453c93302b5226 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Fri, 18 Mar 2016 15:54:17 +0900
Subject: [PATCH 4/5] Infrastructure for partition metadata storage and manipulation.
Catalog pg_partition with:
partrelid oid
partbound pg_node_tree
Now since partition metadata is stored in a dedicated catalog, it is possible
to use that knowledge to enforce special behaviors for inheritance children
(or relations in general) that are partitions. Some such behaviors include:
* Cannot drop using DROP TABLE without first "detaching" from its parent
* Cannot add, drop new columns
* Cannot rename, change type of existing columns
It also becomes now possible to check, from the FOR VALUES specification, that
the new partition (in CREATE TABLE PARTITION OF parent or ALTER TABLE parent
ATTACH PARTITION) does not overlap with any existing partition of parent.
One can get a partition's check expression (that is, an expression tree that
can be used as a check constraint for the rows in it). ExecConstraints() now
checks using that expression, whether a row being inserted or new row value in
update case, violate the partition boundary specification. Also, optimizer's
constraint exclusion uses it remove useless partitions. This expression tree
is cached in the partition's relcache.
When attaching a partition, rows in the table are checked whether any violates
the partition bound specification (AND'ed with the parent's check expression,
if parent itself is a partition), unless NO VALIDATE clause is specified with
the command.
This commit also invents an internal representation of partition bounds in
the form of structs PartitionListBound and PartitionRangeBound, which consist
of datums and information like number of values in a list, booleans for
whether a range bound is infinite, inclusive (vs. exclusive), lower bound
(vs. upper bound), etc. One can get "partition descriptor" for a partitioned
table, which comes in the form of a struct PartitionDesc. It consists of
of an array of oids of partitions, array of pointers to PartitionListBound
structs (NULL if not list partitioned, and 2 arrays of pointers to
PartitionRangeBound structs, (each NULL, if not range partitioned).
A pointer to this structure is also cached in relcache.
One more data structure that this commit invents for future use is struct
PartitionDescNode. It represents a node in partition tree where each node
of the tree corresponds to a partitioned table in the tree. The tree
structure is such that a parent node links to the first child node (which
corresponds with the first partition of the parent that is partitioned
itself), which in turn, links to its right sibling (the next partition
that is partitioned) and so on. Each node consists of some book-keeping
information including a pointer to PartitionDesc of the corresponding table
and recursive pointers just mentioned viz. downlink and next.
Support to route tuples inserted into a partitioned parent to the appropriate
leaf partition will be added shortly which leverages PartitionDescNode.
---
doc/src/sgml/catalogs.sgml | 56 ++
src/backend/catalog/Makefile | 2 +-
src/backend/catalog/heap.c | 6 +
src/backend/catalog/partition.c | 1378 ++++++++++++++++++++++++++++
src/backend/commands/copy.c | 2 +-
src/backend/commands/lockcmds.c | 4 +-
src/backend/commands/tablecmds.c | 210 ++++-
src/backend/executor/execMain.c | 75 ++-
src/backend/executor/nodeModifyTable.c | 4 +-
src/backend/nodes/readfuncs.c | 33 +
src/backend/optimizer/util/plancat.c | 13 +
src/backend/parser/analyze.c | 1 +
src/backend/parser/parse_clause.c | 4 +-
src/backend/parser/parse_relation.c | 3 +-
src/backend/parser/parse_utilcmd.c | 6 +
src/backend/utils/adt/ruleutils.c | 78 ++
src/backend/utils/cache/relcache.c | 7 +
src/backend/utils/cache/syscache.c | 12 +
src/bin/pg_dump/common.c | 86 ++
src/bin/pg_dump/pg_dump.c | 98 ++-
src/bin/pg_dump/pg_dump.h | 12 +
src/bin/psql/describe.c | 46 +-
src/include/catalog/indexing.h | 3 +
src/include/catalog/partition.h | 57 ++
src/include/catalog/pg_partition.h | 54 ++
src/include/nodes/execnodes.h | 4 +
src/include/utils/rel.h | 4 +
src/include/utils/syscache.h | 1 +
src/test/regress/expected/sanity_check.out | 1 +
29 files changed, 2224 insertions(+), 36 deletions(-)
create mode 100644 src/include/catalog/pg_partition.h
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index ba658b1..089fa5b 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -224,6 +224,11 @@
</row>
<row>
+ <entry><link linkend="catalog-pg-partition"><structname>pg_partition</structname></link></entry>
+ <entry>information about partitions</entry>
+ </row>
+
+ <row>
<entry><link linkend="catalog-pg-partitioned"><structname>pg_partitioned</structname></link></entry>
<entry>information about partitioned tables, including the partition key</entry>
</row>
@@ -4687,6 +4692,57 @@
</sect1>
+ <sect1 id="catalog-pg-partition">
+ <title><structname>pg_partition</structname></title>
+
+ <indexterm zone="catalog-pg-partition">
+ <primary>pg_partition</primary>
+ </indexterm>
+
+ <para>
+ The catalog <structname>pg_partition</structname> stores partition bounds
+ of tables that are partitions.
+ </para>
+
+ <table>
+ <title><structname>pg_partition</> Columns</title>
+
+ <tgroup cols="4">
+ <thead>
+ <row>
+ <entry>Name</entry>
+ <entry>Type</entry>
+ <entry>References</entry>
+ <entry>Description</entry>
+ </row>
+ </thead>
+
+ <tbody>
+
+ <row>
+ <entry><structfield>partrelid</structfield></entry>
+ <entry><type>oid</type></entry>
+ <entry><literal><link linkend="catalog-pg-class"><structname>pg_class</structname></link>.oid</literal></entry>
+ <entry>The OID of the <structname>pg_class</> entry for this partition</entry>
+ </row>
+
+ <row>
+ <entry><structfield>partbound</structfield></entry>
+ <entry><type>pg_node_tree</type></entry>
+ <entry></entry>
+ <entry>
+ Expression tree (in <function>nodeToString()</function> representation)
+ for partition boundary value. For list partitions, it is simply a list
+ of values. For range partitions, it consists of lower and upper bound
+ values along with their inclusivity.
+ </entry>
+ </row>
+
+ </tbody>
+ </tgroup>
+ </table>
+ </sect1>
+
<sect1 id="catalog-pg-partitioned">
<title><structname>pg_partitioned</structname></title>
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index 4dc6300..ee1be31 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -41,7 +41,7 @@ POSTGRES_BKI_SRCS = $(addprefix $(top_srcdir)/src/include/catalog/,\
pg_foreign_data_wrapper.h pg_foreign_server.h pg_user_mapping.h \
pg_foreign_table.h pg_policy.h pg_replication_origin.h \
pg_default_acl.h pg_init_privs.h pg_seclabel.h pg_shseclabel.h \
- pg_collation.h pg_range.h pg_transform.h pg_partitioned.h\
+ pg_collation.h pg_range.h pg_transform.h pg_partitioned.h pg_partition.h\
toasting.h indexing.h \
)
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 932a48b..2e99661 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -1808,7 +1808,13 @@ heap_drop_with_catalog(Oid relid)
* If a partitioned table, delete the pg_partitioned tuples.
*/
if (rel->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+ {
RemovePartitionKeyByRelId(relid);
+ RelationDropPartitions(relid);
+ }
+
+ if (relid_is_partition(relid))
+ RemovePartitionEntryByRelId(relid);
/*
* Schedule unlinking of the relation's physical files at commit.
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index 5e238dc..76b9e2b 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -23,12 +23,17 @@
#include "catalog/objectaddress.h"
#include "catalog/partition.h"
#include "catalog/pg_collation.h"
+#include "catalog/pg_inherits.h"
+#include "catalog/pg_inherits_fn.h"
#include "catalog/pg_opclass.h"
#include "catalog/pg_partitioned.h"
+#include "catalog/pg_partition.h"
#include "catalog/pg_type.h"
#include "executor/executor.h"
#include "miscadmin.h"
+#include "nodes/makefuncs.h"
#include "nodes/nodeFuncs.h"
+#include "nodes/parsenodes.h"
#include "optimizer/clauses.h"
#include "optimizer/planmain.h"
#include "storage/lmgr.h"
@@ -41,10 +46,85 @@
#include "utils/lsyscache.h"
#include "utils/syscache.h"
+/*
+ * Information about a single partition
+ */
+typedef struct PartitionInfo
+{
+ Oid oid; /* partition OID */
+ PartitionListBound *list; /* list partition bound */
+ PartitionRangeBound *rangelower; /* range partition lower bound */
+ PartitionRangeBound *rangeupper; /* range partition upper bound */
+} PartitionInfo;
+
+typedef struct translate_var_attno_mutator_context
+{
+ AttrNumber old_attno;
+ AttrNumber new_attno;
+} translate_var_attno_mutator_context;
+
+/*
+ * Information about partitions of a partitioned table.
+ */
+typedef struct PartitionDescData
+{
+ int nparts; /* number of partitions */
+ Oid *oids; /* OIDs of the partitions */
+ PartitionListBound **lists; /* list bounds */
+ PartitionRangeBound **rangelowers; /* range lower bounds */
+ PartitionRangeBound **rangeuppers; /* range upper bounds */
+} PartitionDescData;
+
static PartitionKey CopyPartitionKey(PartitionKey fromkey);
static KeyTypeCollInfo *copy_key_type_coll_info(int nkeycols,
KeyTypeCollInfo *tcinfo);
static void free_key_type_coll_info(KeyTypeCollInfo *tcinfo);
+static bool list_overlaps_existing_partition(PartitionKey key,
+ PartitionListSpec *list_spec,
+ PartitionDesc pdesc,
+ Oid *with);
+static bool partition_range_empty(PartitionKey key,
+ PartitionRangeSpec *range_spec);
+static int32 compare_range_keys(PartitionKey key, Datum *val1, Datum *val2);
+static bool range_overlaps_existing_partition(PartitionKey key,
+ PartitionRangeSpec *range_spec,
+ PartitionDesc pdesc,
+ Oid *with);
+static PartitionListBound *make_list_from_spec(PartitionKey key, List *values);
+static PartitionRangeBound *make_range_from_spec(PartitionKey key, List *val,
+ bool inclusive, bool lower);
+static bool partition_range_overlaps(PartitionKey key,
+ PartitionRangeBound *part1_lower,
+ PartitionRangeBound *part1_upper,
+ PartitionRangeBound *part2_lower,
+ PartitionRangeBound *part2_upper);
+static int32 partition_range_cmp_bounds(PartitionKey key,
+ PartitionRangeBound *b1,
+ PartitionRangeBound *b2);
+
+static PartitionDesc RelationGetPartitionDesc(Relation relation);
+static PartitionDesc GetPartitionDescCopy(PartitionKey key, PartitionDesc src);
+static PartitionInfo **GetRelPartitions(Relation rel, int *nparts);
+static List *get_partitions(Oid relid, int lockmode);
+static int32 range_partition_cmp_upper(const void *a, const void *b,
+ void *arg);
+static PartitionListBound *copyPartitionListBound(PartitionListBound *src,
+ PartitionKey key);
+static PartitionRangeBound *copyPartitionRangeBound(PartitionRangeBound *src,
+ PartitionKey key);
+static void free_partition_info_array(PartitionInfo **p, int num);
+
+static Expr *get_check_expr_for_list(PartitionKey key, List *values);
+static Expr *get_check_expr_for_rangebound(PartitionKey key, List *values,
+ bool inclusive, bool lower);
+static Node *translate_var_attno(Node *expr, AttrNumber attno,
+ AttrNumber new_attno);
+static Node *translate_var_attno_mutator(Node *node,
+ translate_var_attno_mutator_context *cxt);
+static Node *generate_partition_check_expr(Relation rel);
+
+static PartitionDescNode GetPartitionDescNodeRecurse(Relation rel, int offset);
+static int get_leaf_partition_count(PartitionDescNode pdnode);
/*
* StorePartitionKey
@@ -449,3 +529,1301 @@ FreePartitionKey(PartitionKey key)
free_key_type_coll_info(key->tcinfo);
pfree(key);
}
+
+/*
+ * StorePartitionBound
+ *
+ * Store partition boundary info in pg_partition after checking for its
+ * validity.
+ */
+void
+StorePartitionBound(Oid relid, Oid parentId, Node *bound)
+{
+ Relation pg_partition;
+ Relation parent;
+ PartitionKey key;
+ PartitionDesc pdesc;
+ Oid with;
+ HeapTuple tuple;
+ char *boundString;
+ Datum values[Natts_pg_partition];
+ bool nulls[Natts_pg_partition];
+ ParseState *pstate = make_parsestate(NULL);
+
+ parent = heap_open(parentId, AccessShareLock);
+ key = parent->rd_partkey;
+ pdesc = RelationGetPartitionDesc(parent);
+
+ /*
+ * Check if 'bound' happens to overlap with any of parent's existing
+ * partitions.
+ */
+ switch (key->strategy)
+ {
+ case PARTITION_STRAT_LIST:
+ {
+ PartitionListSpec *list;
+
+ Assert(IsA(bound, PartitionListSpec));
+ list = (PartitionListSpec *) bound;
+ if (list_overlaps_existing_partition(key, list, pdesc, &with))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("new partition's list of values overlaps with that"
+ " of partition \"%s\" of \"%s\"",
+ get_rel_name(with), get_rel_name(parentId)),
+ errhint("Please specify a list of values that does not"
+ " overlap with any existing list partition's list."),
+ parser_errposition(pstate, list->location)));
+ break;
+ }
+
+ case PARTITION_STRAT_RANGE:
+ {
+ PartitionRangeSpec *range;
+
+ Assert(IsA(bound, PartitionRangeSpec));
+ range = (PartitionRangeSpec *) bound;
+ if (partition_range_empty(key, range))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("new partition's range contains no element"),
+ errhint("Please specify a non-empty range."),
+ parser_errposition(pstate, range->location)));
+
+ if (range_overlaps_existing_partition(key, range, pdesc, &with))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("new partition's range overlaps with that of"
+ " partition \"%s\" of \"%s\"",
+ get_rel_name(with), get_rel_name(parentId)),
+ errhint("Please specify a range that does not overlap"
+ " with any existing partition's range."),
+ parser_errposition(pstate, range->location)));
+ break;
+ }
+ }
+
+ pg_partition = heap_open(PartitionRelationId, RowExclusiveLock);
+
+ boundString = nodeToString(bound);
+
+ /* Build the tuple - bound can never be null */
+ MemSet(nulls, false, sizeof(nulls));
+
+ values[Anum_pg_partition_partrelid - 1] = ObjectIdGetDatum(relid);
+ values[Anum_pg_partition_partbound - 1] = CStringGetTextDatum(boundString);
+
+ tuple = heap_form_tuple(RelationGetDescr(pg_partition), values, nulls);
+
+ simple_heap_insert(pg_partition, tuple);
+
+ /* Update the indexes on pg_partition */
+ CatalogUpdateIndexes(pg_partition, tuple);
+
+ /*
+ * Invalid the parent's relcache so that the partition is now included in
+ * its partition descriptor.
+ */
+ CacheInvalidateRelcache(parent);
+
+ heap_close(parent, AccessShareLock);
+ heap_close(pg_partition, RowExclusiveLock);
+}
+
+/*
+ * Does new list partition 'bound' overlap any of existing list partitions?
+ */
+static bool
+list_overlaps_existing_partition(PartitionKey key,
+ PartitionListSpec *list_spec,
+ PartitionDesc pdesc,
+ Oid *with)
+{
+ PartitionListBound *new_list;
+ int i;
+
+ if (pdesc->nparts == 0)
+ return false;
+
+ new_list = make_list_from_spec(key, list_spec->values);
+
+ for (i = 0; i < pdesc->nparts; i++)
+ {
+ int j;
+
+ if (pdesc->lists[i]->contains_null && new_list->contains_null)
+ return true;
+
+ for (j = 0; j < pdesc->lists[i]->nvalues; j++)
+ {
+ int k;
+
+ for (k = 0; k < new_list->nvalues; k++)
+ {
+ int32 result;
+
+ result = DatumGetInt32(FunctionCall2Coll(&key->partsupfunc[0],
+ key->tcinfo->typcoll[0],
+ pdesc->lists[i]->values[j],
+ new_list->values[k]));
+ if (!result)
+ {
+ *with = pdesc->oids[i];
+ return true;
+ }
+ }
+ }
+ }
+
+ return false;
+}
+
+/*
+ * Is new partition's range empty?
+ */
+static bool
+partition_range_empty(PartitionKey key, PartitionRangeSpec *range_spec)
+{
+ PartitionRangeBound *lower,
+ *upper;
+
+ lower = make_range_from_spec(key, range_spec->lower, range_spec->lowerinc,
+ true);
+ upper = make_range_from_spec(key, range_spec->upper, range_spec->upperinc,
+ false);
+
+ /*
+ * Only either lower or upper is allowed to be unbounded, which if
+ * one of them is, the range is not empty.
+ */
+ if (lower->infinite || upper->infinite)
+ return false;
+
+ /*
+ * if lower and upper are equal in value and either and either of them
+ * is exclusive.
+ */
+ if (compare_range_keys(key, lower->val, upper->val) == 0 &&
+ (!lower->inclusive || !upper->inclusive))
+ return true;
+
+ return false;
+}
+
+/* Compare range composite keys using logic similar to RowCompareExpr */
+static int32
+compare_range_keys(PartitionKey key, Datum *val1, Datum *val2)
+{
+ int32 result;
+ int i;
+
+ for (i = 0; i < key->partnatts; i++)
+ {
+ result = DatumGetInt32(FunctionCall2Coll(&key->partsupfunc[i],
+ key->tcinfo->typcoll[i],
+ val1[i], val2[i]));
+ if (!result)
+ continue;
+ break;
+ }
+
+ return result;
+}
+
+/*
+ * Does new range partition's range overlap any of existing range partitions?
+ */
+static bool
+range_overlaps_existing_partition(PartitionKey key,
+ PartitionRangeSpec *range_spec,
+ PartitionDesc pdesc,
+ Oid *with)
+{
+ int i;
+ PartitionRangeBound *new_lower,
+ *new_upper;
+
+ if (pdesc->nparts == 0)
+ return false;
+
+ /*
+ * Create internal representation of rangelower and rangeupper from
+ * range_spec.
+ */
+ new_lower = make_range_from_spec(key, range_spec->lower, range_spec->lowerinc,
+ true);
+ new_upper = make_range_from_spec(key, range_spec->upper, range_spec->upperinc,
+ false);
+
+ /* Check with existing partitions for overlap */
+ for (i = 0; i < pdesc->nparts; i++)
+ {
+ if (partition_range_overlaps(key, new_lower, new_upper,
+ pdesc->rangelowers[i],
+ pdesc->rangeuppers[i]))
+ {
+ *with = pdesc->oids[i];
+ return true;
+ }
+ }
+
+ return false;
+}
+
+/*
+ * Make a PartitionListBound from list of values using the information in key
+ */
+static PartitionListBound *
+make_list_from_spec(PartitionKey key, List *values)
+{
+ PartitionListBound *list;
+ ListCell *cell;
+ int i;
+
+ list = (PartitionListBound *) palloc0(sizeof(PartitionListBound));
+ list->nvalues = list_length(values);
+ list->values = (Datum *) palloc(list->nvalues * sizeof(Datum));
+
+ i = 0;
+ foreach (cell, values)
+ {
+ Const *val = lfirst(cell);
+
+ if (val->constisnull)
+ list->contains_null = true;
+ else
+ list->values[i] = datumCopy(val->constvalue,
+ key->tcinfo->typbyval[0],
+ key->tcinfo->typlen[0]);
+ i++;
+ }
+
+ return list;
+}
+
+/*
+ * Make a PartitionRangeBound from arguments using the information in key
+ */
+static PartitionRangeBound *
+make_range_from_spec(PartitionKey key, List *val, bool inclusive, bool lower)
+{
+ PartitionRangeBound *range;
+ ListCell *cell;
+
+ range = (PartitionRangeBound *) palloc0(sizeof(PartitionRangeBound));
+ range->infinite = (val == NIL);
+ range->inclusive = inclusive;
+ range->lower = lower;
+
+ if (val)
+ {
+ int i;
+
+ range->val = (Datum *) palloc0(key->partnatts * sizeof(Datum));
+
+ i = 0;
+ foreach (cell, val)
+ {
+ Const *val = lfirst(cell);
+
+ if (val->constisnull)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("cannot specify NULL in range bound")));
+ else
+ range->val[i] = datumCopy(val->constvalue,
+ key->tcinfo->typbyval[i],
+ key->tcinfo->typlen[i]);
+ i++;
+ }
+ }
+
+ return range;
+}
+
+/* Check two range partitions for overlap */
+static bool
+partition_range_overlaps(PartitionKey key,
+ PartitionRangeBound *part1_lower,
+ PartitionRangeBound *part1_upper,
+ PartitionRangeBound *part2_lower,
+ PartitionRangeBound *part2_upper)
+{
+ if (partition_range_cmp_bounds(key, part1_lower, part2_lower) >= 0 &&
+ partition_range_cmp_bounds(key, part1_lower, part2_upper) <= 0)
+ return true;
+
+ if (partition_range_cmp_bounds(key, part2_lower, part1_lower) >= 0 &&
+ partition_range_cmp_bounds(key, part2_lower, part1_upper) <= 0)
+ return true;
+
+ return false;
+}
+
+/* Returns for two range partition bounds whether, b1 <=, =, >= b2 */
+static int32
+partition_range_cmp_bounds(PartitionKey key,
+ PartitionRangeBound *b1,
+ PartitionRangeBound *b2)
+{
+ int32 result;
+
+ /*
+ * First, handle cases involving infinity, which don't require invoking
+ * the comparison proc.
+ */
+ if (b1->infinite && b2->infinite)
+ {
+ /*
+ * Both are infinity, so they are equal unless one is lower and the
+ * other not.
+ */
+ if (b1->lower == b2->lower)
+ return 0;
+ else
+ return b1->lower ? -1 : 1;
+ }
+ else if (b1->infinite)
+ return b1->lower ? -1 : 1;
+ else if (b2->infinite)
+ return b2->lower ? 1 : -1;
+
+ /*
+ * Both boundaries are finite, so compare the held values.
+ */
+ result = compare_range_keys(key, b1->val, b2->val);
+
+ /*
+ * If the comparison is anything other than equal, we're done. If they
+ * compare equal though, we still have to consider whether the boundaries
+ * are inclusive or exclusive.
+ */
+ if (result == 0)
+ {
+ if (!b1->inclusive && !b2->inclusive)
+ {
+ /* both are exclusive */
+ if (b1->lower == b2->lower)
+ return 0;
+ else
+ return b1->lower ? 1 : -1;
+ }
+ else if (!b1->inclusive)
+ return b1->lower ? 1 : -1;
+ else if (!b2->inclusive)
+ return b2->lower ? -1 : 1;
+ else
+ {
+ /*
+ * Both are inclusive and the values held are equal, so they are
+ * equal regardless of whether they are upper or lower boundaries,
+ * or a mix.
+ */
+ return 0;
+ }
+ }
+
+ return result;
+}
+
+/*
+ * RemovePartitionByRelId
+ * Remove a pg_partition entry for a partition.
+ */
+void
+RemovePartitionEntryByRelId(Oid relid)
+{
+ Relation rel;
+ HeapTuple tuple;
+
+ rel = heap_open(PartitionRelationId, RowExclusiveLock);
+ tuple = SearchSysCache1(PARTRELID, ObjectIdGetDatum(relid));
+
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "cache lookup failed for partition entry of relation %u",
+ relid);
+
+ simple_heap_delete(rel, &tuple->t_self);
+
+ /* Update the indexes on pg_partitioned_rel */
+ CatalogUpdateIndexes(rel, tuple);
+
+ ReleaseSysCache(tuple);
+ heap_close(rel, RowExclusiveLock);
+}
+
+/*
+ * does relid have pg_partition entry?
+ */
+bool
+relid_is_partition(Oid relid)
+{
+ return SearchSysCacheExists1(PARTRELID, ObjectIdGetDatum(relid));
+}
+
+/*
+ * Returns inheritance parent of relid by scanning pg_inherits
+ */
+Oid
+get_partition_parent(Oid relid)
+{
+ Form_pg_inherits form;
+ Relation catalogRelation;
+ SysScanDesc scan;
+ ScanKeyData key[2];
+ HeapTuple tuple;
+
+ catalogRelation = heap_open(InheritsRelationId, AccessShareLock);
+
+ ScanKeyInit(&key[0],
+ Anum_pg_inherits_inhrelid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(relid));
+ ScanKeyInit(&key[1],
+ Anum_pg_inherits_inhseqno,
+ BTEqualStrategyNumber, F_INT4EQ,
+ Int32GetDatum(1));
+
+ scan = systable_beginscan(catalogRelation, InheritsRelidSeqnoIndexId, true,
+ NULL, 2, key);
+
+ tuple = systable_getnext(scan);
+ Assert(HeapTupleIsValid(tuple));
+
+ form = (Form_pg_inherits) GETSTRUCT(tuple);
+
+ systable_endscan(scan);
+ heap_close(catalogRelation, AccessShareLock);
+
+ return form->inhparent;
+}
+
+/*
+ * RelationDropPartitions
+ * Find and drop all partitions of relation with OID 'relid'
+ */
+void
+RelationDropPartitions(Oid relid)
+{
+ List *partitionOids;
+ ListCell *cell;
+
+ partitionOids = get_partitions(relid, AccessExclusiveLock);
+
+ foreach (cell, partitionOids)
+ {
+ Oid partitionOid = lfirst_oid(cell);
+ ObjectAddress obj;
+
+ obj.classId = RelationRelationId;
+ obj.objectId = partitionOid;
+ obj.objectSubId = 0;
+
+ performDeletion(&obj, DROP_CASCADE, 0);
+ }
+}
+
+/*
+ * RelationGetPartitionDesc
+ * Get the PartitionDesc of a partitioned table
+ *
+ * Memory for the struct is allocated in the caller's memory context while
+ * a copy is cached in rd_partdesc so that we don't compute the result
+ * unnecessarily (We don't want to return a pointer to the relcache copy
+ * though, since it could disappear due to relcache invalidation.)
+ *
+ * Never returns NULL. If there are no partitions yet, returns the struct
+ * with nparts set to 0.
+ */
+static PartitionDesc
+RelationGetPartitionDesc(Relation relation)
+{
+ int i,
+ nparts;
+ PartitionKey key = relation->rd_partkey;
+ MemoryContext oldcxt;
+ PartitionDesc result;
+ PartitionInfo **parts;
+
+ /* Quick copy and exit if already computed the descriptor */
+ if (relation->rd_partdesc != NULL)
+ return GetPartitionDescCopy(key, relation->rd_partdesc);
+
+ result = (PartitionDesc) palloc0(sizeof(PartitionDescData));
+
+ parts = GetRelPartitions(relation, &nparts);
+ result->nparts = nparts;
+
+ if (parts)
+ {
+ result->oids = (Oid *) palloc0(nparts * sizeof(Oid));
+
+ switch (key->strategy)
+ {
+ case PARTITION_STRAT_LIST:
+ result->lists = (PartitionListBound **) palloc0(nparts *
+ sizeof(PartitionListBound *));
+ break;
+
+ case PARTITION_STRAT_RANGE:
+ result->rangelowers = (PartitionRangeBound **) palloc0(nparts *
+ sizeof(PartitionRangeBound *));
+ result->rangeuppers = (PartitionRangeBound **) palloc0(nparts *
+ sizeof(PartitionRangeBound *));
+
+ /* Sort on range->upper */
+ qsort_arg(parts, nparts, sizeof(PartitionInfo *),
+ range_partition_cmp_upper, key);
+ break;
+ }
+
+ for (i = 0; i < nparts; i++)
+ {
+ result->oids[i] = parts[i]->oid;
+
+ if (parts[i]->list)
+ result->lists[i] = copyPartitionListBound(parts[i]->list, key);
+ else
+ {
+ result->rangelowers[i] = copyPartitionRangeBound(parts[i]->rangelower, key);
+ result->rangeuppers[i] = copyPartitionRangeBound(parts[i]->rangeupper, key);
+ }
+ }
+
+ /* Clean up */
+ free_partition_info_array(parts, nparts);
+ }
+
+ /* Save a copy in the relcache */
+ relation->rd_pdcxt = AllocSetContextCreate(CacheMemoryContext,
+ RelationGetRelationName(relation),
+ ALLOCSET_DEFAULT_MINSIZE,
+ ALLOCSET_DEFAULT_INITSIZE,
+ ALLOCSET_DEFAULT_MAXSIZE);
+
+ relation->rd_partdesc = MemoryContextAllocZero(relation->rd_pdcxt,
+ sizeof(PartitionDescData));
+ oldcxt = MemoryContextSwitchTo(relation->rd_pdcxt);
+ relation->rd_partdesc = GetPartitionDescCopy(key, result);
+ MemoryContextSwitchTo(oldcxt);
+
+ return result;
+}
+
+/*
+ * GetPartitionDescCopy
+ * Copy PartitionDesc of a partitioned relation into caller's context
+ *
+ * Like its caller, never returns NULL.
+ */
+static PartitionDesc
+GetPartitionDescCopy(PartitionKey key, PartitionDesc src)
+{
+ PartitionDesc result;
+ int i;
+
+ result = (PartitionDesc) palloc0(sizeof(PartitionDescData));
+ result->nparts = src->nparts;
+
+ /* If there are partitions, actually copy partitions */
+ if (src->nparts > 0)
+ {
+ result->oids = (Oid *) palloc0(result->nparts * sizeof(Oid));
+
+ switch (key->strategy)
+ {
+ case PARTITION_STRAT_LIST:
+ result->lists = (PartitionListBound **) palloc0(src->nparts *
+ sizeof(PartitionListBound *));
+ break;
+
+ case PARTITION_STRAT_RANGE:
+ result->rangelowers = (PartitionRangeBound **) palloc0(src->nparts *
+ sizeof(PartitionRangeBound *));
+ result->rangeuppers = (PartitionRangeBound **) palloc0(src->nparts *
+ sizeof(PartitionRangeBound *));
+ break;
+ }
+
+ for (i = 0; i < src->nparts; i++)
+ {
+ result->oids[i] = src->oids[i];
+
+ switch (key->strategy)
+ {
+ case PARTITION_STRAT_LIST:
+ result->lists[i] = copyPartitionListBound(src->lists[i], key);
+ break;
+
+ case PARTITION_STRAT_RANGE:
+ result->rangelowers[i] = copyPartitionRangeBound(src->rangelowers[i], key);
+ result->rangeuppers[i] = copyPartitionRangeBound(src->rangeuppers[i], key);
+ break;
+ }
+ }
+ }
+
+ return result;
+}
+
+/*
+ * GetRelPartitions
+ * Return information of partitions of rel
+ *
+ * Returns number of partitions in *nparts.
+ */
+static PartitionInfo **
+GetRelPartitions(Relation rel, int *nparts)
+{
+ int i;
+ Relation partrel;
+ List *partoids;
+ ListCell *cell;
+ PartitionInfo **parts;
+ PartitionKey key = rel->rd_partkey;
+
+ partoids = get_partitions(RelationGetRelid(rel), NoLock);
+
+ /* no partitions found */
+ if ((*nparts = list_length(partoids)) == 0)
+ return NULL;
+
+ partrel = heap_open(PartitionRelationId, AccessShareLock);
+
+ parts = (PartitionInfo **) palloc0(*nparts * sizeof(PartitionInfo *));
+
+ i = 0;
+ foreach(cell, partoids)
+ {
+ Oid partrelid = lfirst_oid(cell);
+ HeapTuple tuple;
+ Form_pg_partition form;
+ Datum datum;
+ bool isnull;
+ Node *bound;
+
+ tuple = SearchSysCache1(PARTRELID, partrelid);
+ Assert(HeapTupleIsValid(tuple));
+
+ form = (Form_pg_partition) GETSTRUCT(tuple);
+
+ parts[i] = (PartitionInfo *) palloc0(sizeof(PartitionInfo));
+ parts[i]->oid = partrelid;
+
+ datum = fastgetattr(tuple,
+ Anum_pg_partition_partbound,
+ partrel->rd_att,
+ &isnull);
+
+ bound = stringToNode(TextDatumGetCString(datum));
+
+ switch (key->strategy)
+ {
+ case PARTITION_STRAT_LIST:
+ {
+ PartitionListSpec *list_spec;
+
+ Assert(IsA(bound, PartitionListSpec));
+ list_spec = (PartitionListSpec *) bound;
+ parts[i]->list = make_list_from_spec(key, list_spec->values);
+ }
+ break;
+
+ case PARTITION_STRAT_RANGE:
+ {
+ PartitionRangeSpec *range_spec;
+
+ Assert(IsA(bound, PartitionRangeSpec));
+ range_spec = (PartitionRangeSpec *) bound;
+
+ parts[i]->rangelower = make_range_from_spec(key,
+ range_spec->lower,
+ range_spec->lowerinc,
+ true);
+ parts[i]->rangeupper = make_range_from_spec(key,
+ range_spec->upper,
+ range_spec->upperinc,
+ false);
+ }
+ break;
+ }
+
+ ReleaseSysCache(tuple);
+ i++;
+ }
+
+ heap_close(partrel, AccessShareLock);
+
+ return parts;
+}
+
+/*
+ * Wrapper around find_inheritance_children for use by partitioning
+ * related code
+ */
+static List *
+get_partitions(Oid relid, int lockmode)
+{
+ return find_inheritance_children(relid, lockmode);
+}
+
+/*
+ * range_partition_cmp_upper
+ * Compare two range partition PartitionInfos using their rangeupper
+ * bounds
+ *
+ * Used as a callback to qsort_arg for sorting range partitions.
+ */
+static int32
+range_partition_cmp_upper(const void *a, const void *b, void *arg)
+{
+ PartitionRangeBound *b1 = (*(const PartitionInfo **) a)->rangeupper,
+ *b2 = (*(const PartitionInfo **) b)->rangeupper;
+
+ if (b1->infinite || b2->infinite)
+ return b1->infinite ? 1 : -1;
+
+ return compare_range_keys((PartitionKey) arg, b1->val, b2->val);
+}
+
+/*
+ * Helper routine to copy a list partition bound struct
+ */
+static PartitionListBound *
+copyPartitionListBound(PartitionListBound *src, PartitionKey key)
+{
+ int i;
+ PartitionListBound *result;
+
+ result = (PartitionListBound *) palloc0(sizeof(PartitionListBound));
+ result->nvalues = src->nvalues;
+ result->contains_null = src->contains_null;
+ result->values = (Datum *) palloc0(src->nvalues * sizeof(Datum));
+
+ for (i = 0; i < src->nvalues; i++)
+ result->values[i] = datumCopy(src->values[i],
+ key->tcinfo->typbyval[0],
+ key->tcinfo->typlen[0]);
+ return result;
+}
+
+/*
+ * Helper routine to copy a range partition bound struct
+ */
+static PartitionRangeBound *
+copyPartitionRangeBound(PartitionRangeBound *src, PartitionKey key)
+{
+ int i;
+ PartitionRangeBound *result;
+
+ result = (PartitionRangeBound *) palloc0(sizeof(PartitionRangeBound));
+ result->infinite = src->infinite;
+ result->inclusive = src->inclusive;
+ result->lower = src->lower;
+
+ if (src->val)
+ {
+ result->val = (Datum *) palloc0(key->partnatts * sizeof(Datum));
+ for (i = 0; i < key->partnatts; i++)
+ result->val[i] = datumCopy(src->val[i],
+ key->tcinfo->typbyval[i],
+ key->tcinfo->typlen[i]);
+ }
+
+ return result;
+}
+
+/*
+ * Helper routine to deep-free a PartitionInfo array
+ */
+static void
+free_partition_info_array(PartitionInfo **p, int num)
+{
+ int i;
+
+ for (i = 0; i < num; i++)
+ {
+ if (p[i]->list)
+ {
+ pfree(p[i]->list->values);
+ pfree(p[i]->list);
+ }
+
+ if (p[i]->rangelower)
+ {
+ if (p[i]->rangelower->val)
+ pfree(p[i]->rangelower->val);
+ pfree(p[i]->rangelower);
+ }
+
+ if (p[i]->rangeupper)
+ {
+ if (p[i]->rangeupper->val)
+ pfree(p[i]->rangeupper->val);
+ pfree(p[i]->rangeupper);
+ }
+
+ pfree(p[i]);
+ }
+
+ pfree(p);
+}
+
+/*
+ * get_check_expr_from_partbound - for direct use by tablecmds.c
+ */
+Expr *
+get_check_expr_from_partbound(Relation rel, Relation parent, Node *bound)
+{
+ PartitionKey key = parent->rd_partkey;
+ Expr *my_expr;
+ int i;
+
+ if (IsA(bound, PartitionListSpec))
+ {
+ PartitionListSpec *list_spec = (PartitionListSpec *) bound;
+
+ my_expr = get_check_expr_for_list(key, list_spec->values);
+ }
+ else if (IsA(bound, PartitionRangeSpec))
+ {
+ PartitionRangeSpec *range = (PartitionRangeSpec *) bound;
+ BoolExpr *lower_expr = NULL,
+ *upper_expr = NULL;
+
+ if (range->lower)
+ lower_expr = (BoolExpr *) get_check_expr_for_rangebound(key,
+ range->lower,
+ range->lowerinc,
+ true);
+
+ if (range->upper)
+ upper_expr = (BoolExpr *) get_check_expr_for_rangebound(key,
+ range->upper,
+ range->upperinc,
+ false);
+ my_expr = makeBoolExpr(AND_EXPR,
+ list_concat(lower_expr ? lower_expr->args : NIL,
+ upper_expr ? upper_expr->args : NIL),
+ -1);
+ }
+
+ /*
+ * Translate vars in the generated expression with the correct attnos.
+ * Note that the vars in my_expr bear attnos dictated by key which carries
+ * attnos of the parent.
+ */
+ for (i = 0; i < key->partnatts; i++)
+ {
+ AttrNumber attno = key->partattrs[i],
+ new_attno;
+ char *attname = get_attname(RelationGetRelid(parent), attno);
+
+ new_attno = get_attnum(RelationGetRelid(rel), attname);
+
+ my_expr = (Expr *) translate_var_attno((Node *) my_expr, attno,
+ new_attno);
+ }
+
+ return my_expr;
+}
+
+/*
+ * get_check_expr_for_list
+ */
+static Expr *
+get_check_expr_for_list(PartitionKey key, List *values)
+{
+ ArrayExpr *values_arr = makeNode(ArrayExpr);
+ ScalarArrayOpExpr *arrayop_expr;
+ Node *key_col;
+ Oid operoid;
+ StrategyNumber strategy = BTEqualStrategyNumber;
+
+ /* Btree equality operator of specified opfamily */
+ operoid = get_opfamily_member(key->partopfamily[0],
+ key->tcinfo->typid[0],
+ key->tcinfo->typid[0],
+ strategy);
+
+ /* lexpr */
+ if (key->partattrs[0] != 0)
+ key_col = (Node *) makeVar(1, key->partattrs[0],
+ key->tcinfo->typid[0],
+ key->tcinfo->typmod[0],
+ key->tcinfo->typcoll[0],
+ 0);
+ else
+ key_col = (Node *) linitial(key->partexprs);
+
+ /* rexpr */
+ values_arr->array_typeid = get_array_type(key->tcinfo->typid[0]);
+ values_arr->array_collid = key->tcinfo->typcoll[0];
+ values_arr->element_typeid = key->tcinfo->typid[0];
+ values_arr->elements = values;
+ values_arr->multidims = false;
+ values_arr->location = -1;
+
+ /* build the actual predicate expression node */
+ arrayop_expr = makeNode(ScalarArrayOpExpr);
+ arrayop_expr->opno = operoid;
+ arrayop_expr->opfuncid = get_opcode(operoid);
+ arrayop_expr->useOr = true;
+ arrayop_expr->inputcollid = key->tcinfo->typcoll[0];
+ arrayop_expr->args = list_make2(key_col, values_arr);
+ arrayop_expr->location = -1;
+
+ return (Expr *) arrayop_expr;
+}
+
+/*
+ * get_check_expr_for_rangebound
+ */
+static Expr *
+get_check_expr_for_rangebound(PartitionKey key, List *values,
+ bool inclusive, bool lower)
+{
+ List *and_args = NIL;
+ ListCell *cell,
+ *partexprs_item;
+ int i;
+
+ i= 0;
+ partexprs_item = list_head(key->partexprs);
+ foreach (cell, values)
+ {
+ Const *key_val = lfirst(cell); /* rexpr */
+ Node *key_col;
+ Oid operoid;
+ StrategyNumber strategy;
+
+ /* lexpr */
+ if (key->partattrs[i] != 0)
+ {
+ key_col = (Node *) makeVar(1, key->partattrs[i],
+ key->tcinfo->typid[i],
+ key->tcinfo->typmod[i],
+ key->tcinfo->typcoll[i],
+ 0);
+ }
+ else
+ {
+ key_col = (Node *) lfirst(partexprs_item);
+ partexprs_item = lnext(partexprs_item);
+ }
+
+ /*
+ * Consider the following:
+ * key = (k1...kN); rangelower = (max1...maxN):
+ * The resulting expression looks like:
+ * k1 eq max1 AND k2 eq max2 ... AND kN gt/ge maxN
+ */
+ if (i == key->partnatts - 1)
+ {
+ if (lower)
+ strategy = inclusive ? BTGreaterEqualStrategyNumber : BTGreaterStrategyNumber;
+ else
+ strategy = inclusive ? BTLessEqualStrategyNumber : BTLessStrategyNumber;
+ }
+ else
+ strategy = BTEqualStrategyNumber;
+
+ operoid = get_opfamily_member(key->partopfamily[i],
+ key->tcinfo->typid[i],
+ key->tcinfo->typid[i],
+ strategy);
+
+ and_args = lappend(and_args,
+ make_opclause(operoid, BOOLOID,
+ false,
+ (Expr *) key_col,
+ (Expr *) key_val,
+ InvalidOid,
+ key->tcinfo->typcoll[i]));
+ i++;
+ }
+
+ return makeBoolExpr(AND_EXPR, and_args, -1);
+}
+
+/*
+ * translate_var_attno
+ * Changes Vars with a given attno in the provided expression tree to
+ * Vars with new_attno
+ */
+static Node *
+translate_var_attno(Node *expr, AttrNumber attno, AttrNumber new_attno)
+{
+ translate_var_attno_mutator_context cxt;
+
+ cxt.old_attno = attno;
+ cxt.new_attno = new_attno;
+
+ return expression_tree_mutator(expr, translate_var_attno_mutator, &cxt);
+}
+
+/*
+ * translate_var_attno_mutator
+ */
+static Node *
+translate_var_attno_mutator(Node *node,
+ translate_var_attno_mutator_context *cxt)
+{
+ if (node == NULL)
+ return NULL;
+
+ if (IsA(node, Var) && ((Var *) node)->varattno == cxt->old_attno)
+ {
+ Var *newvar = copyObject(node);
+
+ newvar->varattno = cxt->new_attno;
+
+ return (Node *) newvar;
+ }
+
+ return expression_tree_mutator(node, translate_var_attno_mutator,
+ (void *) cxt);
+}
+
+/*
+ * RelationGetPartitionConstraint
+ * Returns the equivelent CHECK constraint expression for a partition
+ */
+Node *
+RelationGetPartitionCheckExpr(Relation rel)
+{
+ /* Quick exit */
+ if (!relid_is_partition(RelationGetRelid(rel)))
+ return NULL;
+
+ /* Quick copy */
+ if (rel->rd_partcheck)
+ return copyObject(rel->rd_partcheck);
+
+ /* None of those, generate */
+ return generate_partition_check_expr(rel);
+}
+
+/*
+ * generate_partition_constraints
+ * Generate partition check constraint expression rel's pg_partition
+ * entry
+ *
+ * Result expression tree is stored CacheMemoryContext to ensure it survives
+ * as long as the relcache entry. But we should be running in a less long-lived
+ * working context. To avoid leaking cache memory if this routine fails partway
+ * routine fails partway through, we build in working memory and then copy
+ * the completed structure into cache memory.
+ */
+static Node *
+generate_partition_check_expr(Relation rel)
+{
+ Form_pg_partition form;
+ HeapTuple tuple;
+ MemoryContext oldcxt;
+ Datum boundDatum;
+ bool isnull;
+ Node *bound;
+ Node *my_expr = NULL,
+ *result = NULL;
+ Oid relid = RelationGetRelid(rel);
+ Relation parent = heap_open(get_partition_parent(relid), NoLock);
+
+ /* quick copy */
+ if (rel->rd_partcheck)
+ {
+ heap_close(parent, NoLock);
+ return copyObject(rel->rd_partcheck);
+ }
+
+ tuple = SearchSysCache1(PARTRELID, relid);
+ Assert(HeapTupleIsValid(tuple));
+ form = (Form_pg_partition) GETSTRUCT(tuple);
+ boundDatum = SysCacheGetAttr(PARTRELID, tuple,
+ Anum_pg_partition_partbound,
+ &isnull);
+ Assert(!isnull);
+ bound = stringToNode(TextDatumGetCString(boundDatum));
+
+ my_expr = (Node *) get_check_expr_from_partbound(rel, parent, bound);
+
+ /*
+ * If parent is also a partition, create a AND_EXPR BoolExpr with just
+ * created expression and parent's check expression, as its args.
+ */
+ if (relid_is_partition(RelationGetRelid(parent)))
+ {
+ Node *parent_expr;
+
+ parent_expr = generate_partition_check_expr(parent);
+ result = (Node *) makeBoolExpr(AND_EXPR,
+ list_make2(parent_expr, my_expr), -1);
+ }
+ else
+ result = my_expr;
+
+ oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
+ rel->rd_partcheck = copyObject(result);
+ MemoryContextSwitchTo(oldcxt);
+
+ ReleaseSysCache(tuple);
+ heap_close(parent, NoLock);
+
+ return result;
+}
+
+/*
+ * RelationGetPartitionDescNode
+ * Recursively form partition node tree rooted at rel's node
+ */
+PartitionDescNode
+RelationGetPartitionDescNode(Relation rel)
+{
+ PartitionDescNode root;
+
+ /*
+ * Recurse to build the tree of partition descriptor nodes with
+ * rel's descriptor as the root of the tree.
+ */
+ root = GetPartitionDescNodeRecurse(rel, 0);
+ root->index = 0;
+ root->num_leaf_partitions = get_leaf_partition_count(root);
+
+ return root;
+}
+
+/*
+ * GetPartitionDescNodeRecurse
+ * Workhorse of RelationGetPartitionDescNode
+ *
+ * offset is the index (0-based) of the first leaf node in the tree, as
+ * as determined and passed by the parent or the left sibling.
+ */
+static PartitionDescNode
+GetPartitionDescNodeRecurse(Relation rel, int offset)
+{
+ PartitionDescNode root,
+ prev;
+ PartitionDesc pdesc = RelationGetPartitionDesc(rel);
+ int i;
+
+ /* First build our own node */
+ root = (PartitionDescNode) palloc0(sizeof(PartitionDescNodeData));
+ root->pdesc = pdesc;
+ root->relid = RelationGetRelid(rel);
+ root->offset = offset;
+ root->downlink = NULL;
+ root->next = NULL;
+
+ /*
+ * Recursively add nodes for partitions that are themselves partitioned,
+ * linking ourselves to the first such node via 'downlink'. Each node
+ * thus created (being on the same level) is linked to the right sibling
+ * via 'next'. For each node, determine the offset to pass and once
+ * created, update its index and num_leaf_partitions.
+ */
+ prev = NULL;
+ for (i = 0; i < pdesc->nparts; i++)
+ {
+ Oid relid = pdesc->oids[i];
+ int offset;
+ Relation rel;
+ PartitionDescNode pdnode;
+
+ if (!relid_is_partitioned(relid))
+ continue;
+
+ rel = heap_open(relid, AccessShareLock);
+
+ /* Offset for the new node */
+ if (prev)
+ /*
+ * Count leaves on the left - prev node has the account of those
+ * on its own left and then there may have been some siblings
+ * in-between that were leaves themselves.
+ */
+ offset = prev->offset + prev->num_leaf_partitions +
+ (i - prev->index - 1);
+ else
+ /*
+ * We're the first child - account of leaf nodes comes from the
+ * parent. Left siblings were all leaves themselves.
+ */
+ offset = root->offset + i;
+
+ pdnode = GetPartitionDescNodeRecurse(rel, offset);
+ pdnode->index = i;
+ pdnode->num_leaf_partitions = get_leaf_partition_count(pdnode);
+
+ heap_close(rel, AccessShareLock);
+
+ /* Found our first child; link to it. */
+ if (root->downlink == NULL)
+ root->downlink = pdnode;
+
+ /* Link new node to the left sibling, if any */
+ if (prev)
+ prev->next = pdnode;
+
+ prev = pdnode;
+ }
+
+ return root;
+}
+
+/*
+ * get_leaf_partition_count
+ * Recursively count the number of leaf partitions in the partition
+ * tree rooted at pdnode
+ */
+static int
+get_leaf_partition_count(PartitionDescNode pdnode)
+{
+ int i;
+ int result = 0;
+ PartitionDescNode node = pdnode->downlink;
+
+ for (i = 0; i < pdnode->pdesc->nparts; i++)
+ {
+ /* Indexes 0..(node->index - 1) are of leaf partitions */
+ if (node && i == node->index)
+ {
+ result += get_leaf_partition_count(node);
+ node = node->next;
+ }
+ else
+ result += 1;
+ }
+
+ return result;
+}
+
+/*
+ * get_leaf_partition_count
+ * Recursively compute the list of OIDs of leaf partitions in the
+ * partition tree rooted at pdnode
+ */
+List *
+get_leaf_partition_oids(PartitionDescNode pdnode)
+{
+ int i;
+ List *result = NIL;
+ Oid *myparts = pdnode->pdesc->oids;
+ int n_myparts = pdnode->pdesc->nparts;
+ PartitionDescNode node = pdnode->downlink;
+
+ for (i = 0; i < n_myparts; i++)
+ {
+ /* Indexes 0..(node->index - 1) are leaf partitions */
+ if (node && i == node->index)
+ {
+ result = list_concat(result, get_leaf_partition_oids(node));
+ node = node->next;
+ }
+ else
+ result = lappend_oid(result, myparts[i]);
+ }
+
+ return result;
+}
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 403738d..14e81de 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -2464,7 +2464,7 @@ CopyFrom(CopyState cstate)
if (!skip_tuple)
{
/* Check the constraints of the tuple */
- if (cstate->rel->rd_att->constr)
+ if (cstate->rel->rd_att->constr || resultRelInfo->ri_PartitionCheck)
ExecConstraints(resultRelInfo, slot, estate);
if (useHeapMultiInsert)
diff --git a/src/backend/commands/lockcmds.c b/src/backend/commands/lockcmds.c
index 62671a8..5120c93 100644
--- a/src/backend/commands/lockcmds.c
+++ b/src/backend/commands/lockcmds.c
@@ -16,6 +16,7 @@
#include "access/heapam.h"
#include "catalog/namespace.h"
+#include "catalog/partition.h"
#include "catalog/pg_inherits_fn.h"
#include "commands/lockcmds.h"
#include "miscadmin.h"
@@ -63,7 +64,8 @@ LockTableCommand(LockStmt *lockstmt)
RangeVarCallbackForLockTable,
(void *) &lockstmt->mode);
- if (recurse)
+ /* Force inheritance recursion, if partitioned table. */
+ if (recurse || relid_is_partitioned(reloid))
LockTableRecurse(reloid, lockstmt->mode, lockstmt->nowait);
}
}
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index b7d230c..41e88bc 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -121,6 +121,19 @@ static List *on_commits = NIL;
/*
+ * For AT ATTACH PARTITION <table> FOR VALUES <partition_bound_spec>,
+ *
+ * Struct describing partition check expression corresponding to
+ * <partition_bound_spec> to validate for rows in <table> during
+ * Phase 3 scan.
+ */
+typedef struct PartitionCheck
+{
+ Expr *expr;
+ List *state;
+} PartitionCheck;
+
+/*
* State information for ALTER TABLE
*
* The pending-work queue for an ALTER TABLE is a List of AlteredTableInfo
@@ -163,6 +176,8 @@ typedef struct AlteredTableInfo
Oid newTableSpace; /* new tablespace; 0 means no change */
bool chgPersistence; /* T if SET LOGGED/UNLOGGED is used */
char newrelpersistence; /* if above is true */
+ PartitionCheck *partitionCheck; /* partition check expression for
+ * ATTACH PARTITION validation */
/* Objects to rebuild after completing ALTER TYPE operations */
List *changedConstraintOids; /* OIDs of constraints to rebuild */
List *changedConstraintDefs; /* string definitions of same */
@@ -712,6 +727,19 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
StoreCatalogInheritance(relationId, inheritOids);
/*
+ * Process and store partition bound. Do this before
+ * CommandCounterIncrement() below, so that we are not deemed an existing
+ * partition by StorePartitionBound() (which is basically found out by
+ * querying pg_inherits).
+ */
+ if (stmt->partbound)
+ {
+ Oid parentId = linitial_oid(inheritOids);
+
+ StorePartitionBound(relationId, parentId, stmt->partbound);
+ }
+
+ /*
* We must bump the command counter to make the newly-created relation
* tuple visible for opening.
*/
@@ -995,6 +1023,13 @@ RangeVarCallbackForDropRelation(const RangeVar *rel, Oid relOid, Oid oldRelOid,
if (!OidIsValid(relOid))
return;
+ if (relid_is_partition(relOid))
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("\"%s\" is a partition of \"%s\"", rel->relname,
+ get_rel_name(get_partition_parent(relOid))),
+ errhint("Use ALTER TABLE DETACH PARTITION to be able to drop it.")));
+
tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relOid));
if (!HeapTupleIsValid(tuple))
return; /* concurrently dropped, so nothing to do */
@@ -1086,7 +1121,8 @@ ExecuteTruncate(TruncateStmt *stmt)
rels = lappend(rels, rel);
relids = lappend_oid(relids, myrelid);
- if (recurse)
+ /* Force inheritance recursion, if partitioned table. */
+ if (recurse || relid_is_partitioned(myrelid))
{
ListCell *child;
List *children;
@@ -1810,8 +1846,12 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
Node *expr;
bool found_whole_row;
- /* ignore if the constraint is non-inheritable */
- if (check[i].ccnoinherit)
+ /*
+ * ignore if the constraint is non-inheritable and this is not
+ * the partition case (constraints of partitioned tables are
+ * always present in their partitions)
+ */
+ if (check[i].ccnoinherit && !is_partition)
continue;
/* Adjust Vars to match new table's column numbering */
@@ -2246,6 +2286,11 @@ renameatt_check(Oid myrelid, Form_pg_class classform, bool recursing)
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot rename column of typed table")));
+ if (relid_is_partition(myrelid) && !recursing)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot rename column of a partition")));
+
/*
* Renaming the columns of sequences or toast tables doesn't actually
* break anything from the system's point of view, since internal
@@ -2472,11 +2517,13 @@ renameatt(RenameStmt *stmt)
return InvalidObjectAddress;
}
+ /* Force inheritance recursion, if partitioned table. */
attnum =
renameatt_internal(relid,
stmt->subname, /* old att name */
stmt->newname, /* new att name */
- interpretInhOption(stmt->relation->inhOpt), /* recursive? */
+ interpretInhOption(stmt->relation->inhOpt) ||
+ relid_is_partitioned(relid), /* recursive? */
false, /* recursing? */
0, /* expected inhcount */
stmt->behavior);
@@ -2624,11 +2671,13 @@ RenameConstraint(RenameStmt *stmt)
}
}
+ /* Force inheritance recursion, if partitioned table. */
return
rename_constraint_internal(relid, typid,
stmt->subname,
stmt->newname,
- stmt->relation ? interpretInhOption(stmt->relation->inhOpt) : false, /* recursive? */
+ stmt->relation ? interpretInhOption(stmt->relation->inhOpt) ||
+ relid_is_partitioned(relid) : false, /* recursive? */
false, /* recursing? */
0 /* expected inhcount */ );
@@ -2870,8 +2919,11 @@ AlterTable(Oid relid, LOCKMODE lockmode, AlterTableStmt *stmt)
CheckTableNotInUse(rel, "ALTER TABLE");
+ /* Force inheritance recursion, if partitioned table */
ATController(stmt,
- rel, stmt->cmds, interpretInhOption(stmt->relation->inhOpt),
+ rel, stmt->cmds,
+ interpretInhOption(stmt->relation->inhOpt) ||
+ relid_is_partitioned(RelationGetRelid(rel)),
lockmode);
}
@@ -3990,7 +4042,7 @@ ATRewriteTables(AlterTableStmt *parsetree, List **wqueue, LOCKMODE lockmode)
* Test the current data within the table against new constraints
* generated by ALTER TABLE commands, but don't rebuild data.
*/
- if (tab->constraints != NIL || tab->new_notnull)
+ if (tab->constraints != NIL || tab->new_notnull || tab->partitionCheck)
ATRewriteTable(tab, InvalidOid, lockmode);
/*
@@ -4134,6 +4186,21 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
}
}
+ /*
+ * Build expression execution states for partition check expression
+ * as well.
+ */
+ if (tab->partitionCheck)
+ {
+ List *expr;
+
+ needscan = true;
+ Assert(tab->partitionCheck->expr);
+ expr = list_make1(tab->partitionCheck->expr);
+ tab->partitionCheck->state = (List *)
+ ExecPrepareExpr((Expr *) expr, estate);
+ }
+
foreach(l, tab->newvals)
{
NewColumnValue *ex = lfirst(l);
@@ -4323,6 +4390,12 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
}
}
+ if (tab->partitionCheck &&
+ !ExecQual(tab->partitionCheck->state, econtext, true))
+ ereport(ERROR,
+ (errcode(ERRCODE_CHECK_VIOLATION),
+ errmsg("table contains a row violating the partition bound specification")));
+
/* Write the tuple out to the new relation */
if (newrel)
heap_insert(newrel, tuple, mycid, hi_options, bistate);
@@ -4520,7 +4593,8 @@ ATSimpleRecursion(List **wqueue, Relation rel,
*/
if (recurse &&
(rel->rd_rel->relkind == RELKIND_RELATION ||
- rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE))
+ rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE ||
+ rel->rd_rel->relkind == RELKIND_PARTITIONED_REL))
{
Oid relid = RelationGetRelid(rel);
ListCell *child;
@@ -4842,6 +4916,11 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
if (recursing)
ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
+ if (relid_is_partition(RelationGetRelid(rel)) && !recursing)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot add column to a partition")));
+
attrdesc = heap_open(AttributeRelationId, RowExclusiveLock);
/*
@@ -5297,6 +5376,7 @@ ATExecDropNotNull(Relation rel, const char *colName, LOCKMODE lockmode)
List *indexoidlist;
ListCell *indexoidscan;
ObjectAddress address;
+ Oid relid;
/*
* lookup the attribute
@@ -5364,6 +5444,28 @@ ATExecDropNotNull(Relation rel, const char *colName, LOCKMODE lockmode)
list_free(indexoidlist);
/*
+ * If rel is partition, throw error if the attribute is not null in
+ * parent
+ */
+ relid = RelationGetRelid(rel);
+ if (relid_is_partition(relid))
+ {
+ Oid parentId = get_partition_parent(relid);
+ Relation parent = heap_open(parentId, AccessShareLock);
+ TupleDesc tupDesc = RelationGetDescr(parent);
+ AttrNumber parent_attnum;
+
+ parent_attnum = get_attnum(parentId, colName);
+ if (tupDesc->attrs[parent_attnum - 1]->attnotnull)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("column \"%s\" is not null in parent",
+ colName),
+ errhint("Please drop not null in the parent instead")));
+ heap_close(parent, AccessShareLock);
+ }
+
+ /*
* Okay, actually perform the catalog change ... if needed
*/
if (((Form_pg_attribute) GETSTRUCT(tuple))->attnotnull)
@@ -5893,6 +5995,11 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
if (recursing)
ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
+ if (relid_is_partition(RelationGetRelid(rel)) && !recursing)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot drop column from a partition")));
+
/*
* get the number of the attribute
*/
@@ -5938,11 +6045,13 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
if (!is_expr)
ereport(ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
- errmsg("cannot drop column named in partition key")));
+ errmsg("cannot drop column named in%s partition key",
+ recursing ? " downstream" : "")));
else
ereport(ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
- errmsg("cannot drop column referenced in partition key expression")));
+ errmsg("cannot drop column referenced in%s partition key expression",
+ recursing ? " downstream" : "")));
}
ReleaseSysCache(tuple);
@@ -6376,8 +6485,9 @@ ATAddCheckConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
/*
* If adding a NO INHERIT constraint, no need to find our children.
+ * However, that applies only to regular inheritance parents.
*/
- if (constr->is_no_inherit)
+ if (constr->is_no_inherit && !relid_is_partitioned(RelationGetRelid(rel)))
return address;
/*
@@ -7938,9 +8048,12 @@ ATExecDropConstraint(Relation rel, const char *constrName,
/*
* Propagate to children as appropriate. Unlike most other ALTER
* routines, we have to do this one level of recursion at a time; we can't
- * use find_all_inheritors to do it in one pass.
+ * use find_all_inheritors to do it in one pass. Note that if the parent
+ * is a partitioned table, we propagate to children (partitions) despite
+ * is_no_inherit_constraint.
*/
- if (!is_no_inherit_constraint)
+ if (!is_no_inherit_constraint ||
+ relid_is_partitioned(RelationGetRelid(rel)))
children = find_inheritance_children(RelationGetRelid(rel), lockmode);
else
children = NIL;
@@ -8070,6 +8183,11 @@ ATPrepAlterColumnType(List **wqueue,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot alter column type of typed table")));
+ if (relid_is_partition(RelationGetRelid(rel)) && !recursing)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot alter column type of a partition")));
+
/* lookup the attribute so we can check inheritance status */
tuple = SearchSysCacheAttName(RelationGetRelid(rel), colName);
if (!HeapTupleIsValid(tuple))
@@ -8100,11 +8218,13 @@ ATPrepAlterColumnType(List **wqueue,
if (!is_expr)
ereport(ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
- errmsg("cannot alter column named in partition key")));
+ errmsg("cannot alter column named in%s partition key",
+ recursing ? " downstream" : "")));
else
ereport(ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
- errmsg("cannot alter column referenced in partition key expression")));
+ errmsg("cannot alter column referenced in%s partition key expression",
+ recursing ? " downstream" : "")));
}
/* Look up the target type */
@@ -10237,6 +10357,11 @@ ATPrepAddInherit(Relation child_rel)
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot change inheritance of typed table")));
+ if (relid_is_partition(RelationGetRelid(child_rel)))
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot change inheritance of a partition")));
+
if (child_rel->rd_rel->relkind == RELKIND_PARTITIONED_REL)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
@@ -10293,6 +10418,11 @@ ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode)
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot inherit from partitioned table \"%s\"", parent->relname)));
+ if (relid_is_partition(RelationGetRelid(parent_rel)))
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot inherit from a partition")));
+
/*
* Prevent circularity by seeing if proposed parent inherits from child.
* (In particular, this disallows making a rel inherit from itself.)
@@ -10679,6 +10809,11 @@ ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
ObjectAddress address;
Relation parent_rel;
+ if (relid_is_partition(RelationGetRelid(rel)))
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot change inheritance of a partition")));
+
/*
* AccessShareLock on the parent is probably enough, seeing that DROP
* TABLE doesn't lock parent tables at all. We need some lock since we'll
@@ -12558,6 +12693,12 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
*/
ATSimplePermissions(attachRel, ATT_TABLE);
+ if (relid_is_partition(RelationGetRelid(attachRel)))
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("\"%s\" is already a partition",
+ RelationGetRelationName(attachRel))));
+
if (attachRel->rd_rel->reloftype)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
@@ -12591,6 +12732,12 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
errmsg("cannot attach a temporary relation of another session as partition ")));
/* If parent has OIDs, attachRel must have OIDs */
+ if (attachRel->rd_rel->reloftype)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot attach a typed table as partition")));
+
+ /* If parent has OIDs then child must have OIDs */
if (rel->rd_rel->relhasoids && !attachRel->rd_rel->relhasoids)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
@@ -12633,6 +12780,30 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
/* OK to create inheritance. Rest of the checks performed there */
CreateInheritance(attachRel, rel);
+ /*
+ * Set up to have the rows in table to be checked for violation of the
+ * partition bound spec in phase 3 scan.
+ */
+ if (!cmd->skip_validate)
+ {
+ AlteredTableInfo *tab;
+ PartitionCheck *check;
+ Expr *my_expr,
+ *parent_expr = (Expr *) RelationGetPartitionCheckExpr(rel);
+
+ tab = ATGetQueueEntry(wqueue, attachRel);
+ check = (PartitionCheck *) palloc0(sizeof(PartitionCheck));
+ my_expr = get_check_expr_from_partbound(attachRel, rel, cmd->bound);
+ check->expr = parent_expr ? makeBoolExpr(AND_EXPR,
+ list_make2(parent_expr, my_expr), -1)
+ : my_expr;
+ tab->partitionCheck = check;
+ }
+
+ /* Store bound into the catalog */
+ StorePartitionBound(RelationGetRelid(attachRel), RelationGetRelid(rel),
+ cmd->bound);
+
ObjectAddressSet(address, RelationRelationId, RelationGetRelid(attachRel));
/* keep our lock until commit */
@@ -12657,6 +12828,15 @@ ATExecDetachPartition(Relation rel, RangeVar *name)
/* All inheritance related checks are performed within the function */
RemoveInheritance(partRel, rel);
+ /* Remove the pg_partition entry */
+ RemovePartitionEntryByRelId(RelationGetRelid(partRel));
+
+ /*
+ * Invalidate the relcache so that the partition is no longer included
+ * in our partition descriptor.
+ */
+ CacheInvalidateRelcache(rel);
+
ObjectAddressSet(address, RelationRelationId, RelationGetRelid(partRel));
/* keep our lock until commit */
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 67492a5..23ed0b6 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -42,6 +42,7 @@
#include "access/transam.h"
#include "access/xact.h"
#include "catalog/namespace.h"
+#include "catalog/partition.h"
#include "commands/matview.h"
#include "commands/trigger.h"
#include "executor/execdebug.h"
@@ -1250,6 +1251,8 @@ InitResultRelInfo(ResultRelInfo *resultRelInfo,
resultRelInfo->ri_ConstraintExprs = NULL;
resultRelInfo->ri_junkFilter = NULL;
resultRelInfo->ri_projectReturning = NULL;
+ resultRelInfo->ri_PartitionCheck = (Expr *)
+ RelationGetPartitionCheckExpr(resultRelationDesc);
}
/*
@@ -1683,6 +1686,49 @@ ExecRelCheck(ResultRelInfo *resultRelInfo,
return NULL;
}
+/*
+ * ExecPartitionCheck --- check that tuple meets the partition boundary
+ * specification.
+ *
+ * Note: This is called, *iff* resultRelInfo is the main target table.
+ */
+static bool
+ExecPartitionCheck(ResultRelInfo *resultRelInfo, TupleTableSlot *slot,
+ EState *estate)
+{
+ ExprContext *econtext;
+
+ /*
+ * If first time through, build expression state tree for the partition
+ * check expression. Keep it in the per-query memory context so they'll
+ * survive throughout the query.
+ */
+ if (resultRelInfo->ri_PartitionCheckExpr == NULL)
+ {
+ List *qual = make_ands_implicit(resultRelInfo->ri_PartitionCheck);
+ resultRelInfo->ri_PartitionCheckExpr = (List *)
+ ExecPrepareExpr((Expr *) qual, estate);
+ }
+
+ /*
+ * We will use the EState's per-tuple context for evaluating constraint
+ * expressions (creating it if it's not already there).
+ */
+ econtext = GetPerTupleExprContext(estate);
+
+ /* Arrange for econtext's scan tuple to be the tuple under test */
+ econtext->ecxt_scantuple = slot;
+
+ /*
+ * NOTE: SQL specifies that a NULL result from a constraint expression
+ * is not to be treated as a failure. Therefore, tell ExecQual to
+ * return TRUE for NULL.
+ *
+ * XXX - although, it's unlikely that NULL would result.
+ */
+ return ExecQual(resultRelInfo->ri_PartitionCheckExpr, econtext, true);
+}
+
void
ExecConstraints(ResultRelInfo *resultRelInfo,
TupleTableSlot *slot, EState *estate)
@@ -1694,9 +1740,9 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
Bitmapset *insertedCols;
Bitmapset *updatedCols;
- Assert(constr);
+ Assert(constr || resultRelInfo->ri_PartitionCheck);
- if (constr->has_not_null)
+ if (constr && constr->has_not_null)
{
int natts = tupdesc->natts;
int attrChk;
@@ -1727,7 +1773,7 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
}
}
- if (constr->num_check > 0)
+ if (constr && constr->num_check > 0)
{
const char *failed;
@@ -1751,6 +1797,29 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
errtableconstraint(rel, failed)));
}
}
+
+ if (resultRelInfo->ri_PartitionCheck)
+ {
+ if (!ExecPartitionCheck(resultRelInfo, slot, estate))
+ {
+ char *val_desc;
+
+ insertedCols = GetInsertedColumns(resultRelInfo, estate);
+ updatedCols = GetUpdatedColumns(resultRelInfo, estate);
+ modifiedCols = bms_union(insertedCols, updatedCols);
+ val_desc = ExecBuildSlotValueDescription(RelationGetRelid(rel),
+ slot,
+ tupdesc,
+ modifiedCols,
+ 64);
+ ereport(ERROR,
+ (errcode(ERRCODE_CHECK_VIOLATION),
+ errmsg("new row violates the partition boundary"
+ " specification of \"%s\"",
+ RelationGetRelationName(rel)),
+ val_desc ? errdetail("Failing row contains %s.", val_desc) : 0));
+ }
+ }
}
/*
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 7ad1140..6ecb6be 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -354,7 +354,7 @@ ExecInsert(ModifyTableState *mtstate,
/*
* Check the constraints of the tuple
*/
- if (resultRelationDesc->rd_att->constr)
+ if (resultRelationDesc->rd_att->constr || resultRelInfo->ri_PartitionCheck)
ExecConstraints(resultRelInfo, slot, estate);
if (onconflict != ONCONFLICT_NONE && resultRelInfo->ri_NumIndices > 0)
@@ -907,7 +907,7 @@ lreplace:;
/*
* Check the constraints of the tuple
*/
- if (resultRelationDesc->rd_att->constr)
+ if (resultRelationDesc->rd_att->constr || resultRelInfo->ri_PartitionCheck)
ExecConstraints(resultRelInfo, slot, estate);
/*
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 8059594..45f0064 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -2247,6 +2247,35 @@ _readExtensibleNode(void)
}
/*
+ * _readPartitionListSpec
+ */
+static PartitionListSpec *
+_readPartitionListSpec(void)
+{
+ READ_LOCALS(PartitionListSpec);
+
+ READ_NODE_FIELD(values);
+
+ READ_DONE();
+}
+
+/*
+ * _readPartitionRangeSpec
+ */
+static PartitionRangeSpec *
+_readPartitionRangeSpec(void)
+{
+ READ_LOCALS(PartitionRangeSpec);
+
+ READ_BOOL_FIELD(lowerinc);
+ READ_NODE_FIELD(lower);
+ READ_BOOL_FIELD(upperinc);
+ READ_NODE_FIELD(upper);
+
+ READ_DONE();
+}
+
+/*
* parseNodeString
*
* Given a character string representing a node tree, parseNodeString creates
@@ -2476,6 +2505,10 @@ parseNodeString(void)
return_value = _readAlternativeSubPlan();
else if (MATCH("EXTENSIBLENODE", 14))
return_value = _readExtensibleNode();
+ else if (MATCH("PARTITIONLISTVALUES", 19))
+ return_value = _readPartitionListSpec();
+ else if (MATCH("PARTITIONRANGE", 14))
+ return_value = _readPartitionRangeSpec();
else
{
elog(ERROR, "badly formatted node string \"%.32s\"...", token);
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index 9c11b09..ed3193b 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -27,6 +27,7 @@
#include "catalog/catalog.h"
#include "catalog/dependency.h"
#include "catalog/heap.h"
+#include "catalog/partition.h"
#include "catalog/pg_am.h"
#include "catalog/pg_constraint.h"
#include "foreign/fdwapi.h"
@@ -1124,6 +1125,7 @@ get_relation_constraints(PlannerInfo *root,
Index varno = rel->relid;
Relation relation;
TupleConstr *constr;
+ Node *pexpr;
/*
* We assume the relation has already been safely locked.
@@ -1203,6 +1205,17 @@ get_relation_constraints(PlannerInfo *root,
}
}
+ /* Append partition check constraint, if any */
+ pexpr = RelationGetPartitionCheckExpr(relation);
+ if (pexpr)
+ {
+ /* Fix Vars to have the desired varno */
+ if (varno != 1)
+ ChangeVarNodes(pexpr, 1, varno, 0);
+
+ result = list_concat(result, make_ands_implicit((Expr *) pexpr));
+ }
+
heap_close(relation, NoLock);
return result;
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 7d2fedf..f55129a 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -25,6 +25,7 @@
#include "postgres.h"
#include "access/sysattr.h"
+#include "catalog/partition.h"
#include "catalog/pg_type.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
diff --git a/src/backend/parser/parse_clause.c b/src/backend/parser/parse_clause.c
index 307e383..5124dbf 100644
--- a/src/backend/parser/parse_clause.c
+++ b/src/backend/parser/parse_clause.c
@@ -202,8 +202,8 @@ setTargetTable(ParseState *pstate, RangeVar *relation,
pstate->p_target_rangetblentry = rte;
/*
- * Override specified inheritance option, if relation is a partitioned
- * table and it's a target of INSERT (ie, alsoSource == false).
+ * For UPDATE/DELETE, force inheritance recursion, if partitioned
+ * table.
*/
if (alsoSource)
rte->inh |= relid_is_partitioned(rte->relid);
diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c
index abcc1d6..b3870f6 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -1218,8 +1218,7 @@ addRangeTableEntry(ParseState *pstate,
* The initial default on access checks is always check-for-READ-access,
* which is the right thing for all except target tables.
*
- * Note: we override the specified inheritance option, if relation is a
- * partitioned table.
+ * Note: We force inheritance recursion, if partitioned table.
*/
rte->lateral = false;
rte->inh = inh || relid_is_partitioned(RelationGetRelid(rel));
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e75add7..f5a3469 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -32,6 +32,7 @@
#include "catalog/dependency.h"
#include "catalog/heap.h"
#include "catalog/index.h"
+#include "catalog/partition.h"
#include "catalog/namespace.h"
#include "catalog/partition.h"
#include "catalog/pg_am.h"
@@ -3105,6 +3106,11 @@ transformPartitionOf(CreateStmtContext *cxt, Node *bound)
if (parentRel->rd_rel->relhasoids);
cxt->hasoids = true;
+ /*
+ * Create ColumnDefs correponding to parent's attributes as "inherited"
+ * attributes. Also, set is_for_partition whose function is similar to
+ * is_from_type for MergeAttributes()'s purposes.
+ */
tupdesc = RelationGetDescr(parentRel);
for (i = 0; i < tupdesc->natts; i++)
{
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 49f6fad..bf59cc3 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -8159,6 +8159,84 @@ get_rule_expr(Node *node, deparse_context *context,
}
break;
+ case T_PartitionListSpec:
+ {
+ PartitionListSpec *list_spec = (PartitionListSpec *) node;
+ ListCell *cell;
+ char *sep;
+
+ appendStringInfoString(buf, "FOR VALUES");
+
+ appendStringInfoString(buf, " IN (");
+ sep = "";
+ foreach (cell, list_spec->values)
+ {
+ Const *val = lfirst(cell);
+
+ appendStringInfoString(buf, sep);
+ get_const_expr(val, context, -1);
+ sep = ", ";
+ }
+
+ appendStringInfoString(buf, ")");
+ }
+ break;
+
+ case T_PartitionRangeSpec:
+ {
+ PartitionRangeSpec *range_spec = (PartitionRangeSpec *) node;
+ ListCell *cell;
+ char *sep;
+
+ appendStringInfoString(buf, "FOR VALUES");
+
+ appendStringInfoString(buf, " START");
+ if (!range_spec->lower)
+ appendStringInfoString(buf, " UNBOUNDED");
+ else
+ {
+ appendStringInfoString(buf, " (");
+
+ sep = "";
+ foreach (cell, range_spec->lower)
+ {
+ Const *val = lfirst(cell);
+
+ appendStringInfoString(buf, sep);
+ get_const_expr(val, context, -1);
+ sep = ", ";
+ }
+ appendStringInfoString(buf, ")");
+
+ if (!range_spec->lowerinc)
+ appendStringInfoString(buf, " EXCLUSIVE");
+ }
+
+ appendStringInfoString(buf, " END");
+
+ if (!range_spec->upper)
+ appendStringInfoString(buf, " UNBOUNDED");
+ else
+ {
+ appendStringInfoString(buf, " (");
+
+ sep = "";
+ foreach (cell, range_spec->upper)
+ {
+ Const *val = lfirst(cell);
+
+ appendStringInfoString(buf, sep);
+ get_const_expr(val, context, -1);
+ sep = ", ";
+ }
+ appendStringInfoString(buf, ")");
+
+ if (range_spec->upperinc)
+ appendStringInfoString(buf, " INCLUSIVE");
+ }
+ }
+ break;
+
case T_List:
{
char *sep;
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 2841745..05c4f87 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -50,6 +50,7 @@
#include "catalog/pg_database.h"
#include "catalog/pg_namespace.h"
#include "catalog/pg_opclass.h"
+#include "catalog/partition.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_rewrite.h"
#include "catalog/pg_shseclabel.h"
@@ -2055,6 +2056,10 @@ RelationDestroyRelation(Relation relation, bool remember_tupdesc)
if (relation->rd_rsdesc)
MemoryContextDelete(relation->rd_rsdesc->rscxt);
FreePartitionKey(relation->rd_partkey);
+ if (relation->rd_pdcxt)
+ MemoryContextDelete(relation->rd_pdcxt);
+ if (relation->rd_partcheck)
+ pfree(relation->rd_partcheck);
if (relation->rd_fdwroutine)
pfree(relation->rd_fdwroutine);
pfree(relation);
@@ -4984,6 +4989,8 @@ load_relcache_init_file(bool shared)
rel->trigdesc = NULL;
rel->rd_rsdesc = NULL;
rel->rd_partkey = NULL;
+ rel->rd_partdesc = NULL;
+ rel->rd_partcheck = NULL;
rel->rd_indexprs = NIL;
rel->rd_indpred = NIL;
rel->rd_exclops = NULL;
diff --git a/src/backend/utils/cache/syscache.c b/src/backend/utils/cache/syscache.c
index a6563fe..4a5783a 100644
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -49,6 +49,7 @@
#include "catalog/pg_operator.h"
#include "catalog/pg_opfamily.h"
#include "catalog/pg_partitioned.h"
+#include "catalog/pg_partition.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_range.h"
#include "catalog/pg_rewrite.h"
@@ -569,6 +570,17 @@ static const struct cachedesc cacheinfo[] = {
},
8
},
+ {PartitionRelationId, /* PARTRELID */
+ PartitionRelidIndexId,
+ 1,
+ {
+ Anum_pg_partition_partrelid,
+ 0,
+ 0,
+ 0
+ },
+ 128
+ },
{PartitionedRelationId, /* PARTEDRELID */
PartitionedRelidIndexId,
1,
diff --git a/src/bin/pg_dump/common.c b/src/bin/pg_dump/common.c
index 1cbb987..c8e56bd 100644
--- a/src/bin/pg_dump/common.c
+++ b/src/bin/pg_dump/common.c
@@ -68,6 +68,8 @@ static int numextmembers;
static void flagInhTables(TableInfo *tbinfo, int numTables,
InhInfo *inhinfo, int numInherits);
+static void flagPartitions(TableInfo *tblinfo, int numTables,
+ PartInfo *partinfo, int numPartitions);
static void flagInhAttrs(DumpOptions *dopt, TableInfo *tblinfo, int numTables);
static DumpableObject **buildIndexArray(void *objArray, int numObjs,
Size objSize);
@@ -75,6 +77,8 @@ static int DOCatalogIdCompare(const void *p1, const void *p2);
static int ExtensionMemberIdCompare(const void *p1, const void *p2);
static void findParentsByOid(TableInfo *self,
InhInfo *inhinfo, int numInherits);
+static void findPartitionParentByOid(TableInfo *self, PartInfo *partinfo,
+ int numPartitions);
static int strInArray(const char *pattern, char **arr, int arr_size);
@@ -93,8 +97,10 @@ getSchemaData(Archive *fout, int *numTablesPtr)
NamespaceInfo *nspinfo;
ExtensionInfo *extinfo;
InhInfo *inhinfo;
+ PartInfo *partinfo;
int numAggregates;
int numInherits;
+ int numPartitions;
int numRules;
int numProcLangs;
int numCasts;
@@ -232,6 +238,10 @@ getSchemaData(Archive *fout, int *numTablesPtr)
inhinfo = getInherits(fout, &numInherits);
if (g_verbose)
+ write_msg(NULL, "reading partition information\n");
+ partinfo = getPartitions(fout, &numPartitions);
+
+ if (g_verbose)
write_msg(NULL, "reading event triggers\n");
getEventTriggers(fout, &numEventTriggers);
@@ -245,6 +255,11 @@ getSchemaData(Archive *fout, int *numTablesPtr)
write_msg(NULL, "finding inheritance relationships\n");
flagInhTables(tblinfo, numTables, inhinfo, numInherits);
+ /* Link tables to partition parents, mark parents as interesting */
+ if (g_verbose)
+ write_msg(NULL, "finding partition relationships\n");
+ flagPartitions(tblinfo, numTables, partinfo, numPartitions);
+
if (g_verbose)
write_msg(NULL, "reading column info for interesting tables\n");
getTableAttrs(fout, tblinfo, numTables);
@@ -319,6 +334,43 @@ flagInhTables(TableInfo *tblinfo, int numTables,
}
}
+/* flagPartitions -
+ * Fill in parent link fields of every target table that is partition,
+ * and mark parents of partitions as interesting
+ *
+ * modifies tblinfo
+ */
+static void
+flagPartitions(TableInfo *tblinfo, int numTables,
+ PartInfo *partinfo, int numPartitions)
+{
+ int i;
+
+ for (i = 0; i < numTables; i++)
+ {
+ /* Some kinds are never partitions */
+ if (tblinfo[i].relkind == RELKIND_SEQUENCE ||
+ tblinfo[i].relkind == RELKIND_VIEW ||
+ tblinfo[i].relkind == RELKIND_MATVIEW)
+ continue;
+
+ /* Don't bother computing anything for non-target tables, either */
+ if (!tblinfo[i].dobj.dump)
+ continue;
+
+ /* Find the parent TableInfo and save */
+ findPartitionParentByOid(&tblinfo[i], partinfo, numPartitions);
+
+ /* Mark the parent as interesting for getTableAttrs */
+ if (tblinfo[i].partitionOf)
+ {
+ tblinfo[i].partitionOf->interesting = true;
+ addObjectDependency(&tblinfo[i].dobj,
+ tblinfo[i].partitionOf->dobj.dumpId);
+ }
+ }
+}
+
/* flagInhAttrs -
* for each dumpable table in tblinfo, flag its inherited attributes
*
@@ -920,6 +972,40 @@ findParentsByOid(TableInfo *self,
}
/*
+ * findPartitionParentByOid
+ * find a partition's parent in tblinfo[]
+ */
+static void
+findPartitionParentByOid(TableInfo *self, PartInfo *partinfo,
+ int numPartitions)
+{
+ Oid oid = self->dobj.catId.oid;
+ int i;
+
+ for (i = 0; i < numPartitions; i++)
+ {
+ if (partinfo[i].partrelid == oid)
+ {
+ TableInfo *parent;
+
+ parent = findTableByOid(partinfo[i].partparent);
+ if (parent == NULL)
+ {
+ write_msg(NULL, "failed sanity check, parent OID %u of table \"%s\" (OID %u) not found\n",
+ partinfo[i].partparent,
+ self->dobj.name,
+ oid);
+ exit_nicely(1);
+ }
+ self->partitionOf = parent;
+
+ /* While we're at it, also save the partdef */
+ self->partitiondef = partinfo[i].partdef;
+ }
+ }
+}
+
+/*
* parseOidArray
* parse a string of numbers delimited by spaces into a character array
*
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 4c95aca..7337145 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -5986,6 +5986,63 @@ getInherits(Archive *fout, int *numInherits)
}
/*
+ * getPartitions
+ * read all the partition inheritance and partition bound information
+ * from the system catalogs return them in the PartInfo* structure
+ *
+ * numPartitions is set to the number of pairs read in
+ */
+PartInfo *
+getPartitions(Archive *fout, int *numPartitions)
+{
+ PGresult *res;
+ int ntups;
+ int i;
+ PQExpBuffer query = createPQExpBuffer();
+ PartInfo *partinfo;
+
+ int i_partrelid;
+ int i_partparent;
+ int i_partbound;
+
+ /* Make sure we are in proper schema */
+ selectSourceSchema(fout, "pg_catalog");
+
+ /* find all the inheritance information */
+
+ appendPQExpBufferStr(query,
+ "SELECT partrelid, inhparent AS partparent, "
+ " pg_get_expr(partbound, partrelid) AS partbound "
+ "FROM pg_partition, pg_inherits "
+ "WHERE partrelid = inhrelid");
+
+ res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
+
+ ntups = PQntuples(res);
+
+ *numPartitions = ntups;
+
+ partinfo = (PartInfo *) pg_malloc(ntups * sizeof(PartInfo));
+
+ i_partrelid = PQfnumber(res, "partrelid");
+ i_partparent = PQfnumber(res, "partparent");
+ i_partbound = PQfnumber(res, "partbound");
+
+ for (i = 0; i < ntups; i++)
+ {
+ partinfo[i].partrelid = atooid(PQgetvalue(res, i, i_partrelid));
+ partinfo[i].partparent = atooid(PQgetvalue(res, i, i_partparent));
+ partinfo[i].partdef = pg_strdup(PQgetvalue(res, i, i_partbound));
+ }
+
+ PQclear(res);
+
+ destroyPQExpBuffer(query);
+
+ return partinfo;
+}
+
+/*
* getIndexes
* get information about every index on a dumpable table
*
@@ -15133,6 +15190,17 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
if (tbinfo->reloftype && !dopt->binary_upgrade)
appendPQExpBuffer(q, " OF %s", tbinfo->reloftype);
+ if (tbinfo->partitionOf && !dopt->binary_upgrade)
+ {
+ TableInfo *parentRel = tbinfo->partitionOf;
+
+ appendPQExpBuffer(q, " PARTITION OF ");
+ if (parentRel->dobj.namespace != tbinfo->dobj.namespace)
+ appendPQExpBuffer(q, "%s.",
+ fmtId(parentRel->dobj.namespace->dobj.name));
+ appendPQExpBufferStr(q, fmtId(parentRel->dobj.name));
+ }
+
if (tbinfo->relkind != RELKIND_MATVIEW)
{
/* Dump the attributes */
@@ -15161,8 +15229,11 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
(!tbinfo->inhNotNull[j] ||
dopt->binary_upgrade));
- /* Skip column if fully defined by reloftype */
- if (tbinfo->reloftype &&
+ /*
+ * Skip column if fully defined by reloftype or the
+ * partition parent.
+ */
+ if ((tbinfo->reloftype || tbinfo->partitionOf) &&
!has_default && !has_notnull && !dopt->binary_upgrade)
continue;
@@ -15191,7 +15262,8 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
}
/* Attribute type */
- if (tbinfo->reloftype && !dopt->binary_upgrade)
+ if ((tbinfo->reloftype || tbinfo->partitionOf) &&
+ !dopt->binary_upgrade)
{
appendPQExpBufferStr(q, " WITH OPTIONS");
}
@@ -15256,15 +15328,22 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
if (actual_atts)
appendPQExpBufferStr(q, "\n)");
- else if (!(tbinfo->reloftype && !dopt->binary_upgrade))
+ else if (!((tbinfo->reloftype || tbinfo->partitionOf) &&
+ !dopt->binary_upgrade))
{
/*
* We must have a parenthesized attribute list, even though
- * empty, when not using the OF TYPE syntax.
+ * empty, when not using the OF TYPE or PARTITION OF syntax.
*/
appendPQExpBufferStr(q, " (\n)");
}
+ if (tbinfo->partitiondef && !dopt->binary_upgrade)
+ {
+ appendPQExpBufferStr(q, "\n");
+ appendPQExpBufferStr(q, tbinfo->partitiondef);
+ }
+
if (numParents > 0 && !dopt->binary_upgrade)
{
appendPQExpBufferStr(q, "\nINHERITS (");
@@ -15433,6 +15512,15 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
tbinfo->reloftype);
}
+ if (tbinfo->partitionOf)
+ {
+ appendPQExpBufferStr(q, "\n-- For binary upgrade, set up partitions this way.\n");
+ appendPQExpBuffer(q, "ALTER TABLE ONLY %s ATTACH PARTITION %s %s;\n",
+ fmtId(tbinfo->partitionOf->dobj.name),
+ tbinfo->dobj.name,
+ tbinfo->partitiondef);
+ }
+
appendPQExpBufferStr(q, "\n-- For binary upgrade, set heap's relfrozenxid and relminmxid\n");
appendPQExpBuffer(q, "UPDATE pg_catalog.pg_class\n"
"SET relfrozenxid = '%u', relminmxid = '%u'\n"
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 3094425..e3309b6 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -293,6 +293,8 @@ typedef struct _tableInfo
struct _tableDataInfo *dataObj; /* TableDataInfo, if dumping its data */
int numTriggers; /* number of triggers for table */
struct _triggerInfo *triggers; /* array of TriggerInfo structs */
+ struct _tableInfo *partitionOf; /* TableInfo for the partition parent */
+ char *partitiondef; /* partition key definition */
} TableInfo;
typedef struct _attrDefInfo
@@ -433,6 +435,15 @@ typedef struct _inhInfo
Oid inhparent; /* OID of its parent */
} InhInfo;
+/* PartInfo isn't a DumpableObject, just temporary state */
+typedef struct _partInfo
+{
+ Oid partrelid; /* OID of a partition */
+ Oid partparent; /* OID of its parent */
+ char *partdef; /* partition bound definition */
+} PartInfo;
+
+
typedef struct _prsInfo
{
DumpableObject dobj;
@@ -599,6 +610,7 @@ extern ConvInfo *getConversions(Archive *fout, int *numConversions);
extern TableInfo *getTables(Archive *fout, int *numTables);
extern void getOwnedSeqs(Archive *fout, TableInfo tblinfo[], int numTables);
extern InhInfo *getInherits(Archive *fout, int *numInherits);
+extern PartInfo *getPartitions(Archive *fout, int *numPartitions);
extern void getIndexes(Archive *fout, TableInfo tblinfo[], int numTables);
extern void getConstraints(Archive *fout, TableInfo tblinfo[], int numTables);
extern RuleInfo *getRules(Archive *fout, int *numRules);
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 78a1fdb..6bbfc9a 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -1694,6 +1694,34 @@ describeOneTableDetails(const char *schemaname,
}
/* Make footers */
+ if (pset.sversion >= 90600)
+ {
+ /* Get the partition information */
+ PGresult *result;
+ char *parent_name;
+ char *partdef;
+
+ printfPQExpBuffer(&buf,
+ "SELECT inhparent::pg_catalog.regclass, pg_get_expr(partbound, partrelid)"
+ " FROM pg_catalog.pg_partition p"
+ " JOIN pg_catalog.pg_inherits i"
+ " ON p.partrelid = i.inhrelid"
+ " WHERE p.partrelid = '%s';", oid);
+ result = PSQLexec(buf.data);
+ if (!result)
+ goto error_return;
+
+ if (PQntuples(result) > 0)
+ {
+ parent_name = PQgetvalue(result, 0, 0);
+ partdef = PQgetvalue(result, 0, 1);
+ printfPQExpBuffer(&tmpbuf, _("Partition Of: %s %s"), parent_name,
+ partdef);
+ printTableAddFooter(&cont, tmpbuf.data);
+ PQclear(result);
+ }
+ }
+
if (pset.sversion >= 90600 && tableinfo.relkind == 'P')
{
/* Get the partition key information */
@@ -2455,8 +2483,12 @@ describeOneTableDetails(const char *schemaname,
PQclear(result);
}
- /* print inherited tables */
- printfPQExpBuffer(&buf, "SELECT c.oid::pg_catalog.regclass FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i WHERE c.oid=i.inhparent AND i.inhrelid = '%s' ORDER BY inhseqno;", oid);
+ /* print inherited tables (exclude, if parent is a partitioned table) */
+ printfPQExpBuffer(&buf,
+ "SELECT c.oid::pg_catalog.regclass"
+ " FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i"
+ " WHERE c.oid=i.inhparent AND i.inhrelid = '%s'"
+ " AND c.relkind != 'P' ORDER BY inhseqno;", oid);
result = PSQLexec(buf.data);
if (!result)
@@ -2485,9 +2517,15 @@ describeOneTableDetails(const char *schemaname,
PQclear(result);
}
- /* print child tables */
+ /* print child tables (exclude, if parent is a partitioned table) */
if (pset.sversion >= 80300)
- printfPQExpBuffer(&buf, "SELECT c.oid::pg_catalog.regclass FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i WHERE c.oid=i.inhrelid AND i.inhparent = '%s' ORDER BY c.oid::pg_catalog.regclass::pg_catalog.text;", oid);
+ printfPQExpBuffer(&buf,
+ "SELECT c.oid::pg_catalog.regclass"
+ " FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i"
+ " WHERE c.oid=i.inhrelid AND"
+ " i.inhparent = '%s' AND"
+ " EXISTS (SELECT 1 FROM pg_class c WHERE c.oid = '%s' AND c.relkind != 'P')"
+ " ORDER BY c.oid::pg_catalog.regclass::pg_catalog.text;", oid, oid);
else
printfPQExpBuffer(&buf, "SELECT c.oid::pg_catalog.regclass FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i WHERE c.oid=i.inhrelid AND i.inhparent = '%s' ORDER BY c.relname;", oid);
diff --git a/src/include/catalog/indexing.h b/src/include/catalog/indexing.h
index cadb741..73786c0 100644
--- a/src/include/catalog/indexing.h
+++ b/src/include/catalog/indexing.h
@@ -322,6 +322,9 @@ DECLARE_UNIQUE_INDEX(pg_replication_origin_roname_index, 6002, on pg_replication
DECLARE_UNIQUE_INDEX(pg_partitioned_partedrelid_index, 3351, on pg_partitioned using btree(partedrelid oid_ops));
#define PartitionedRelidIndexId 3351
+DECLARE_UNIQUE_INDEX(pg_partition_partrelid_index, 3354, on pg_partition using btree(partrelid oid_ops));
+#define PartitionRelidIndexId 3354
+
/* last step of initialization script: build the indexes declared above */
BUILD_INDICES
diff --git a/src/include/catalog/partition.h b/src/include/catalog/partition.h
index 31dbe17..6204ad2 100644
--- a/src/include/catalog/partition.h
+++ b/src/include/catalog/partition.h
@@ -42,6 +42,53 @@ typedef struct PartitionKeyData
typedef struct PartitionKeyData *PartitionKey;
+/* Internal representation of a list partition bound */
+typedef struct PartitionListBound
+{
+ bool contains_null;
+ int nvalues; /* number of values in the following array */
+ Datum *values; /* values contained in the list */
+} PartitionListBound;
+
+/* Internal representation of a range partition bound */
+typedef struct
+{
+ Datum *val; /* composite bound value, if any */
+ bool infinite; /* bound is +/- infinity */
+ bool inclusive; /* bound is inclusive (vs exclusive) */
+ bool lower; /* this is the lower (vs upper) bound */
+} PartitionRangeBound;
+
+typedef struct PartitionDescData *PartitionDesc;
+
+/*
+ * Partition tree node
+ *
+ * pkinfo PartitionKey executor state
+ * pdesc info about immediate partitions (see
+ PartitionDescData)
+ * index index within 0..(parent_pdesc->nparts - 1)
+ * num_leaf_partitions number of leaf partitions in the partition
+ * tree rooted at this node
+ * offset index (0-based) of the first leaf partition
+ * in the partition tree rooted at this node
+ * downlink link to the leftmost child node
+ * next link to the right sibling node on a given level
+ */
+typedef struct PartitionDescNodeData
+{
+ PartitionDesc pdesc;
+ Oid relid;
+ int index;
+ int offset;
+ int num_leaf_partitions;
+
+ struct PartitionDescNodeData *downlink;
+ struct PartitionDescNodeData *next;
+} PartitionDescNodeData;
+
+typedef struct PartitionDescNodeData *PartitionDescNode;
+
extern void StorePartitionKey(Relation rel,
char strategy,
int16 partnatts,
@@ -53,4 +100,14 @@ extern void RelationBuildPartitionKey(Relation relation);
extern void FreePartitionKey(PartitionKey key);
extern bool relid_is_partitioned(Oid relid);
+extern void StorePartitionBound(Oid relid, Oid parentId, Node *bound);
+extern void RemovePartitionEntryByRelId(Oid relid);
+extern bool relid_is_partition(Oid relid);
+extern Oid get_partition_parent(Oid relid);
+extern void RelationDropPartitions(Oid relid);
+extern Node *RelationGetPartitionCheckExpr(Relation rel);
+extern Expr *get_check_expr_from_partbound(Relation rel, Relation parent,
+ Node *bound);
+extern List *get_leaf_partition_oids(PartitionDescNode pdnode);
+extern PartitionDescNode RelationGetPartitionDescNode(Relation rel);
#endif /* PARTITION_H */
diff --git a/src/include/catalog/pg_partition.h b/src/include/catalog/pg_partition.h
new file mode 100644
index 0000000..d31a6b7
--- /dev/null
+++ b/src/include/catalog/pg_partition.h
@@ -0,0 +1,54 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_partition.h
+ * definition of the system "partition" relation (pg_partition)
+ * along with the relation's initial contents.
+ *
+ *
+ * Portions Copyright (c) 1996-2015, PostgreSQL Global Development Group
+ *
+ * $PostgreSQL: pgsql/src/include/catalog/pg_partition.h $
+ *
+ * NOTES
+ * the genbki.sh script reads this file and generates .bki
+ * information from the DATA() statements.
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_PARTITION_H
+#define PG_PARTITION_H
+
+#include "catalog/genbki.h"
+
+/* ----------------
+ * pg_partitioned_rel definition. cpp turns this into
+ * typedef struct FormData_pg_partitioned_rel
+ * ----------------
+ */
+#define PartitionRelationId 3353
+
+CATALOG(pg_partition,3353) BKI_WITHOUT_OIDS
+{
+ Oid partrelid; /* partition relation OID */
+
+#ifdef CATALOG_VARLEN /* variable-length fields start here */
+ pg_node_tree partbound; /* node representation of partition bound */
+#endif
+} FormData_pg_partition;
+
+/* ----------------
+ * Form_pg_partition corresponds to a pointer to a tuple with
+ * the format of pg_partition relation.
+ * ----------------
+ */
+typedef FormData_pg_partition *Form_pg_partition;
+
+/* ----------------
+ * compiler constants for pg_partition
+ * ----------------
+ */
+#define Natts_pg_partition 2
+#define Anum_pg_partition_partrelid 1
+#define Anum_pg_partition_partbound 2
+
+#endif /* PG_PARTITION_H */
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index dbec07e..375e56e 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -319,6 +319,8 @@ typedef struct JunkFilter
* projectReturning for computing a RETURNING list
* onConflictSetProj for computing ON CONFLICT DO UPDATE SET
* onConflictSetWhere list of ON CONFLICT DO UPDATE exprs (qual)
+ * PartitionCheck partition check expression
+ * PartitionCheckExpr partition check expression state
* ----------------
*/
typedef struct ResultRelInfo
@@ -343,6 +345,8 @@ typedef struct ResultRelInfo
ProjectionInfo *ri_projectReturning;
ProjectionInfo *ri_onConflictSetProj;
List *ri_onConflictSetWhere;
+ Expr *ri_PartitionCheck;
+ List *ri_PartitionCheckExpr;
} ResultRelInfo;
/* ----------------
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index cf91ef8..c3177d7 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -91,6 +91,10 @@ typedef struct RelationData
struct RowSecurityDesc *rd_rsdesc; /* row security policies, or NULL */
struct PartitionKeyData *rd_partkey; /* partition key, or NULL */
+ MemoryContext rd_pdcxt; /* private context for partdesc */
+ struct PartitionDescData *rd_partdesc; /* partitions, or NULL */
+ Node *rd_partcheck; /* partition CHECK constraint expression
+ * tree */
/* data managed by RelationGetIndexList: */
List *rd_indexlist; /* list of OIDs of indexes on relation */
diff --git a/src/include/utils/syscache.h b/src/include/utils/syscache.h
index e727842..4e493e0 100644
--- a/src/include/utils/syscache.h
+++ b/src/include/utils/syscache.h
@@ -72,6 +72,7 @@ enum SysCacheIdentifier
OPEROID,
OPFAMILYAMNAMENSP,
OPFAMILYOID,
+ PARTRELID,
PARTEDRELID,
PROCNAMEARGSNSP,
PROCOID,
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index b1e473d..57bde9d 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -118,6 +118,7 @@ pg_namespace|t
pg_opclass|t
pg_operator|t
pg_opfamily|t
+pg_partition|t
pg_partitioned|t
pg_pltemplate|t
pg_policy|t
--
1.7.1
[text/x-diff] 0005-Introduce-tuple-routing-for-partitioned-tables-v1.patch (27.8K, ../../57106F9E.3000309@lab.ntt.co.jp/6-0005-Introduce-tuple-routing-for-partitioned-tables-v1.patch)
download | inline diff:
From 5eeb8ca653762306d6c0534a466def453e608111 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Fri, 18 Mar 2016 19:48:49 +0900
Subject: [PATCH 5/5] Introduce tuple routing for partitioned tables.
A tuple inserted into a partitioned table using insert or copy is now
routed to the appropriate leaf partition, recursively.
State required for the same is built in advance by BeginCopy() and
ExecInitModifyTable(). It consists of N ResultRelInfos and pointers
to TupleConversionMap structs, where N is the number of leaf partitions
in the multi-level partition tree. One needs only to get a pointer to the
root PartitionDescNode node for the tree. A tuple conversion map is only
allocated if the tuple descriptor of a leaf partition differs from that
of the root table. Partition check expression is not initialized for
leaf partition result relations, as they are redundant in this case.
Whether or not to build one is now determined by new parameter
load_partition_check of InitResultRelInfo().
CopyFrom() or ExecInsert() passes the root node (set up initially, as
just mentioned) along with the tuple slot to get_partition_for_tuple()
which traverses the tree to determine the appropriate leaf partition for
the tuple. It returns the index (0..N) of the leaf partition. Caller
then switches the result relation (ResultRelInfo) appropriately and
performs the tuple conversion, if required, before following up with
the row triggers, constraints, actual heap and index insertion.
* Following requires further consideration:
Both CopyFrom() and ExecInsert() switch to the partition before invoking
BR triggers and back after AR trigger are processed. That ignores parent's
triggers completely. Note that those triggers are not required to be present
in each partition. OTOH, constraints are fine (both check and not null),
since parent's constraints are present in partitions.
---
src/backend/catalog/partition.c | 318 ++++++++++++++++++++++++++++++++
src/backend/commands/copy.c | 97 +++++++++-
src/backend/commands/tablecmds.c | 1 +
src/backend/executor/execMain.c | 44 +++++-
src/backend/executor/nodeModifyTable.c | 79 ++++++++
src/backend/parser/analyze.c | 8 +
src/include/catalog/partition.h | 24 +++
src/include/executor/executor.h | 6 +
src/include/nodes/execnodes.h | 10 +
9 files changed, 582 insertions(+), 5 deletions(-)
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index 76b9e2b..975f148 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -126,6 +126,21 @@ static Node *generate_partition_check_expr(Relation rel);
static PartitionDescNode GetPartitionDescNodeRecurse(Relation rel, int offset);
static int get_leaf_partition_count(PartitionDescNode pdnode);
+static PartitionKeyExecInfo *BuildPartitionKeyExecInfo(Relation rel);
+static void FormPartitionKeyDatum(PartitionKeyExecInfo *pkinfo,
+ TupleTableSlot *slot,
+ EState *estate,
+ Datum *values,
+ bool *isnull);
+static int list_partition_for_tuple(PartitionKey key, PartitionDesc pdesc,
+ Datum value, bool isnull);
+static int range_partition_for_tuple(PartitionKey key, PartitionDesc pdesc,
+ Datum *values);
+static int range_partition_bsearch(PartitionKey key, PartitionDesc pdesc,
+ Datum *values);
+static bool rightof(PartitionKey key, Datum *values, PartitionRangeBound *bound);
+static bool leftof(PartitionKey key, Datum *values, PartitionRangeBound *bound);
+
/*
* StorePartitionKey
* Store the partition keys of rel into pg_partitioned catalog
@@ -1710,6 +1725,7 @@ GetPartitionDescNodeRecurse(Relation rel, int offset)
/* First build our own node */
root = (PartitionDescNode) palloc0(sizeof(PartitionDescNodeData));
+ root->pkinfo = BuildPartitionKeyExecInfo(rel);
root->pdesc = pdesc;
root->relid = RelationGetRelid(rel);
root->offset = offset;
@@ -1827,3 +1843,305 @@ get_leaf_partition_oids(PartitionDescNode pdnode)
return result;
}
+
+/* ----------------
+ * BuildPartitionKeyExecInfo
+ * Construct a list of PartitionKeyExecInfo records for an open
+ * relation
+ *
+ * PartitionKeyExecInfo stores the information about the partition key
+ * that's needed when inserting tuples into a partitioned table; especially,
+ * partition key expression state if there are any expression columns in
+ * the partition key. Normally we build a PartitionKeyExecInfo for a
+ * partitioned table just once per command, and then use it for (potentially)
+ * many tuples.
+ * ----------------
+ */
+static PartitionKeyExecInfo *
+BuildPartitionKeyExecInfo(Relation rel)
+{
+ PartitionKeyExecInfo *pkinfo;
+
+ pkinfo = (PartitionKeyExecInfo *) palloc0(sizeof(PartitionKeyExecInfo));
+ pkinfo->pi_Key = CopyPartitionKey(rel->rd_partkey);
+ pkinfo->pi_ExpressionState = NIL;
+
+ return pkinfo;
+}
+
+/*
+ * get_partition_for_tuple
+ * Recursively finds the "leaf" partition for tuple (slot)
+ *
+ * Returns -1 if no partition is found and sets *failed_at to the OID of
+ * the partitioned table whose partition was not found.
+ */
+int
+get_partition_for_tuple(PartitionDescNode pdnode,
+ TupleTableSlot *slot,
+ EState *estate,
+ Oid *failed_at)
+{
+ PartitionKeyExecInfo *pkinfo = pdnode->pkinfo;
+ PartitionDescNode node;
+ Datum values[PARTITION_MAX_KEYS];
+ bool isnull[PARTITION_MAX_KEYS];
+ int i;
+ int index;
+
+ if (pdnode->pdesc->nparts == 0)
+ return -1;
+
+ /* Extract partition key from tuple */
+ Assert(GetPerTupleExprContext(estate)->ecxt_scantuple == slot);
+ FormPartitionKeyDatum(pkinfo, slot, estate, values, isnull);
+
+ /* Disallow nulls, if range partition key */
+ for (i = 0; i < pkinfo->pi_Key->partnatts; i++)
+ if (isnull[i] && pkinfo->pi_Key->strategy == PARTITION_STRAT_RANGE)
+ ereport(ERROR,
+ (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+ errmsg("range partition key contains null")));
+
+ switch (pkinfo->pi_Key->strategy)
+ {
+ case PARTITION_STRAT_LIST:
+ index = list_partition_for_tuple(pkinfo->pi_Key, pdnode->pdesc,
+ values[0], isnull[0]);
+ break;
+
+ case PARTITION_STRAT_RANGE:
+ index = range_partition_for_tuple(pkinfo->pi_Key, pdnode->pdesc,
+ values);
+ break;
+ }
+
+ /* Don't recurse, if no partition exists for the key at this level... */
+ if (index < 0)
+ {
+ *failed_at = pdnode->relid;
+ return index;
+ }
+
+ /* ... or, if the index'th partition is a leaf partition. */
+ if (!relid_is_partitioned(pdnode->pdesc->oids[index]))
+ {
+ PartitionDescNode prev;
+
+ /* Patiently determine own leaf partition index */
+ prev = node = pdnode->downlink;
+ if (node && node->index < index)
+ {
+ while (node)
+ {
+ if (node->index > index)
+ {
+ node = prev;
+ break;
+ }
+
+ prev = node;
+ node = node->next;
+ }
+
+ if (!node)
+ node = prev;
+
+ /*
+ * Account for siblings on left that are partition trees and those
+ * that are leaf partitions themsleves.
+ */
+ return node->offset + node->num_leaf_partitions +
+ (index - node->index - 1);
+ }
+ else
+ /* Only leaf partitions in pdnode->offset..index */
+ return pdnode->offset + index;
+
+ }
+
+ /* Recurse, by locating the index'th partition's node */
+ node = pdnode->downlink;
+ while (node->next != NULL && node->index != index)
+ node = node->next;
+ Assert (node != NULL);
+
+ return get_partition_for_tuple(node, slot, estate, failed_at);
+}
+
+/*
+ * FormPartitionKeyDatum
+ * Construct values[] and isnull[] arrays for partition key columns
+ */
+static void
+FormPartitionKeyDatum(PartitionKeyExecInfo *pkinfo,
+ TupleTableSlot *slot,
+ EState *estate,
+ Datum *values,
+ bool *isnull)
+{
+ ListCell *partexpr_item;
+ int i;
+
+ if (pkinfo->pi_Key->partexprs != NIL && pkinfo->pi_ExpressionState == NIL)
+ {
+ /* First time through, set up expression evaluation state */
+ pkinfo->pi_ExpressionState = (List *)
+ ExecPrepareExpr((Expr *) pkinfo->pi_Key->partexprs,
+ estate);
+ /* Check caller has set up context correctly */
+ Assert(GetPerTupleExprContext(estate)->ecxt_scantuple == slot);
+ }
+
+ partexpr_item = list_head(pkinfo->pi_ExpressionState);
+ for (i = 0; i < pkinfo->pi_Key->partnatts; i++)
+ {
+ AttrNumber keycol = pkinfo->pi_Key->partattrs[i];
+ Datum pkDatum;
+ bool isNull;
+
+ if (keycol != 0)
+ {
+ /* Plain column; get the value directly from the heap tuple */
+ pkDatum = slot_getattr(slot, keycol, &isNull);
+ }
+ else
+ {
+ /* Expression; need to evaluate it */
+ if (partexpr_item == NULL)
+ elog(ERROR, "wrong number of partition key expressions");
+ pkDatum = ExecEvalExprSwitchContext((ExprState *) lfirst(partexpr_item),
+ GetPerTupleExprContext(estate),
+ &isNull,
+ NULL);
+ partexpr_item = lnext(partexpr_item);
+ }
+ values[i] = pkDatum;
+ isnull[i] = isNull;
+ }
+
+ if (partexpr_item != NULL)
+ elog(ERROR, "wrong number of partition key expressions");
+}
+
+/*
+ * list_partition_for_tuple
+ * Find the list partition for a tuple
+ *
+ * Returns -1 if none found.
+ */
+static int
+list_partition_for_tuple(PartitionKey key, PartitionDesc pdesc,
+ Datum value, bool isnull)
+{
+ int i;
+
+ Assert(pdesc->nparts > 0);
+
+ for (i = 0; i < pdesc->nparts; i++)
+ {
+ int j;
+
+ if (isnull)
+ {
+ if (pdesc->lists[i]->contains_null)
+ return i;
+
+ continue;
+ }
+
+ for (j = 0; j < pdesc->lists[i]->nvalues; j++)
+ {
+ int32 cmpval;
+
+ cmpval = DatumGetInt32(FunctionCall2Coll(&key->partsupfunc[0],
+ key->tcinfo->typcoll[0],
+ pdesc->lists[i]->values[j],
+ value));
+ if (!cmpval)
+ return i;
+ }
+ }
+
+ return -1;
+}
+
+/*
+ * range_partition_for_tuple
+ * Search the range partition for a range key ('values')
+ *
+ * Returns -1 if none found.
+ */
+static int
+range_partition_for_tuple(PartitionKey key, PartitionDesc pdesc, Datum *values)
+{
+ Assert(pdesc->nparts > 0);
+
+ return range_partition_bsearch(key, pdesc, values);
+}
+
+/*
+ * range_partition_bsearch
+ * Workhorse of range_partition_for_tuple
+ */
+static int
+range_partition_bsearch(PartitionKey key, PartitionDesc pdesc,
+ Datum *values)
+{
+ int low, high;
+
+ /* Good ol' bsearch */
+ low = 0;
+ high = pdesc->nparts - 1;
+ while (low <= high)
+ {
+ int idx = (low + high) / 2;
+
+ if (pdesc->rangeuppers[idx]->infinite)
+ {
+ if (rightof(key, values, pdesc->rangelowers[idx]))
+ return idx;
+
+ break;
+ }
+ else if (leftof(key, values, pdesc->rangeuppers[idx]))
+ {
+ if (pdesc->rangelowers[idx]->infinite)
+ return idx;
+
+ if (rightof(key, values, pdesc->rangelowers[idx]))
+ return idx;
+
+ high = idx - 1;
+ continue;
+ }
+
+ low = idx + 1;
+ }
+
+ return -1;
+}
+
+/* Does range key lie to the right of partition bound */
+static bool
+rightof(PartitionKey key, Datum *values, PartitionRangeBound *bound)
+{
+ int32 cmpval = compare_range_keys(key, values, bound->val);
+
+ if (!cmpval)
+ return bound->lower ? bound->inclusive : !bound->inclusive;
+
+ return cmpval > 0;
+}
+
+/* Does range key lie to the left of partition bound */
+static bool
+leftof(PartitionKey key, Datum *values, PartitionRangeBound *bound)
+{
+ int32 cmpval = compare_range_keys(key, values, bound->val);
+
+ if (!cmpval)
+ return !bound->lower ? bound->inclusive : !bound->inclusive;
+
+ return cmpval < 0;
+}
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 14e81de..9c9b401 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -161,6 +161,10 @@ typedef struct CopyStateData
ExprState **defexprs; /* array of default att expressions */
bool volatile_defexprs; /* is any of defexprs volatile? */
List *range_table;
+ PartitionDescNode pdnode; /* partition descriptor node tree */
+ ResultRelInfo *partitions;
+ TupleConversionMap **partition_tupconv_maps;
+ int num_partitions;
/*
* These variables are used to reduce overhead in textual COPY FROM.
@@ -1364,6 +1368,50 @@ BeginCopy(bool is_from,
(errcode(ERRCODE_UNDEFINED_COLUMN),
errmsg("table \"%s\" does not have OIDs",
RelationGetRelationName(cstate->rel))));
+
+ /* Initialize state for CopyFrom tuple routing */
+ if (is_from && rel->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+ {
+ List *leaf_part_oids;
+ ListCell *cell;
+ int i;
+ int num_leaf_parts;
+ ResultRelInfo *leaf_rel_rri;
+
+ cstate->pdnode = RelationGetPartitionDescNode(rel);
+ leaf_part_oids = get_leaf_partition_oids(cstate->pdnode);
+ num_leaf_parts = list_length(leaf_part_oids);
+
+ cstate->num_partitions = num_leaf_parts;
+ cstate->partitions = (ResultRelInfo *)
+ palloc0(num_leaf_parts * sizeof(ResultRelInfo));
+ cstate->partition_tupconv_maps = (TupleConversionMap **)
+ palloc0(num_leaf_parts * sizeof(TupleConversionMap *));
+
+ leaf_rel_rri = cstate->partitions;
+ i = 0;
+ foreach(cell, leaf_part_oids)
+ {
+ Relation leaf_rel;
+
+ leaf_rel = heap_open(lfirst_oid(cell), RowExclusiveLock);
+ InitResultRelInfo(leaf_rel_rri,
+ leaf_rel,
+ 1, /* dummy */
+ false, /* no need for partition check */
+ 0);
+
+ if (!equalTupleDescs(tupDesc, RelationGetDescr(leaf_rel)))
+ cstate->partition_tupconv_maps[i] =
+ convert_tuples_by_name(tupDesc,
+ RelationGetDescr(leaf_rel),
+ gettext_noop("could not convert row type"));
+
+ heap_close(leaf_rel, NoLock); /* keep the lock */
+ leaf_rel_rri++;
+ i++;
+ }
+ }
}
else
{
@@ -2216,6 +2264,7 @@ CopyFrom(CopyState cstate)
Datum *values;
bool *nulls;
ResultRelInfo *resultRelInfo;
+ ResultRelInfo *saved_resultRelInfo = NULL;
EState *estate = CreateExecutorState(); /* for ExecConstraints() */
ExprContext *econtext;
TupleTableSlot *myslot;
@@ -2236,7 +2285,8 @@ CopyFrom(CopyState cstate)
Assert(cstate->rel);
- if (cstate->rel->rd_rel->relkind != RELKIND_RELATION)
+ if (cstate->rel->rd_rel->relkind != RELKIND_RELATION &&
+ cstate->rel->rd_rel->relkind != RELKIND_PARTITIONED_REL)
{
if (cstate->rel->rd_rel->relkind == RELKIND_VIEW)
ereport(ERROR,
@@ -2344,6 +2394,7 @@ CopyFrom(CopyState cstate)
InitResultRelInfo(resultRelInfo,
cstate->rel,
1, /* dummy rangetable index */
+ true, /* do load partition check expression */
0);
ExecOpenIndices(resultRelInfo, false);
@@ -2371,6 +2422,7 @@ CopyFrom(CopyState cstate)
if ((resultRelInfo->ri_TrigDesc != NULL &&
(resultRelInfo->ri_TrigDesc->trig_insert_before_row ||
resultRelInfo->ri_TrigDesc->trig_insert_instead_row)) ||
+ cstate->pdnode != NULL ||
cstate->volatile_defexprs)
{
useHeapMultiInsert = false;
@@ -2395,7 +2447,11 @@ CopyFrom(CopyState cstate)
values = (Datum *) palloc(tupDesc->natts * sizeof(Datum));
nulls = (bool *) palloc(tupDesc->natts * sizeof(bool));
- bistate = GetBulkInsertState();
+ if (useHeapMultiInsert)
+ bistate = GetBulkInsertState();
+ else
+ bistate = NULL;
+
econtext = GetPerTupleExprContext(estate);
/* Set up callback to identify error line number */
@@ -2447,6 +2503,31 @@ CopyFrom(CopyState cstate)
slot = myslot;
ExecStoreTuple(tuple, slot, InvalidBuffer, false);
+ /* Determine the partition */
+ saved_resultRelInfo = resultRelInfo;
+ if (cstate->pdnode)
+ {
+ int i_leaf_partition;
+ TupleConversionMap *map;
+
+ econtext->ecxt_scantuple = slot;
+ i_leaf_partition = ExecFindPartition(resultRelInfo,
+ cstate->pdnode,
+ slot,
+ estate);
+ Assert(i_leaf_partition >= 0 &&
+ i_leaf_partition < cstate->num_partitions);
+
+ resultRelInfo = cstate->partitions + i_leaf_partition;
+ estate->es_result_relation_info = resultRelInfo;
+
+ map = cstate->partition_tupconv_maps[i_leaf_partition];
+ if (map)
+ tuple = do_convert_tuple(tuple, map);
+
+ tuple->t_tableOid = RelationGetRelid(resultRelInfo->ri_RelationDesc);
+ }
+
skip_tuple = false;
/* BEFORE ROW INSERT Triggers */
@@ -2497,7 +2578,8 @@ CopyFrom(CopyState cstate)
List *recheckIndexes = NIL;
/* OK, store the tuple and create index entries for it */
- heap_insert(cstate->rel, tuple, mycid, hi_options, bistate);
+ heap_insert(resultRelInfo->ri_RelationDesc,
+ tuple, mycid, hi_options, bistate);
if (resultRelInfo->ri_NumIndices > 0)
recheckIndexes = ExecInsertIndexTuples(slot, &(tuple->t_self),
@@ -2517,6 +2599,12 @@ CopyFrom(CopyState cstate)
* tuples inserted by an INSERT command.
*/
processed++;
+
+ if (saved_resultRelInfo)
+ {
+ resultRelInfo = saved_resultRelInfo;
+ estate->es_result_relation_info = resultRelInfo;
+ }
}
}
@@ -2530,7 +2618,8 @@ CopyFrom(CopyState cstate)
/* Done, clean up */
error_context_stack = errcallback.previous;
- FreeBulkInsertState(bistate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
MemoryContextSwitchTo(oldcontext);
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 41e88bc..8170a11 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -1242,6 +1242,7 @@ ExecuteTruncate(TruncateStmt *stmt)
InitResultRelInfo(resultRelInfo,
rel,
0, /* dummy rangetable index */
+ false,
0);
resultRelInfo++;
}
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 23ed0b6..b151fb5 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -826,6 +826,7 @@ InitPlan(QueryDesc *queryDesc, int eflags)
InitResultRelInfo(resultRelInfo,
resultRelation,
resultRelationIndex,
+ true,
estate->es_instrument);
resultRelInfo++;
}
@@ -1214,6 +1215,7 @@ void
InitResultRelInfo(ResultRelInfo *resultRelInfo,
Relation resultRelationDesc,
Index resultRelationIndex,
+ bool load_partition_check,
int instrument_options)
{
MemSet(resultRelInfo, 0, sizeof(ResultRelInfo));
@@ -1251,7 +1253,8 @@ InitResultRelInfo(ResultRelInfo *resultRelInfo,
resultRelInfo->ri_ConstraintExprs = NULL;
resultRelInfo->ri_junkFilter = NULL;
resultRelInfo->ri_projectReturning = NULL;
- resultRelInfo->ri_PartitionCheck = (Expr *)
+ if (load_partition_check)
+ resultRelInfo->ri_PartitionCheck = (Expr *)
RelationGetPartitionCheckExpr(resultRelationDesc);
}
@@ -1315,6 +1318,7 @@ ExecGetTriggerResultRel(EState *estate, Oid relid)
InitResultRelInfo(rInfo,
rel,
0, /* dummy rangetable index */
+ true,
estate->es_instrument);
estate->es_trig_target_relations =
lappend(estate->es_trig_target_relations, rInfo);
@@ -2987,3 +2991,41 @@ EvalPlanQualEnd(EPQState *epqstate)
epqstate->planstate = NULL;
epqstate->origslot = NULL;
}
+
+int
+ExecFindPartition(ResultRelInfo *resultRelInfo, PartitionDescNode pdnode,
+ TupleTableSlot *slot, EState *estate)
+{
+ int i_leaf_partition;
+ Oid failed_at;
+
+ i_leaf_partition = get_partition_for_tuple(pdnode, slot, estate,
+ &failed_at);
+
+ if (i_leaf_partition < 0)
+ {
+ Relation rel = resultRelInfo->ri_RelationDesc;
+ char *val_desc;
+ Bitmapset *insertedCols,
+ *updatedCols,
+ *modifiedCols;
+ TupleDesc tupDesc = RelationGetDescr(rel);
+
+ insertedCols = GetInsertedColumns(resultRelInfo, estate);
+ updatedCols = GetUpdatedColumns(resultRelInfo, estate);
+ modifiedCols = bms_union(insertedCols, updatedCols);
+ val_desc = ExecBuildSlotValueDescription(RelationGetRelid(rel),
+ slot,
+ tupDesc,
+ modifiedCols,
+ 64);
+ Assert(OidIsValid(failed_at));
+ ereport(ERROR,
+ (errcode(ERRCODE_CHECK_VIOLATION),
+ errmsg("no partition of relation \"%s\" found for row",
+ get_rel_name(failed_at)),
+ val_desc ? errdetail("Failing row contains %s.", val_desc) : 0));
+ }
+
+ return i_leaf_partition;
+}
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 6ecb6be..18ce333 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -243,6 +243,7 @@ ExecInsert(ModifyTableState *mtstate,
{
HeapTuple tuple;
ResultRelInfo *resultRelInfo;
+ ResultRelInfo *saved_resultRelInfo = NULL;
Relation resultRelationDesc;
Oid newId;
List *recheckIndexes = NIL;
@@ -257,6 +258,31 @@ ExecInsert(ModifyTableState *mtstate,
* get information on the (current) result relation
*/
resultRelInfo = estate->es_result_relation_info;
+
+ saved_resultRelInfo = resultRelInfo;
+
+ if (mtstate->mt_partition_node)
+ {
+ int i_leaf_partition;
+ ExprContext *econtext = GetPerTupleExprContext(estate);
+ TupleConversionMap *map;
+
+ econtext->ecxt_scantuple = slot;
+ i_leaf_partition = ExecFindPartition(resultRelInfo,
+ mtstate->mt_partition_node,
+ slot,
+ estate);
+ Assert(i_leaf_partition >= 0 &&
+ i_leaf_partition < mtstate->mt_num_partitions);
+
+ resultRelInfo = mtstate->mt_partitions + i_leaf_partition;
+ estate->es_result_relation_info = resultRelInfo;
+
+ map = mtstate->mt_partition_tupconv_maps[i_leaf_partition];
+ if (map)
+ tuple = do_convert_tuple(tuple, map);
+ }
+
resultRelationDesc = resultRelInfo->ri_RelationDesc;
/*
@@ -496,6 +522,12 @@ ExecInsert(ModifyTableState *mtstate,
list_free(recheckIndexes);
+ if (saved_resultRelInfo)
+ {
+ resultRelInfo = saved_resultRelInfo;
+ estate->es_result_relation_info = resultRelInfo;
+ }
+
/*
* Check any WITH CHECK OPTION constraints from parent views. We are
* required to do this after testing all constraints and uniqueness
@@ -1550,6 +1582,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
Plan *subplan;
ListCell *l;
int i;
+ Relation rel;
/* check for unsupported flags */
Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));
@@ -1640,6 +1673,52 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
estate->es_result_relation_info = saved_resultRelInfo;
+ /* Build state for INSERT tuple routing */
+ rel = mtstate->resultRelInfo->ri_RelationDesc;
+ if (operation == CMD_INSERT && rel->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+ {
+ int i,
+ num_leaf_parts;
+ List *leaf_part_oids;
+ ListCell *cell;
+ ResultRelInfo *leaf_rel_rri;
+
+ mtstate->mt_partition_node = RelationGetPartitionDescNode(rel);
+ leaf_part_oids = get_leaf_partition_oids(mtstate->mt_partition_node);
+ num_leaf_parts = list_length(leaf_part_oids);
+
+ mtstate->mt_num_partitions = num_leaf_parts;
+ mtstate->mt_partitions = (ResultRelInfo *)
+ palloc0(num_leaf_parts * sizeof(ResultRelInfo));
+ mtstate->mt_partition_tupconv_maps = (TupleConversionMap **)
+ palloc0(num_leaf_parts * sizeof(TupleConversionMap *));
+
+ leaf_rel_rri = mtstate->mt_partitions;
+ i = 0;
+ foreach(cell, leaf_part_oids)
+ {
+ Relation leaf_rel;
+
+ leaf_rel = heap_open(lfirst_oid(cell), RowExclusiveLock);
+ InitResultRelInfo(leaf_rel_rri,
+ leaf_rel,
+ 1, /* dummy */
+ false, /* no need for partition checks */
+ eflags);
+
+ if (!equalTupleDescs(RelationGetDescr(rel),
+ RelationGetDescr(leaf_rel)))
+ mtstate->mt_partition_tupconv_maps[i] =
+ convert_tuples_by_name(RelationGetDescr(rel),
+ RelationGetDescr(leaf_rel),
+ gettext_noop("could not convert row type"));
+
+ heap_close(leaf_rel, NoLock); /* keep the lock */
+ leaf_rel_rri++;
+ i++;
+ }
+ }
+
/*
* Initialize any WITH CHECK OPTION constraints if needed.
*/
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index f55129a..802cd68 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -755,8 +755,16 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
/* Process ON CONFLICT, if any. */
if (stmt->onConflictClause)
+ {
+ /* Bail out if target relation is partitioned table */
+ if (relid_is_partitioned(pstate->p_target_rangetblentry->relid))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("ON CONFLICT clause is not supported with partitioned tables")));
+
qry->onConflict = transformOnConflictClause(pstate,
stmt->onConflictClause);
+ }
/*
* If we have a RETURNING clause, we need to add the target relation to
diff --git a/src/include/catalog/partition.h b/src/include/catalog/partition.h
index 6204ad2..f4840e4 100644
--- a/src/include/catalog/partition.h
+++ b/src/include/catalog/partition.h
@@ -14,6 +14,8 @@
#define PARTITION_H
#include "fmgr.h"
+#include "executor/tuptable.h"
+#include "nodes/execnodes.h"
#include "parser/parse_node.h"
#include "utils/relcache.h"
@@ -62,6 +64,22 @@ typedef struct
typedef struct PartitionDescData *PartitionDesc;
/*
+ * PartitionKeyExecInfo
+ *
+ * This struct holds the information needed to extract partition
+ * column values from a heap tuple.
+ *
+ * Key copy of the rd_partkey of rel
+ * ExpressionState exec state for expressions, or NIL if none
+ */
+typedef struct PartitionKeyExecInfo
+{
+ NodeTag type;
+ PartitionKey pi_Key;
+ List *pi_ExpressionState; /* list of ExprState */
+} PartitionKeyExecInfo;
+
+/*
* Partition tree node
*
* pkinfo PartitionKey executor state
@@ -77,6 +95,7 @@ typedef struct PartitionDescData *PartitionDesc;
*/
typedef struct PartitionDescNodeData
{
+ PartitionKeyExecInfo *pkinfo;
PartitionDesc pdesc;
Oid relid;
int index;
@@ -110,4 +129,9 @@ extern Expr *get_check_expr_from_partbound(Relation rel, Relation parent,
Node *bound);
extern List *get_leaf_partition_oids(PartitionDescNode pdnode);
extern PartitionDescNode RelationGetPartitionDescNode(Relation rel);
+
+extern int get_partition_for_tuple(PartitionDescNode pdnode,
+ TupleTableSlot *slot,
+ EState *estate,
+ Oid *failed_at);
#endif /* PARTITION_H */
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index 44fac27..d4e8b35 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -14,6 +14,7 @@
#ifndef EXECUTOR_H
#define EXECUTOR_H
+#include "catalog/partition.h"
#include "executor/execdesc.h"
#include "nodes/parsenodes.h"
@@ -188,6 +189,7 @@ extern void CheckValidResultRel(Relation resultRel, CmdType operation);
extern void InitResultRelInfo(ResultRelInfo *resultRelInfo,
Relation resultRelationDesc,
Index resultRelationIndex,
+ bool load_partition_check,
int instrument_options);
extern ResultRelInfo *ExecGetTriggerResultRel(EState *estate, Oid relid);
extern bool ExecContextForcesOids(PlanState *planstate, bool *hasoids);
@@ -211,6 +213,10 @@ extern void EvalPlanQualSetPlan(EPQState *epqstate,
extern void EvalPlanQualSetTuple(EPQState *epqstate, Index rti,
HeapTuple tuple);
extern HeapTuple EvalPlanQualGetTuple(EPQState *epqstate, Index rti);
+extern int ExecFindPartition(ResultRelInfo *resultRelInfo,
+ PartitionDescNode pdnode,
+ TupleTableSlot *slot,
+ EState *estate);
#define EvalPlanQualSetSlot(epqstate, slot) ((epqstate)->origslot = (slot))
extern void EvalPlanQualFetchRowMarks(EPQState *epqstate);
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 375e56e..b93ce61 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -16,6 +16,7 @@
#include "access/genam.h"
#include "access/heapam.h"
+#include "access/tupconvert.h"
#include "executor/instrument.h"
#include "lib/pairingheap.h"
#include "nodes/params.h"
@@ -1140,6 +1141,15 @@ typedef struct ModifyTableState
* tlist */
TupleTableSlot *mt_conflproj; /* CONFLICT ... SET ... projection
* target */
+ struct PartitionDescNodeData *mt_partition_node;
+ /* Partition descriptor node tree */
+ ResultRelInfo *mt_partitions; /* Per leaf partition target
+ * relations */
+ TupleConversionMap **mt_partition_tupconv_maps;
+ /* Per leaf partition
+ * tuple conversion map */
+ int mt_num_partitions; /* Number of leaf partition target
+ * relations in the above array */
} ModifyTableState;
/* ----------------
--
1.7.1
^ permalink raw reply [nested|flat] 161+ messages in thread
* Re: Declarative partitioning
@ 2016-04-15 09:46 Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
3 siblings, 2 replies; 161+ messages in thread
From: Ashutosh Bapat @ 2016-04-15 09:46 UTC (permalink / raw)
To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; +Cc: Robert Haas <robertmhaas@gmail.com>; Alexander Korotkov <a.korotkov@postgrespro.ru>; Amit Langote <amitlangote09@gmail.com>; Corey Huinker <corey.huinker@gmail.com>; pgsql-hackers
With the new set of patches, I am getting following warnings but no
compilation failures. Patches apply smoothly.
partition.c:1216:21: warning: variable ‘form’ set but not used
[-Wunused-but-set-variable]
partition.c:1637:20: warning: variable ‘form’ set but not used
[-Wunused-but-set-variable]
For creating partition the documentation seems to suggest the syntax
create table t1_p1 partition of t1 for values {start {0} end {100}
exclusive;
but following syntax works
create table t1_p1 partition of t1 for values start (0) end (100) exclusive;
> > 5. In expand_partitioned_rtentry(), instead of finding all the leaf level
> > relations, it might be better to build the RTEs, RelOptInfo and paths for
> > intermediate relations as well. This helps in partition pruning. In your
> > introductory write-up you have mentioned this. It might be better if v1
> > includes this change, so that the partition hierarchy is visible in
> EXPLAIN
> > output as well.
> >
> > 6. Explain output of scan on a partitioned table shows Append node with
> > individual table scans as sub-plans. May be we should annotate Append
> node
> > with
> > the name of the partitioned table to make EXPLAIN output more readable.
>
> I got rid of all the optimizer changes in the new version (except a line
> or two). I did that by switching to storing parent-child relationships in
> pg_inherits so that all the existing optimizer code for inheritance sets
> works unchanged. Also, the implementation detail that required to put
> only leaf partitions in the append list is also gone. Previously no
> storage was allocated for partitioned tables (either root or any of the
> internal partitions), so it was being done that way.
>
With this set of patches we are still flattening all the partitions.
postgres=# create table t1 (a int, b int) partition by range(a);
CREATE TABLE
postgres=# create table t1_p1 partition of t1 for values start (0) end
(100) exclusive partition by range(b);
CREATE TABLE
postgres=# create table t1_p1_p1 partition of t1_p1 for values start (0)
end (100) exclusive;
CREATE TABLE
postgres=# explain verbose select * from t1;
QUERY PLAN
-------------------------------------------------------------------------
Append (cost=0.00..32.60 rows=2262 width=8)
-> Seq Scan on public.t1 (cost=0.00..0.00 rows=1 width=8)
Output: t1.a, t1.b
-> Seq Scan on public.t1_p1 (cost=0.00..0.00 rows=1 width=8)
Output: t1_p1.a, t1_p1.b
-> Seq Scan on public.t1_p1_p1 (cost=0.00..32.60 rows=2260 width=8)
Output: t1_p1_p1.a, t1_p1_p1.b
(7 rows)
Retaining the partition hierarchy would help to push-down join across
partition hierarchy effectively.
>
> Regarding 6, it seems to me that because Append does not have a associated
> relid (like scan nodes have with scanrelid). Maybe we need to either fix
> Append or create some enhanced version of Append which would also support
> dynamic pruning.
>
Right, I think, Append might store the relid of the parent table as well as
partitioning information at that level along-with the subplans.
Some more comments:
1. Would it be better to declare PartitionDescData as
{
int nparts;
PartitionInfo *partinfo; /* array of partition information structures. */
}
2. The new syntax allows CREATE TABLE to be specified as partition of an
already partitioned table. Is it possible to do the same for CREATE FOREIGN
TABLE? Or that's material for v2? Similarly for ATTACH PARTITION.
3. PartitionKeyData contains KeyTypeCollInfo, whose contents can be
obtained by calling functions exprType, exprTypemod on partexprs. Why do we
need to store that information as a separate member?
--
Best Wishes,
Ashutosh Bapat
EnterpriseDB Corporation
The Postgres Database Company
^ permalink raw reply [nested|flat] 161+ messages in thread
* Re: Declarative partitioning
@ 2016-04-15 09:48 Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
3 siblings, 1 reply; 161+ messages in thread
From: Ashutosh Bapat @ 2016-04-15 09:48 UTC (permalink / raw)
To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; +Cc: Robert Haas <robertmhaas@gmail.com>; Alexander Korotkov <a.korotkov@postgrespro.ru>; Amit Langote <amitlangote09@gmail.com>; Corey Huinker <corey.huinker@gmail.com>; pgsql-hackers
With the new set of patches, I am getting following warnings but no
compilation failures. Patches apply smoothly.
partition.c:1216:21: warning: variable ‘form’ set but not used
[-Wunused-but-set-variable]
partition.c:1637:20: warning: variable ‘form’ set but not used
[-Wunused-but-set-variable]
For creating partition the documentation seems to suggest the syntax
create table t1_p1 partition of t1 for values {start {0} end {100}
exclusive;
but following syntax works
create table t1_p1 partition of t1 for values start (0) end (100) exclusive;
> > 5. In expand_partitioned_rtentry(), instead of finding all the leaf level
> > relations, it might be better to build the RTEs, RelOptInfo and paths for
> > intermediate relations as well. This helps in partition pruning. In your
> > introductory write-up you have mentioned this. It might be better if v1
> > includes this change, so that the partition hierarchy is visible in
> EXPLAIN
> > output as well.
> >
> > 6. Explain output of scan on a partitioned table shows Append node with
> > individual table scans as sub-plans. May be we should annotate Append
> node
> > with
> > the name of the partitioned table to make EXPLAIN output more readable.
>
> I got rid of all the optimizer changes in the new version (except a line
> or two). I did that by switching to storing parent-child relationships in
> pg_inherits so that all the existing optimizer code for inheritance sets
> works unchanged. Also, the implementation detail that required to put
> only leaf partitions in the append list is also gone. Previously no
> storage was allocated for partitioned tables (either root or any of the
> internal partitions), so it was being done that way.
>
With this set of patches we are still flattening all the partitions.
postgres=# create table t1 (a int, b int) partition by range(a);
CREATE TABLE
postgres=# create table t1_p1 partition of t1 for values start (0) end
(100) exclusive partition by range(b);
CREATE TABLE
postgres=# create table t1_p1_p1 partition of t1_p1 for values start (0)
end (100) exclusive;
CREATE TABLE
postgres=# explain verbose select * from t1;
QUERY PLAN
-------------------------------------------------------------------------
Append (cost=0.00..32.60 rows=2262 width=8)
-> Seq Scan on public.t1 (cost=0.00..0.00 rows=1 width=8)
Output: t1.a, t1.b
-> Seq Scan on public.t1_p1 (cost=0.00..0.00 rows=1 width=8)
Output: t1_p1.a, t1_p1.b
-> Seq Scan on public.t1_p1_p1 (cost=0.00..32.60 rows=2260 width=8)
Output: t1_p1_p1.a, t1_p1_p1.b
(7 rows)
Retaining the partition hierarchy would help to push-down join across
partition hierarchy effectively.
>
> Regarding 6, it seems to me that because Append does not have a associated
> relid (like scan nodes have with scanrelid). Maybe we need to either fix
> Append or create some enhanced version of Append which would also support
> dynamic pruning.
>
Right, I think, Append might store the relid of the parent table as well as
partitioning information at that level along-with the subplans.
Some more comments
Would it be better to declare PartitionDescData as
{
int nparts;
PartitionInfo *partinfo; /* array of partition information structures. */
}
The new syntax allows CREATE TABLE to be specified as partition of an
already partitioned table. Is it possible to do the same for CREATE FOREIGN
TABLE? Or that's material for v2? Similarly for ATTACH PARTITION.
--
Best Wishes,
Ashutosh Bapat
EnterpriseDB Corporation
The Postgres Database Company
^ permalink raw reply [nested|flat] 161+ messages in thread
* Re: Declarative partitioning
@ 2016-04-15 17:00 Robert Haas <robertmhaas@gmail.com>
parent: Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
1 sibling, 1 reply; 161+ messages in thread
From: Robert Haas @ 2016-04-15 17:00 UTC (permalink / raw)
To: Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; +Cc: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; Alexander Korotkov <a.korotkov@postgrespro.ru>; Amit Langote <amitlangote09@gmail.com>; Corey Huinker <corey.huinker@gmail.com>; pgsql-hackers
On Fri, Apr 15, 2016 at 5:46 AM, Ashutosh Bapat
<ashutosh.bapat@enterprisedb.com> wrote:
> Retaining the partition hierarchy would help to push-down join across
> partition hierarchy effectively.
-1. You don't get to insert cruft into the final plan for the
convenience of the optimizer. I think the AppendPath needs to be
annotated with sufficient information to do whatever query planning
optimizations we want, and some or all of that may need to carry over
to the Append plan to allow run-time partition pruning. But I think
that flattening nests of Appends is a good optimization and we should
preserve it. If that makes the additional information that any given
Append needs to carry a bit more complex, so be it.
I also think it's very good that Amit has kept the query planner
unchanged in this initial patch. Let's leave that work to phase two.
What I suggest we do when the time comes is invent new nodes
RangePartitionMap, ListPartitionMap, HashPartitionMap. Each contains
minimal metadata needed for tuple routing or planner transformation.
For example, RangePartitionMap can contain an array of partition
boundaries - represented as Datums - and an array of mappings, each a
Node *. The associated value can be another PartitionMap object if
there is subpartitioning in use, or an OID. This can be used both for
matching up partitions for join pushdown, and also for fast tuple
routing and runtime partition pruning.
> 2. The new syntax allows CREATE TABLE to be specified as partition of an
> already partitioned table. Is it possible to do the same for CREATE FOREIGN
> TABLE? Or that's material for v2? Similarly for ATTACH PARTITION.
+1 for making CREATE FOREIGN TABLE support that also, and in version
1. And same for ATTACH PARTITION.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 161+ messages in thread
* Re: Declarative partitioning
@ 2016-04-18 05:02 Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
parent: Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
0 siblings, 1 reply; 161+ messages in thread
From: Amit Langote @ 2016-04-18 05:02 UTC (permalink / raw)
To: Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; +Cc: Robert Haas <robertmhaas@gmail.com>; Alexander Korotkov <a.korotkov@postgrespro.ru>; Amit Langote <amitlangote09@gmail.com>; Corey Huinker <corey.huinker@gmail.com>; pgsql-hackers
Hi Ashutosh,
On 2016/04/15 18:48, Ashutosh Bapat wrote:
> With the new set of patches, I am getting following warnings but no
> compilation failures. Patches apply smoothly.
> partition.c:1216:21: warning: variable ‘form’ set but not used
> [-Wunused-but-set-variable]
> partition.c:1637:20: warning: variable ‘form’ set but not used
> [-Wunused-but-set-variable]
Ah, will fix.
> For creating partition the documentation seems to suggest the syntax
> create table t1_p1 partition of t1 for values {start {0} end {100}
> exclusive;
> but following syntax works
> create table t1_p1 partition of t1 for values start (0) end (100) exclusive;
Hmm, I see the following in docs:
...
and partition_bound_spec is:
FOR VALUES { list_spec | range_spec }
...
list_spec in FOR VALUES is:
{ IN ( expression [, ...] ) }
range_spec in FOR VALUES is:
{ START (lower-bound) [ INCLUSIVE | EXCLUSIVE ] |
END (upper-bound) [ INCLUSIVE | EXCLUSIVE ] |
START (lower-bound) [ INCLUSIVE | EXCLUSIVE ] END (upper-bound) [
INCLUSIVE | EXCLUSIVE ] }
By the way, I see that you are always specifying "exclusive" for end bound
in your examples. The specification is optional if that wasn't apparent.
Default (ie, without explicit specification) is [start, end).
>> I got rid of all the optimizer changes in the new version (except a line
>> or two). I did that by switching to storing parent-child relationships in
>> pg_inherits so that all the existing optimizer code for inheritance sets
>> works unchanged. Also, the implementation detail that required to put
>> only leaf partitions in the append list is also gone. Previously no
>> storage was allocated for partitioned tables (either root or any of the
>> internal partitions), so it was being done that way.
>>
>
> With this set of patches we are still flattening all the partitions.
> postgres=# create table t1 (a int, b int) partition by range(a);
> CREATE TABLE
> postgres=# create table t1_p1 partition of t1 for values start (0) end
> (100) exclusive partition by range(b);
> CREATE TABLE
> postgres=# create table t1_p1_p1 partition of t1_p1 for values start (0)
> end (100) exclusive;
> CREATE TABLE
> postgres=# explain verbose select * from t1;
> QUERY PLAN
> -------------------------------------------------------------------------
> Append (cost=0.00..32.60 rows=2262 width=8)
> -> Seq Scan on public.t1 (cost=0.00..0.00 rows=1 width=8)
> Output: t1.a, t1.b
> -> Seq Scan on public.t1_p1 (cost=0.00..0.00 rows=1 width=8)
> Output: t1_p1.a, t1_p1.b
> -> Seq Scan on public.t1_p1_p1 (cost=0.00..32.60 rows=2260 width=8)
> Output: t1_p1_p1.a, t1_p1_p1.b
> (7 rows)
> Retaining the partition hierarchy would help to push-down join across
> partition hierarchy effectively.
Yes, it's still a flattened append (basically what optimizer currently
creates for arbitrary inheritance trees). I didn't modify any of that yet.
>> Regarding 6, it seems to me that because Append does not have a associated
>> relid (like scan nodes have with scanrelid). Maybe we need to either fix
>> Append or create some enhanced version of Append which would also support
>> dynamic pruning.
>>
>
> Right, I think, Append might store the relid of the parent table as well as
> partitioning information at that level along-with the subplans.
For time being, I will leave this as yet unaddressed (I am thinking about
what is reasonable to do for this also considering Robert's comment). Is
that OK?
> Some more comments
> Would it be better to declare PartitionDescData as
> {
> int nparts;
> PartitionInfo *partinfo; /* array of partition information structures. */
> }
I think that might be better. Will do it the way you suggest.
> The new syntax allows CREATE TABLE to be specified as partition of an
> already partitioned table. Is it possible to do the same for CREATE FOREIGN
> TABLE? Or that's material for v2? Similarly for ATTACH PARTITION.
OK, I will address this in the next version. One question though: should
foreign table be only allowed to be leaf partitions (ie, no PARTITION BY
clause in CREATE FOREIGN TABLE ... PARTITION OF)? By the way, ATTACH
PARTITION only allows leaf partitions anyway.
Thanks a lot for the review!
Thanks,
Amt
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 161+ messages in thread
* Re: Declarative partitioning
@ 2016-04-18 05:19 Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
parent: Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
1 sibling, 1 reply; 161+ messages in thread
From: Amit Langote @ 2016-04-18 05:19 UTC (permalink / raw)
To: Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; +Cc: Robert Haas <robertmhaas@gmail.com>; Alexander Korotkov <a.korotkov@postgrespro.ru>; Amit Langote <amitlangote09@gmail.com>; Corey Huinker <corey.huinker@gmail.com>; pgsql-hackers
Hi,
On 2016/04/15 18:46, Ashutosh Bapat wrote:
>
> 3. PartitionKeyData contains KeyTypeCollInfo, whose contents can be
> obtained by calling functions exprType, exprTypemod on partexprs. Why do we
> need to store that information as a separate member?
There was no KeyTypeCollInfo in early days of the patch and then I found
myself doing a lot of:
partexprs_item = list_head(key->partexprs);
for (attr in key->partattrs)
{
if (attr->attnum != 0)
{
// simple column reference, get type from attr
}
else
{
// expression, get type using exprType, etc.
partexprs_item = lnext(partexprs_item);
}
}
That ended up being quite a few places (though I managed to reduce the
number of places over time). So, I created this struct which is
initialized when partition key is built (on first open of the partitioned
table).
Thanks,
Amit
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 161+ messages in thread
* Re: Declarative partitioning
@ 2016-04-18 06:19 Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
parent: Robert Haas <robertmhaas@gmail.com>
0 siblings, 0 replies; 161+ messages in thread
From: Ashutosh Bapat @ 2016-04-18 06:19 UTC (permalink / raw)
To: Robert Haas <robertmhaas@gmail.com>; +Cc: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; Alexander Korotkov <a.korotkov@postgrespro.ru>; Amit Langote <amitlangote09@gmail.com>; Corey Huinker <corey.huinker@gmail.com>; pgsql-hackers
On Fri, Apr 15, 2016 at 10:30 PM, Robert Haas <robertmhaas@gmail.com> wrote:
> On Fri, Apr 15, 2016 at 5:46 AM, Ashutosh Bapat
> <ashutosh.bapat@enterprisedb.com> wrote:
> > Retaining the partition hierarchy would help to push-down join across
> > partition hierarchy effectively.
>
> -1. You don't get to insert cruft into the final plan for the
> convenience of the optimizer. I think the AppendPath needs to be
> annotated with sufficient information to do whatever query planning
> optimizations we want, and some or all of that may need to carry over
> to the Append plan to allow run-time partition pruning. But I think
> that flattening nests of Appends is a good optimization and we should
> preserve it. If that makes the additional information that any given
> Append needs to carry a bit more complex, so be it.
>
> I also think it's very good that Amit has kept the query planner
> unchanged in this initial patch. Let's leave that work to phase two.
> What I suggest we do when the time comes is invent new nodes
> RangePartitionMap, ListPartitionMap, HashPartitionMap. Each contains
> minimal metadata needed for tuple routing or planner transformation.
> For example, RangePartitionMap can contain an array of partition
> boundaries - represented as Datums - and an array of mappings, each a
> Node *. The associated value can be another PartitionMap object if
> there is subpartitioning in use, or an OID.
range table index instead of OID to make it easy to lookup the
> This can be used both for
> matching up partitions for join pushdown, and also for fast tuple
> routing and runtime partition pruning.
>
I was thinking about join pushdown (in partitioning hierarchy) of multiple
tables which have similar partitioning structure for few upper levels but
the entire partitioning hierarchy does not match. Pushing down the join as
much possible into the partition hierarchy will help. We might end up with
joins between a plain table and Append relation at the leaf level. For such
join push-down it looks like we will have to construct corresponding Append
hierarchy, push the joins down and then (may be) collapse it to just
collect the results of joins at the leaf level. But preparing for that kind
of optimization need not be part of this work.
>
> > 2. The new syntax allows CREATE TABLE to be specified as partition of an
> > already partitioned table. Is it possible to do the same for CREATE
> FOREIGN
> > TABLE? Or that's material for v2? Similarly for ATTACH PARTITION.
>
> +1 for making CREATE FOREIGN TABLE support that also, and in version
> 1. And same for ATTACH PARTITION.
>
>
--
Best Wishes,
Ashutosh Bapat
EnterpriseDB Corporation
The Postgres Database Company
^ permalink raw reply [nested|flat] 161+ messages in thread
* Re: Declarative partitioning
@ 2016-04-18 06:33 Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
0 siblings, 1 reply; 161+ messages in thread
From: Ashutosh Bapat @ 2016-04-18 06:33 UTC (permalink / raw)
To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; +Cc: Robert Haas <robertmhaas@gmail.com>; Alexander Korotkov <a.korotkov@postgrespro.ru>; Amit Langote <amitlangote09@gmail.com>; Corey Huinker <corey.huinker@gmail.com>; pgsql-hackers
> >> Regarding 6, it seems to me that because Append does not have a
> associated
> >> relid (like scan nodes have with scanrelid). Maybe we need to either
> fix
> >> Append or create some enhanced version of Append which would also
> support
> >> dynamic pruning.
> >>
> >
> > Right, I think, Append might store the relid of the parent table as well
> as
> > partitioning information at that level along-with the subplans.
>
> For time being, I will leave this as yet unaddressed (I am thinking about
> what is reasonable to do for this also considering Robert's comment). Is
> that OK?
>
Right now EXPLAIN of select * from t1, where t1 is partitioned table shows
Append
-> Seq Scan on t1
-> Seq scan on partition 1
-> seq scan on partition 2
...
which shows t1 as well as all the partitions on the same level. Users might
have accepted that for inheritance hierarchy but for partitioning that can
be confusing, esp. when all the error messages and documentation indicate
that t1 is an empty (shell?) table. Instead showing it like
Append for t1 -- (essentially show that this is Append for partitioned
table t1, exact text might vary)
-> Seq scan on partition 1
-> ....
would be more readable. Similarly if we are going to collapse all the
Append hierarchy, it might get even more confusing. Listing all the
intermediate partitioned tables as Seq Scan on them would be confusing for
the reasons mentioned above, and not listing them might make user wonder
about the reasons for their disappearance. I am not sure what's the
solution their.
> > Some more comments
> > Would it be better to declare PartitionDescData as
> > {
> > int nparts;
> > PartitionInfo *partinfo; /* array of partition information structures. */
> > }
>
> I think that might be better. Will do it the way you suggest.
>
> > The new syntax allows CREATE TABLE to be specified as partition of an
> > already partitioned table. Is it possible to do the same for CREATE
> FOREIGN
> > TABLE? Or that's material for v2? Similarly for ATTACH PARTITION.
>
> OK, I will address this in the next version. One question though: should
> foreign table be only allowed to be leaf partitions (ie, no PARTITION BY
> clause in CREATE FOREIGN TABLE ... PARTITION OF)?
That seems a better way. Otherwise users might wonder whether we keep the
partitions of a foreign table on the foreign server which won't be true.
But then we allow foreign tables to have local tables as children in
inheritance, so somebody from that background might find it incompatible. I
think we shouldn't let the connection between partitioning and inheritance
linger longer than necessary.
--
Best Wishes,
Ashutosh Bapat
EnterpriseDB Corporation
The Postgres Database Company
^ permalink raw reply [nested|flat] 161+ messages in thread
* Re: Declarative partitioning
@ 2016-04-18 06:38 Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
0 siblings, 1 reply; 161+ messages in thread
From: Ashutosh Bapat @ 2016-04-18 06:38 UTC (permalink / raw)
To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; +Cc: Robert Haas <robertmhaas@gmail.com>; Alexander Korotkov <a.korotkov@postgrespro.ru>; Amit Langote <amitlangote09@gmail.com>; Corey Huinker <corey.huinker@gmail.com>; pgsql-hackers
> On 2016/04/15 18:46, Ashutosh Bapat wrote:
> >
> > 3. PartitionKeyData contains KeyTypeCollInfo, whose contents can be
> > obtained by calling functions exprType, exprTypemod on partexprs. Why do
> we
> > need to store that information as a separate member?
>
> There was no KeyTypeCollInfo in early days of the patch and then I found
> myself doing a lot of:
>
> partexprs_item = list_head(key->partexprs);
> for (attr in key->partattrs)
> {
> if (attr->attnum != 0)
> {
> // simple column reference, get type from attr
> }
> else
> {
> // expression, get type using exprType, etc.
> partexprs_item = lnext(partexprs_item);
> }
> }
>
At least the two loops can be flattened to a single loop if we keep only
expressions list with attributes being just Var nodes. exprType() etc.
would then work seemlessly.
>
> That ended up being quite a few places (though I managed to reduce the
> number of places over time). So, I created this struct which is
> initialized when partition key is built (on first open of the partitioned
> table).
>
Hmm, I am just afraid that we might end up with some code using cached
information and some using exprType, exprTypmod etc.
--
Best Wishes,
Ashutosh Bapat
EnterpriseDB Corporation
The Postgres Database Company
^ permalink raw reply [nested|flat] 161+ messages in thread
* Re: Declarative partitioning
@ 2016-04-18 07:53 Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
parent: Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
0 siblings, 1 reply; 161+ messages in thread
From: Amit Langote @ 2016-04-18 07:53 UTC (permalink / raw)
To: Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; +Cc: Robert Haas <robertmhaas@gmail.com>; Alexander Korotkov <a.korotkov@postgrespro.ru>; Amit Langote <amitlangote09@gmail.com>; Corey Huinker <corey.huinker@gmail.com>; pgsql-hackers
On 2016/04/18 15:38, Ashutosh Bapat wrote:
>> There was no KeyTypeCollInfo in early days of the patch and then I found
>> myself doing a lot of:
>>
>> partexprs_item = list_head(key->partexprs);
>> for (attr in key->partattrs)
>> {
>> if (attr->attnum != 0)
>> {
>> // simple column reference, get type from attr
>> }
>> else
>> {
>> // expression, get type using exprType, etc.
>> partexprs_item = lnext(partexprs_item);
>> }
>> }
>>
>
> At least the two loops can be flattened to a single loop if we keep only
> expressions list with attributes being just Var nodes. exprType() etc.
> would then work seemlessly.
I didn't say anything about your suggestion to use a Node * list as a
representation for the cached partition key information. IIUC, you mean
instead of the AttrNumber[partnatts] array with non-zero attnum for a
named column slot and 0 for a expressional column slot, create a Node *
list with Var nodes for simple column references and Expr nodes for
expressions.
I would mention that the same information is also being used in contexts
where having simple attnums may be better (for example, when extracting
key of a tuple slot during tuple routing). Moreover, this is cached
information and I thought it may be better to follow the format that other
similar information uses (index key and such). Furthermore, looking at
qual matching code for indexes and recently introduced foreign key
optimization, it seems we will want to use a similar representation within
optimizer for partition keys. IndexOptInfo has int ncolumns and int *
indexkeys and then match_index_to_operand() compares index key attnums
with varattno of vars in qual. It's perhaps speculative at the moment
because there is not much code wanting to use it yet other than partition
DDL and tuple-routing and cached info seems to work as-is for the latter.
>> That ended up being quite a few places (though I managed to reduce the
>> number of places over time). So, I created this struct which is
>> initialized when partition key is built (on first open of the partitioned
>> table).
>>
>
> Hmm, I am just afraid that we might end up with some code using cached
> information and some using exprType, exprTypmod etc.
Well, you never use exprType(), etc. for partition keys in other than a
few places. All places that do always use the cached values. Mostly
partitioning DDL stuff so far. Tuple routing considers collation of
individual key columns when comparing input value with partition bounds.
Am I missing something?
Thanks,
Amit
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 161+ messages in thread
* Re: Declarative partitioning
@ 2016-04-18 08:43 Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
0 siblings, 0 replies; 161+ messages in thread
From: Ashutosh Bapat @ 2016-04-18 08:43 UTC (permalink / raw)
To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; +Cc: Robert Haas <robertmhaas@gmail.com>; Alexander Korotkov <a.korotkov@postgrespro.ru>; Amit Langote <amitlangote09@gmail.com>; Corey Huinker <corey.huinker@gmail.com>; pgsql-hackers
On Mon, Apr 18, 2016 at 1:23 PM, Amit Langote <Langote_Amit_f8@lab.ntt.co.jp
> wrote:
> On 2016/04/18 15:38, Ashutosh Bapat wrote:
> >> There was no KeyTypeCollInfo in early days of the patch and then I found
> >> myself doing a lot of:
> >>
> >> partexprs_item = list_head(key->partexprs);
> >> for (attr in key->partattrs)
> >> {
> >> if (attr->attnum != 0)
> >> {
> >> // simple column reference, get type from attr
> >> }
> >> else
> >> {
> >> // expression, get type using exprType, etc.
> >> partexprs_item = lnext(partexprs_item);
> >> }
> >> }
> >>
> >
> > At least the two loops can be flattened to a single loop if we keep only
> > expressions list with attributes being just Var nodes. exprType() etc.
> > would then work seemlessly.
>
> I didn't say anything about your suggestion to use a Node * list as a
> representation for the cached partition key information. IIUC, you mean
> instead of the AttrNumber[partnatts] array with non-zero attnum for a
> named column slot and 0 for a expressional column slot, create a Node *
> list with Var nodes for simple column references and Expr nodes for
> expressions.
>
> I would mention that the same information is also being used in contexts
> where having simple attnums may be better (for example, when extracting
> key of a tuple slot during tuple routing). Moreover, this is cached
> information and I thought it may be better to follow the format that other
> similar information uses (index key and such). Furthermore, looking at
> qual matching code for indexes and recently introduced foreign key
> optimization, it seems we will want to use a similar representation within
> optimizer for partition keys. IndexOptInfo has int ncolumns and int *
> indexkeys and then match_index_to_operand() compares index key attnums
> with varattno of vars in qual. It's perhaps speculative at the moment
> because there is not much code wanting to use it yet other than partition
> DDL and tuple-routing and cached info seems to work as-is for the latter.
>
Ok.
>
> >> That ended up being quite a few places (though I managed to reduce the
> >> number of places over time). So, I created this struct which is
> >> initialized when partition key is built (on first open of the
> partitioned
> >> table).
> >>
> >
> > Hmm, I am just afraid that we might end up with some code using cached
> > information and some using exprType, exprTypmod etc.
>
> Well, you never use exprType(), etc. for partition keys in other than a
> few places. All places that do always use the cached values. Mostly
> partitioning DDL stuff so far. Tuple routing considers collation of
> individual key columns when comparing input value with partition bounds.
>
>
I am not worried about the current code. But there will be a lot of code
added after version 1. I am worried about that.
--
Best Wishes,
Ashutosh Bapat
EnterpriseDB Corporation
The Postgres Database Company
^ permalink raw reply [nested|flat] 161+ messages in thread
* Re: Declarative partitioning
@ 2016-04-18 08:55 Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
parent: Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
0 siblings, 0 replies; 161+ messages in thread
From: Amit Langote @ 2016-04-18 08:55 UTC (permalink / raw)
To: Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; +Cc: Robert Haas <robertmhaas@gmail.com>; Alexander Korotkov <a.korotkov@postgrespro.ru>; Amit Langote <amitlangote09@gmail.com>; Corey Huinker <corey.huinker@gmail.com>; pgsql-hackers
On 2016/04/18 15:33, Ashutosh Bapat wrote:
>> For time being, I will leave this as yet unaddressed (I am thinking about
>> what is reasonable to do for this also considering Robert's comment). Is
>> that OK?
>>
>
> Right now EXPLAIN of select * from t1, where t1 is partitioned table shows
> Append
> -> Seq Scan on t1
> -> Seq scan on partition 1
> -> seq scan on partition 2
> ...
> which shows t1 as well as all the partitions on the same level. Users might
> have accepted that for inheritance hierarchy but for partitioning that can
> be confusing, esp. when all the error messages and documentation indicate
> that t1 is an empty (shell?) table. Instead showing it like
> Append for t1 -- (essentially show that this is Append for partitioned
> table t1, exact text might vary)
> -> Seq scan on partition 1
> -> ....
> would be more readable. Similarly if we are going to collapse all the
> Append hierarchy, it might get even more confusing. Listing all the
> intermediate partitioned tables as Seq Scan on them would be confusing for
> the reasons mentioned above, and not listing them might make user wonder
> about the reasons for their disappearance. I am not sure what's the
> solution their.
Yes, things remain confusing with Append plans for partitioned tables.
Note that currently if an internal partition doesn't show up, none of its
partitions do (given the way CHECK constraints are generated for each
table in the partition tree).
>> OK, I will address this in the next version. One question though: should
>> foreign table be only allowed to be leaf partitions (ie, no PARTITION BY
>> clause in CREATE FOREIGN TABLE ... PARTITION OF)?
>
> That seems a better way. Otherwise users might wonder whether we keep the
> partitions of a foreign table on the foreign server which won't be true.
Quite true. Can't assume anything other than what the FDW for a given
foreign table lets us assume about what the remote table looks like.
Existing FDW API allows to treat a foreign table as nothing other than
regular tables at best.
> But then we allow foreign tables to have local tables as children in
> inheritance, so somebody from that background might find it incompatible. I
> think we shouldn't let the connection between partitioning and inheritance
> linger longer than necessary.
I, too, think quite some about this last sentence. But I have only
considered DDL yet.
Thanks,
Amit
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 161+ messages in thread
* Re: Declarative partitioning
@ 2016-04-19 13:57 Ildar Musin <i.musin@postgrespro.ru>
parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
3 siblings, 1 reply; 161+ messages in thread
From: Ildar Musin @ 2016-04-19 13:57 UTC (permalink / raw)
To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; +Cc: Robert Haas <robertmhaas@gmail.com>; Alexander Korotkov <a.korotkov@postgrespro.ru>; Amit Langote <amitlangote09@gmail.com>; Corey Huinker <corey.huinker@gmail.com>; pgsql-hackers
Hi Amit,
On 15.04.2016 07:35, Amit Langote wrote:
> Thanks a lot for the comments. The patch set changed quite a bit since
> the last version. Once the CF entry was marked returned with feedback
> on March 22, I held off sending the new version at all. Perhaps, it
> would have been OK. Anyway here it is, if you are interested. I will
> create an entry in CF 2016-09 for the same. Also, see below replies to
> you individual comments.
Thanks for your new patch! I've tried it and discovered some strange
behavior for partitioning by composite key. Here is an example of my setup:
create table test(a int, b int) partition by range (a, b);
create table test_1 partition of test for values start (0, 0) end (100,
100);
create table test_2 partition of test for values start (100, 100) end
(200, 200);
create table test_3 partition of test for values start (200, 200) end
(300, 300);
It's alright so far. But if we try to insert record in which attribute
'a' belongs to one partition and attribute 'b' belongs to another then
record will be inserted in the first one:
insert into test(a, b) values (150, 50);
select tableoid::regclass, * from test;
tableoid | a | b
----------+-----+----
test_2 | 150 | 50
(1 row)
I think there should be an error because value for 'b' violates range
constraint for test_2. Now if we query data from 'test' and add filter b
< 100, then planner will exclude partitions 'test_2' (which actually
contains inserted row) and 'test_3' and return nothing:
select * from test where b < 100;
a | b
---+---
(0 rows)
explain (costs off) select * from test where b < 100;
QUERY PLAN
---------------------------
Append
-> Seq Scan on test
Filter: (b < 100)
-> Seq Scan on test_1
Filter: (b < 100)
(5 rows)
--
Ildar Musin
i.musin@postgrespro.ru
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 161+ messages in thread
* Re: Declarative partitioning
@ 2016-04-19 14:26 Alexander Korotkov <a.korotkov@postgrespro.ru>
parent: Ildar Musin <i.musin@postgrespro.ru>
0 siblings, 1 reply; 161+ messages in thread
From: Alexander Korotkov @ 2016-04-19 14:26 UTC (permalink / raw)
To: Ildar Musin <i.musin@postgrespro.ru>; +Cc: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; Robert Haas <robertmhaas@gmail.com>; Amit Langote <amitlangote09@gmail.com>; Corey Huinker <corey.huinker@gmail.com>; pgsql-hackers
On Tue, Apr 19, 2016 at 4:57 PM, Ildar Musin <i.musin@postgrespro.ru> wrote:
> On 15.04.2016 07:35, Amit Langote wrote:
>
>> Thanks a lot for the comments. The patch set changed quite a bit since
>> the last version. Once the CF entry was marked returned with feedback on
>> March 22, I held off sending the new version at all. Perhaps, it would have
>> been OK. Anyway here it is, if you are interested. I will create an entry
>> in CF 2016-09 for the same. Also, see below replies to you individual
>> comments.
>>
>
> Thanks for your new patch! I've tried it and discovered some strange
> behavior for partitioning by composite key. Here is an example of my setup:
>
> create table test(a int, b int) partition by range (a, b);
> create table test_1 partition of test for values start (0, 0) end (100,
> 100);
> create table test_2 partition of test for values start (100, 100) end
> (200, 200);
> create table test_3 partition of test for values start (200, 200) end
> (300, 300);
>
> It's alright so far. But if we try to insert record in which attribute 'a'
> belongs to one partition and attribute 'b' belongs to another then record
> will be inserted in the first one:
>
> insert into test(a, b) values (150, 50);
>
> select tableoid::regclass, * from test;
> tableoid | a | b
> ----------+-----+----
> test_2 | 150 | 50
> (1 row)
That's how composite keys work. First subkey is checked. If it's equal then
second subkey is checked and so on.
# SELECT (100, 100) < (150, 50), (150, 50) < (200, 200);
?column? | ?column?
----------+----------
t | t
(1 row)
Another question is that it might be NOT what users expect from that. From
the syntax side it very looks like defining something boxes regions for two
keys which could be replacement for subpartitioning. But it isn't so.
------
Alexander Korotkov
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company
^ permalink raw reply [nested|flat] 161+ messages in thread
* Re: Declarative partitioning
@ 2016-04-19 14:52 Amit Langote <amitlangote09@gmail.com>
parent: Alexander Korotkov <a.korotkov@postgrespro.ru>
0 siblings, 1 reply; 161+ messages in thread
From: Amit Langote @ 2016-04-19 14:52 UTC (permalink / raw)
To: Alexander Korotkov <a.korotkov@postgrespro.ru>; +Cc: Ildar Musin <i.musin@postgrespro.ru>; Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; Robert Haas <robertmhaas@gmail.com>; Corey Huinker <corey.huinker@gmail.com>; pgsql-hackers
Hi Idlar, Alexander,
On Tue, Apr 19, 2016 at 11:26 PM, Alexander Korotkov
<a.korotkov@postgrespro.ru> wrote:
> On Tue, Apr 19, 2016 at 4:57 PM, Ildar Musin <i.musin@postgrespro.ru> wrote:
>>
>> Thanks for your new patch! I've tried it and discovered some strange
>> behavior for partitioning by composite key. Here is an example of my setup:
>>
>> create table test(a int, b int) partition by range (a, b);
>> create table test_1 partition of test for values start (0, 0) end (100,
>> 100);
>> create table test_2 partition of test for values start (100, 100) end
>> (200, 200);
>> create table test_3 partition of test for values start (200, 200) end
>> (300, 300);
>>
>> It's alright so far. But if we try to insert record in which attribute 'a'
>> belongs to one partition and attribute 'b' belongs to another then record
>> will be inserted in the first one:
>>
>> insert into test(a, b) values (150, 50);
>>
>> select tableoid::regclass, * from test;
>> tableoid | a | b
>> ----------+-----+----
>> test_2 | 150 | 50
>> (1 row)
>
>
> That's how composite keys work. First subkey is checked. If it's equal then
> second subkey is checked and so on.
>
> # SELECT (100, 100) < (150, 50), (150, 50) < (200, 200);
> ?column? | ?column?
> ----------+----------
> t | t
> (1 row)
Yes.
> Another question is that it might be NOT what users expect from that. From
> the syntax side it very looks like defining something boxes regions for two
> keys which could be replacement for subpartitioning. But it isn't so.
Need to check why query with qual b < 100 behaves the way it does.
Something's going wrong there with the constraints (partition
predicates) that are being generated internally (as mentioned before,
still driven by constraint exclusion using the constraints generated
on-the-fly).
As for the composite range partition bounds in Ildar's example, it's
as if the second value in the key never determines the fate of a row
going into some partition, therefore no constraints should have been
generated for column b of the key. I'm afraid that's not the case as
per the latest patch. Will fix.
Thanks a lot for trying it out and the report.
Thanks,
Amit
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 161+ messages in thread
* Re: Declarative partitioning
@ 2016-04-20 10:28 Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
parent: Amit Langote <amitlangote09@gmail.com>
0 siblings, 1 reply; 161+ messages in thread
From: Amit Langote @ 2016-04-20 10:28 UTC (permalink / raw)
To: Amit Langote <amitlangote09@gmail.com>; Alexander Korotkov <a.korotkov@postgrespro.ru>; +Cc: Ildar Musin <i.musin@postgrespro.ru>; Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; Robert Haas <robertmhaas@gmail.com>; Corey Huinker <corey.huinker@gmail.com>; pgsql-hackers
On 2016/04/19 23:52, Amit Langote wrote:
> On Tue, Apr 19, 2016 at 11:26 PM, Alexander Korotkov
>> Another question is that it might be NOT what users expect from that. From
>> the syntax side it very looks like defining something boxes regions for two
>> keys which could be replacement for subpartitioning. But it isn't so.
>
> Need to check why query with qual b < 100 behaves the way it does.
> Something's going wrong there with the constraints (partition
> predicates) that are being generated internally (as mentioned before,
> still driven by constraint exclusion using the constraints generated
> on-the-fly).
>
> As for the composite range partition bounds in Ildar's example, it's
> as if the second value in the key never determines the fate of a row
> going into some partition, therefore no constraints should have been
> generated for column b of the key. I'm afraid that's not the case as
> per the latest patch. Will fix.
The strange behavior that Ildar reported should have been fixed with the
attached updated set of patches (v2):
create table test(a int, b int) partition by range (a, b);
create table test_1 partition of test for values start (0, 0) end (100, 100);
create table test_2 partition of test for values start (100, 100) end
(200, 200);
create table test_3 partition of test for values start (200, 200) end
(300, 300);
CREATE TABLE
CREATE TABLE
CREATE TABLE
CREATE TABLE
insert into test(a, b) values (150, 50);
INSERT 0 1
select * from test where b < 100;
a | b
-----+----
150 | 50
(1 row)
explain (costs off) select * from test where b < 100;
QUERY PLAN
---------------------------
Append
-> Seq Scan on test
Filter: (b < 100)
-> Seq Scan on test_1
Filter: (b < 100)
-> Seq Scan on test_2
Filter: (b < 100)
-> Seq Scan on test_3
Filter: (b < 100)
(9 rows)
Multi-column range partitioning seems a bit tricky as far as generating
constraints on individual columns using a partition's lower and upper
bounds (both composite values) is concerned. I mentally pictured
something like the following example scenario:
create table test(a int, b int, c int)
partition by range (a, b, c);
create table test_1 partition of test
for values start (0, 0, 0) end (0, 2, 0);
create table test_2 partition of test
for values start (0, 2, 0) end (0, 3, 0);
create table test_3 partition of test
for values start (0, 3, 0) end (0, 4, 0);
create table test_4 partition of test
for values start (0, 4, 0) end (1, 0, 0);
create table test_5 partition of test
for values start (1, 0, 0) end (1, 2, 0);
create table test_6 partition of test
for values start (1, 2, 0) end (1, 3, 0);
create table test_7 partition of test
for values start (1, 3, 0) end (1, 4, 0);
create table test_8 partition of test
for values start (1, 4, 0) end (2, 0, 0);
Useful to think of the above as sequence of ranges [000, 020), [020, 030),
[030, 040), [040, 100), [100, 120), [120, 130), [130, 140), [140, 200) for
purposes of finding the partition for a row.
Then constraints generated internally for each partition:
test_1: a = 0 AND b >= 0 AND b <= 2
test_2: a = 0 AND b >= 2 AND b <= 3
test_3: a = 0 AND b >= 3 AND b <= 4
test_4: a >= 0 AND a <= 1
test_5: a = 1 AND b >= 0 AND b <= 2
test_6: a = 1 AND b >= 2 AND b <= 3
test_7: a = 1 AND b >= 3 AND b <= 4
test_8: a >= 1 AND a <= 2
I will try further to poke holes in my thinking about this. Please feel
free to point out if you find any.
Thanks,
Amit
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Attachments:
[text/x-diff] 0001-Add-syntax-to-specify-partition-key-v2.patch (41.5K, ../../571759D8.7030205@lab.ntt.co.jp/2-0001-Add-syntax-to-specify-partition-key-v2.patch)
download | inline diff:
From c38e75b08869ecbfd81c5fe753cbcaa9358946fb Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Mon, 22 Feb 2016 14:06:33 +0900
Subject: [PATCH 1/5] Add syntax to specify partition key.
This adds the following clause to CREATE TABLE:
PARTITION BY method (col [USING opclass] [, ...])
where 'method' is either RANGE or LIST. 'col' could be either a simple
column reference or a parenthesized expression. 'opclass' is name
of btree operator class to use with the column. If method is LIST,
only one column is allowed, whereas, for RANGE method up to 32 columns
can be specified. If this clause is specified, the following constraints
viz. PARTITION KEY, UNIQUE, FOREIGN KEY, EXCLUSION are not allowed
because they cannot be supported on partitioned tables.
This adds 2 new parser nodes viz. PartitionElem and PartitionBy. The former
is similar to IndexElem and the latter consists of details of partition
key such as the method and list of PartitionElems. The new member partby
of CreateStmt is a pointer to the PartitionBy struct created when PARTITION
BY clause is present.
---
doc/src/sgml/ref/create_table.sgml | 35 +++++
src/backend/commands/indexcmds.c | 4 +-
src/backend/commands/tablecmds.c | 215 ++++++++++++++++++++++++++++
src/backend/nodes/copyfuncs.c | 33 +++++
src/backend/nodes/equalfuncs.c | 28 ++++
src/backend/nodes/outfuncs.c | 27 ++++
src/backend/parser/gram.y | 110 ++++++++++++---
src/backend/parser/parse_agg.c | 11 ++
src/backend/parser/parse_expr.c | 5 +
src/backend/parser/parse_utilcmd.c | 63 ++++++++
src/include/commands/defrem.h | 2 +
src/include/nodes/nodes.h | 2 +
src/include/nodes/parsenodes.h | 35 +++++
src/include/parser/kwlist.h | 1 +
src/include/parser/parse_node.h | 3 +-
src/include/pg_config_manual.h | 5 +
src/test/regress/expected/create_table.out | 79 ++++++++++
src/test/regress/sql/create_table.sql | 68 +++++++++
18 files changed, 704 insertions(+), 22 deletions(-)
diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml
index d1807ed..70a7579 100644
--- a/doc/src/sgml/ref/create_table.sgml
+++ b/doc/src/sgml/ref/create_table.sgml
@@ -28,6 +28,7 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
[, ... ]
] )
[ INHERITS ( <replaceable>parent_table</replaceable> [, ... ] ) ]
+[ PARTITION BY {RANGE | LIST} ( { <replaceable class="parameter">column_name</replaceable> | ( <replaceable class="parameter">expression</replaceable> ) } [ <replaceable class="parameter">opclass</replaceable> ] [, ...] )
[ WITH ( <replaceable class="PARAMETER">storage_parameter</replaceable> [= <replaceable class="PARAMETER">value</replaceable>] [, ... ] ) | WITH OIDS | WITHOUT OIDS ]
[ ON COMMIT { PRESERVE ROWS | DELETE ROWS | DROP } ]
[ TABLESPACE <replaceable class="PARAMETER">tablespace_name</replaceable> ]
@@ -38,6 +39,7 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
| <replaceable>table_constraint</replaceable> }
[, ... ]
) ]
+[ PARTITION BY {RANGE | LIST} ( { <replaceable class="parameter">column_name</replaceable> | ( <replaceable class="parameter">expression</replaceable> ) } [ <replaceable class="parameter">opclass</replaceable> ] [, ...] )
[ WITH ( <replaceable class="PARAMETER">storage_parameter</replaceable> [= <replaceable class="PARAMETER">value</replaceable>] [, ... ] ) | WITH OIDS | WITHOUT OIDS ]
[ ON COMMIT { PRESERVE ROWS | DELETE ROWS | DROP } ]
[ TABLESPACE <replaceable class="PARAMETER">tablespace_name</replaceable> ]
@@ -314,6 +316,39 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
</varlistentry>
<varlistentry>
+ <term><literal>PARTITION BY {RANGE | LIST} ( { <replaceable class="parameter">column_name</replaceable> | ( <replaceable class="parameter">expression</replaceable> ) } [ <replaceable class="parameter">opclass</replaceable> ] [, ...] ) </literal></term>
+ <listitem>
+ <para>
+ The optional <literal>PARTITION BY</> clause specifies a method of
+ partitioning the table and the corresponding partition key. Table
+ thus created is called <firstterm>partitioned</firstterm> table. Key
+ consists of an ordered list of column names and/or expressions when
+ using the <literal>RANGE</> method, whereas only a single column or
+ expression can be specified when using the <literal>LIST</> method.
+ The type of a key column or an expresion must have an associated
+ btree operator class or one must be specified along with the column
+ or the expression.
+ </para>
+
+ <para>
+ A partitioned table is divided into sub-tables (called partitions), which
+ in turn, are created using separate <literal>CREATE TABLE</> commands.
+ The table itself is empty. A data row inserted into the table is mapped
+ to and stored in one of the partitions (if one exists) based on the
+ values of columns and/or expressions in the partition key and partition
+ rules associated with the partitions.
+ </para>
+
+ <para>
+ Currently, there are following limitations on definition of partitioned
+ tables: one cannot specify any UNIQUE, PRIMARY KEY, EXCLUDE and/or
+ FOREIGN KEY constraints.
+ </para>
+
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><literal>LIKE <replaceable>source_table</replaceable> [ <replaceable>like_option</replaceable> ... ]</literal></term>
<listitem>
<para>
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index d14d540..4ef6b80 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -69,8 +69,6 @@ static void ComputeIndexAttrs(IndexInfo *indexInfo,
char *accessMethodName, Oid accessMethodId,
bool amcanorder,
bool isconstraint);
-static Oid GetIndexOpClass(List *opclass, Oid attrType,
- char *accessMethodName, Oid accessMethodId);
static char *ChooseIndexName(const char *tabname, Oid namespaceId,
List *colnames, List *exclusionOpNames,
bool primary, bool isconstraint);
@@ -1256,7 +1254,7 @@ ComputeIndexAttrs(IndexInfo *indexInfo,
/*
* Resolve possibly-defaulted operator class specification
*/
-static Oid
+Oid
GetIndexOpClass(List *opclass, Oid attrType,
char *accessMethodName, Oid accessMethodId)
{
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 45a5144..c881ff0 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -433,6 +433,11 @@ static void RangeVarCallbackForDropRelation(const RangeVar *rel, Oid relOid,
Oid oldRelOid, void *arg);
static void RangeVarCallbackForAlterRelation(const RangeVar *rv, Oid relid,
Oid oldrelid, void *arg);
+static PartitionBy *transformPartitionBy(Relation rel, PartitionBy *partitionby);
+static void ComputePartitionAttrs(Oid relid, List *partParams,
+ AttrNumber *partattrs,
+ List **partexprs,
+ Oid *partoplass);
/* ----------------------------------------------------------------
@@ -710,6 +715,21 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
AddRelationNewConstraints(rel, rawDefaults, stmt->constraints,
true, true, false);
+ /* Process partition key, if any */
+ if (stmt->partby)
+ {
+ PartitionBy *partby;
+ int16 partnatts;
+ AttrNumber partattrs[PARTITION_MAX_KEYS];
+ Oid partopclass[PARTITION_MAX_KEYS];
+ List *partexprs = NIL;
+
+ partnatts = list_length(stmt->partby->partParams);
+ partby = transformPartitionBy(rel, stmt->partby);
+ ComputePartitionAttrs(relationId, stmt->partby->partParams,
+ partattrs, &partexprs, partopclass);
+ }
+
ObjectAddressSet(address, RelationRelationId, relationId);
/*
@@ -12061,3 +12081,198 @@ RangeVarCallbackForAlterRelation(const RangeVar *rv, Oid relid, Oid oldrelid,
ReleaseSysCache(tuple);
}
+
+/*
+ * transformPartitionBy
+ * Transform any expressions present in the partition key
+ */
+static PartitionBy *
+transformPartitionBy(Relation rel, PartitionBy *partitionby)
+{
+ PartitionBy *partby;
+ ParseState *pstate;
+ RangeTblEntry *rte;
+ ListCell *l;
+
+ partby = (PartitionBy *) makeNode(PartitionBy);
+
+ partby->strategy = partitionby->strategy;
+ partby->location = partitionby->location;
+ partby->partParams = NIL;
+
+ /*
+ * Create a dummy ParseState and insert the target relation as its sole
+ * rangetable entry. We need a ParseState for transformExpr.
+ */
+ pstate = make_parsestate(NULL);
+ rte = addRangeTableEntryForRelation(pstate, rel, NULL, false, true);
+ addRTEtoQuery(pstate, rte, true, true, true);
+
+ /* take care of any partition expressions */
+ foreach(l, partitionby->partParams)
+ {
+ ListCell *column;
+ PartitionElem *pelem = (PartitionElem *) lfirst(l);
+
+ /* Check for PARTITION BY ... ON (foo, foo) */
+ foreach(column, partby->partParams)
+ {
+ PartitionElem *pparam = (PartitionElem *) lfirst(column);
+
+ if (pelem->name && pparam->name &&
+ !strcmp(pelem->name, pparam->name))
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_COLUMN),
+ errmsg("column \"%s\" appears twice in partition key", pelem->name),
+ parser_errposition(pstate, pelem->location)));
+ }
+
+ if (pelem->expr)
+ {
+ /* Now do parse transformation of the expression */
+ pelem->expr = transformExpr(pstate, pelem->expr,
+ EXPR_KIND_PARTITION_KEY);
+
+ /* we have to fix its collations too */
+ assign_expr_collations(pstate, pelem->expr);
+
+ /*
+ * transformExpr() should have already rejected subqueries,
+ * aggregates, and window functions, based on the EXPR_KIND_ for
+ * a partition key expression.
+ *
+ * Also reject expressions returning sets; this is for consistency
+ * DefineRelation() will make more checks.
+ */
+ if (expression_returns_set(pelem->expr))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("partition key expression cannot return a set"),
+ parser_errposition(pstate, pelem->location)));
+ }
+
+ partby->partParams = lappend(partby->partParams, pelem);
+ }
+
+ return partby;
+}
+
+/*
+ * ComputePartitionAttrs
+ * Compute per-partition-column information from partParams
+ */
+static void
+ComputePartitionAttrs(Oid relid, List *partParams, AttrNumber *partattrs,
+ List **partexprs, Oid *partopclass)
+{
+ int attn;
+ ListCell *lc;
+
+ attn = 0;
+ foreach(lc, partParams)
+ {
+ PartitionElem *pelem = (PartitionElem *) lfirst(lc);
+ Oid atttype;
+ Oid opclassOid;
+
+ if (pelem->name != NULL)
+ {
+ HeapTuple atttuple;
+ Form_pg_attribute attform;
+
+ atttuple = SearchSysCacheAttName(relid, pelem->name);
+ if (!HeapTupleIsValid(atttuple))
+ {
+ /* difference in error message spellings is historical */
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_COLUMN),
+ errmsg("column \"%s\" named in partition key does not exist",
+ pelem->name)));
+ }
+ attform = (Form_pg_attribute) GETSTRUCT(atttuple);
+
+ if (attform->attnum <= 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_COLUMN),
+ errmsg("cannot use system column \"%s\" in partition key",
+ pelem->name)));
+
+ partattrs[attn] = attform->attnum;
+ atttype = attform->atttypid;
+ ReleaseSysCache(atttuple);
+ }
+ else
+ {
+ /* Partition key expression */
+ Node *expr = pelem->expr;
+
+ Assert(expr != NULL);
+ atttype = exprType(expr);
+
+ if (IsA(expr, CollateExpr))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("cannot use COLLATE in partition key expression")));
+
+ if (IsA(expr, Var) &&
+ ((Var *) expr)->varattno != InvalidAttrNumber)
+ {
+ /*
+ * User wrote "(column)" or "(column COLLATE something)".
+ * Treat it like simple attribute anyway.
+ */
+ partattrs[attn] = ((Var *) expr)->varattno;
+ }
+ else
+ {
+ partattrs[attn] = 0; /* marks expression */
+ *partexprs = lappend(*partexprs, expr);
+
+ /*
+ * transformExpr() should have already rejected subqueries,
+ * aggregates, and window functions, based on the EXPR_KIND_
+ * for a partition key expression.
+ */
+
+ /*
+ * An expression using mutable functions is probably wrong even
+ * even to use in a partition key
+ */
+ expr = (Node *) expression_planner((Expr *) expr);
+
+ if (IsA(expr, Const))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("cannot use a constant expression as partition key")));
+
+ if (contain_mutable_functions(expr))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("functions in partition key expression must be marked IMMUTABLE")));
+ }
+ }
+
+ /*
+ * Identify the opclass to use. At the moment, we use "btree" operators
+ * that seems enough for list and range partitioning.
+ */
+ if (!pelem->opclass)
+ {
+ opclassOid = GetDefaultOpClass(atttype, BTREE_AM_OID);
+
+ if (!OidIsValid(opclassOid))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("data type %s has no default btree operator class",
+ format_type_be(atttype)),
+ errhint("You must specify an existing btree operator class or define one for the type.")));
+ }
+ else
+ opclassOid = GetIndexOpClass(pelem->opclass,
+ atttype,
+ "btree",
+ BTREE_AM_OID);
+
+ partopclass[attn++] = opclassOid;
+ }
+}
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index a21928b..5f4f66f 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2999,6 +2999,7 @@ CopyCreateStmtFields(const CreateStmt *from, CreateStmt *newnode)
COPY_NODE_FIELD(relation);
COPY_NODE_FIELD(tableElts);
COPY_NODE_FIELD(inhRelations);
+ COPY_NODE_FIELD(partby);
COPY_NODE_FIELD(ofTypename);
COPY_NODE_FIELD(constraints);
COPY_NODE_FIELD(options);
@@ -4154,6 +4155,32 @@ _copyAlterPolicyStmt(const AlterPolicyStmt *from)
return newnode;
}
+static PartitionBy *
+_copyPartitionBy(const PartitionBy *from)
+{
+
+ PartitionBy *newnode = makeNode(PartitionBy);
+
+ COPY_SCALAR_FIELD(strategy);
+ COPY_NODE_FIELD(partParams);
+ COPY_LOCATION_FIELD(location);
+
+ return newnode;
+}
+
+static PartitionElem *
+_copyPartitionElem(const PartitionElem *from)
+{
+ PartitionElem *newnode = makeNode(PartitionElem);
+
+ COPY_STRING_FIELD(name);
+ COPY_NODE_FIELD(expr);
+ COPY_NODE_FIELD(opclass);
+ COPY_LOCATION_FIELD(location);
+
+ return newnode;
+}
+
/* ****************************************************************
* pg_list.h copy functions
* ****************************************************************
@@ -5047,6 +5074,12 @@ copyObject(const void *from)
case T_RoleSpec:
retval = _copyRoleSpec(from);
break;
+ case T_PartitionBy:
+ retval = _copyPartitionBy(from);
+ break;
+ case T_PartitionElem:
+ retval = _copyPartitionElem(from);
+ break;
default:
elog(ERROR, "unrecognized node type: %d", (int) nodeTag(from));
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 3c6c567..001a468 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1154,6 +1154,7 @@ _equalCreateStmt(const CreateStmt *a, const CreateStmt *b)
COMPARE_NODE_FIELD(relation);
COMPARE_NODE_FIELD(tableElts);
COMPARE_NODE_FIELD(inhRelations);
+ COMPARE_NODE_FIELD(partby);
COMPARE_NODE_FIELD(ofTypename);
COMPARE_NODE_FIELD(constraints);
COMPARE_NODE_FIELD(options);
@@ -2617,6 +2618,27 @@ _equalRoleSpec(const RoleSpec *a, const RoleSpec *b)
return true;
}
+static bool
+_equalPartitionBy(const PartitionBy *a, const PartitionBy *b)
+{
+ COMPARE_SCALAR_FIELD(strategy);
+ COMPARE_NODE_FIELD(partParams);
+ COMPARE_LOCATION_FIELD(location);
+
+ return true;
+}
+
+static bool
+_equalPartitionElem(const PartitionElem *a, const PartitionElem *b)
+{
+ COMPARE_STRING_FIELD(name);
+ COMPARE_NODE_FIELD(expr);
+ COMPARE_NODE_FIELD(opclass);
+ COMPARE_LOCATION_FIELD(location);
+
+ return true;
+}
+
/*
* Stuff from pg_list.h
*/
@@ -3367,6 +3389,12 @@ equal(const void *a, const void *b)
case T_RoleSpec:
retval = _equalRoleSpec(a, b);
break;
+ case T_PartitionBy:
+ retval = _equalPartitionBy(a, b);
+ break;
+ case T_PartitionElem:
+ retval = _equalPartitionElem(a, b);
+ break;
default:
elog(ERROR, "unrecognized node type: %d",
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 5ac7446..4e70a69 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2367,6 +2367,7 @@ _outCreateStmtInfo(StringInfo str, const CreateStmt *node)
WRITE_NODE_FIELD(relation);
WRITE_NODE_FIELD(tableElts);
WRITE_NODE_FIELD(inhRelations);
+ WRITE_NODE_FIELD(partby);
WRITE_NODE_FIELD(ofTypename);
WRITE_NODE_FIELD(constraints);
WRITE_NODE_FIELD(options);
@@ -3215,6 +3216,26 @@ _outConstraint(StringInfo str, const Constraint *node)
}
}
+static void
+_outPartitionBy(StringInfo str, const PartitionBy *node)
+{
+ WRITE_NODE_TYPE("PARTITIONBY");
+
+ WRITE_CHAR_FIELD(strategy);
+ WRITE_NODE_FIELD(partParams);
+ WRITE_LOCATION_FIELD(location);
+}
+
+static void
+_outPartitionElem(StringInfo str, const PartitionElem *node)
+{
+ WRITE_NODE_TYPE("PARTITIONELEM");
+
+ WRITE_STRING_FIELD(name);
+ WRITE_NODE_FIELD(expr);
+ WRITE_NODE_FIELD(opclass);
+ WRITE_LOCATION_FIELD(location);
+}
/*
* outNode -
@@ -3794,6 +3815,12 @@ outNode(StringInfo str, const void *obj)
case T_XmlSerialize:
_outXmlSerialize(str, obj);
break;
+ case T_PartitionBy:
+ _outPartitionBy(str, obj);
+ break;
+ case T_PartitionElem:
+ _outPartitionElem(str, obj);
+ break;
default:
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 18ec5f0..0607439 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -227,6 +227,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
struct ImportQual *importqual;
InsertStmt *istmt;
VariableSetStmt *vsetstmt;
+ PartitionElem *partelem;
+ PartitionBy *partby;
}
%type <node> stmt schema_stmt
@@ -539,6 +541,9 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
opt_frame_clause frame_extent frame_bound
%type <str> opt_existing_window_name
%type <boolean> opt_if_not_exists
+%type <partby> PartitionBy OptPartitionBy
+%type <partelem> part_elem
+%type <list> part_params
/*
* Non-keyword token types. These are hard-wired into the "flex" lexer.
@@ -603,7 +608,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
KEY
LABEL LANGUAGE LARGE_P LAST_P LATERAL_P
- LEADING LEAKPROOF LEAST LEFT LEVEL LIKE LIMIT LISTEN LOAD LOCAL
+ LEADING LEAKPROOF LEAST LEFT LEVEL LIKE LIMIT LIST LISTEN LOAD LOCAL
LOCALTIME LOCALTIMESTAMP LOCATION LOCK_P LOCKED LOGGED
MAPPING MATCH MATERIALIZED MAXVALUE METHOD MINUTE_P MINVALUE MODE MONTH_P MOVE
@@ -2808,69 +2813,75 @@ copy_generic_opt_arg_list_item:
*****************************************************************************/
CreateStmt: CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
- OptInherit OptWith OnCommitOption OptTableSpace
+ OptInherit OptPartitionBy OptWith OnCommitOption OptTableSpace
{
CreateStmt *n = makeNode(CreateStmt);
$4->relpersistence = $2;
n->relation = $4;
n->tableElts = $6;
n->inhRelations = $8;
+ n->partby = $9;
n->ofTypename = NULL;
n->constraints = NIL;
- n->options = $9;
- n->oncommit = $10;
- n->tablespacename = $11;
+ n->options = $10;
+ n->oncommit = $11;
+ n->tablespacename = $12;
n->if_not_exists = false;
$$ = (Node *)n;
}
| CREATE OptTemp TABLE IF_P NOT EXISTS qualified_name '('
- OptTableElementList ')' OptInherit OptWith OnCommitOption
- OptTableSpace
+ OptTableElementList ')' OptInherit OptPartitionBy OptWith
+ OnCommitOption OptTableSpace
{
CreateStmt *n = makeNode(CreateStmt);
$7->relpersistence = $2;
n->relation = $7;
n->tableElts = $9;
n->inhRelations = $11;
+ n->partby = $12;
n->ofTypename = NULL;
n->constraints = NIL;
- n->options = $12;
- n->oncommit = $13;
- n->tablespacename = $14;
+ n->options = $13;
+ n->oncommit = $14;
+ n->tablespacename = $15;
n->if_not_exists = true;
$$ = (Node *)n;
}
| CREATE OptTemp TABLE qualified_name OF any_name
- OptTypedTableElementList OptWith OnCommitOption OptTableSpace
+ OptTypedTableElementList OptPartitionBy OptWith OnCommitOption
+ OptTableSpace
{
CreateStmt *n = makeNode(CreateStmt);
$4->relpersistence = $2;
n->relation = $4;
n->tableElts = $7;
n->inhRelations = NIL;
+ n->partby = $8;
n->ofTypename = makeTypeNameFromNameList($6);
n->ofTypename->location = @6;
n->constraints = NIL;
- n->options = $8;
- n->oncommit = $9;
- n->tablespacename = $10;
+ n->options = $9;
+ n->oncommit = $10;
+ n->tablespacename = $11;
n->if_not_exists = false;
$$ = (Node *)n;
}
| CREATE OptTemp TABLE IF_P NOT EXISTS qualified_name OF any_name
- OptTypedTableElementList OptWith OnCommitOption OptTableSpace
+ OptTypedTableElementList OptPartitionBy OptWith OnCommitOption
+ OptTableSpace
{
CreateStmt *n = makeNode(CreateStmt);
$7->relpersistence = $2;
n->relation = $7;
n->tableElts = $10;
n->inhRelations = NIL;
+ n->partby = $11;
n->ofTypename = makeTypeNameFromNameList($9);
n->ofTypename->location = @9;
n->constraints = NIL;
- n->options = $11;
- n->oncommit = $12;
- n->tablespacename = $13;
+ n->options = $12;
+ n->oncommit = $13;
+ n->tablespacename = $14;
n->if_not_exists = true;
$$ = (Node *)n;
}
@@ -3415,6 +3426,68 @@ OptInherit: INHERITS '(' qualified_name_list ')' { $$ = $3; }
| /*EMPTY*/ { $$ = NIL; }
;
+/* Optional partition key definition */
+OptPartitionBy: PartitionBy { $$ = $1; }
+ | /*EMPTY*/ { $$ = NULL; }
+ ;
+
+PartitionBy: PARTITION BY RANGE '(' part_params ')'
+ {
+ PartitionBy *n = makeNode(PartitionBy);
+
+ n->strategy = PARTITION_STRAT_RANGE;
+ n->partParams = $5;
+ n->location = @1;
+
+ $$ = n;
+ }
+ | PARTITION BY LIST '(' part_params ')'
+ {
+ PartitionBy *n = makeNode(PartitionBy);
+
+ n->strategy = PARTITION_STRAT_LIST;
+ n->partParams = $5;
+ n->location = @1;
+
+ $$ = n;
+ }
+ ;
+
+part_params: part_elem { $$ = list_make1($1); }
+ | part_params ',' part_elem { $$ = lappend($1, $3); }
+ ;
+
+part_elem: ColId opt_class
+ {
+ PartitionElem *n = makeNode(PartitionElem);
+
+ n->name = $1;
+ n->expr = NULL;
+ n->opclass = $2;
+ n->location = @1;
+ $$ = n;
+ }
+ | func_expr_windowless opt_class
+ {
+ PartitionElem *n = makeNode(PartitionElem);
+
+ n->name = NULL;
+ n->expr = $1;
+ n->opclass = $2;
+ n->location = @1;
+ $$ = n;
+ }
+ | '(' a_expr ')' opt_class
+ {
+ PartitionElem *n = makeNode(PartitionElem);
+
+ n->name = NULL;
+ n->expr = $2;
+ n->opclass = $4;
+ n->location = @1;
+ $$ = n;
+ }
+ ;
/* WITH (options) is preferred, WITH OIDS and WITHOUT OIDS are legacy forms */
OptWith:
WITH reloptions { $$ = $2; }
@@ -13839,6 +13912,7 @@ unreserved_keyword:
| LAST_P
| LEAKPROOF
| LEVEL
+ | LIST
| LISTEN
| LOAD
| LOCAL
diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c
index 91bfe66..b56fd4f 100644
--- a/src/backend/parser/parse_agg.c
+++ b/src/backend/parser/parse_agg.c
@@ -486,6 +486,14 @@ check_agglevels_and_constraints(ParseState *pstate, Node *expr)
err = _("grouping operations are not allowed in trigger WHEN conditions");
break;
+ case EXPR_KIND_PARTITION_KEY:
+ if (isAgg)
+ err = _("aggregate functions are not allowed in partition key expressions");
+ else
+ err = _("grouping operations are not allowed in partition key expressions");
+
+ break;
+
/*
* There is intentionally no default: case here, so that the
@@ -843,6 +851,9 @@ transformWindowFuncCall(ParseState *pstate, WindowFunc *wfunc,
case EXPR_KIND_TRIGGER_WHEN:
err = _("window functions are not allowed in trigger WHEN conditions");
break;
+ case EXPR_KIND_PARTITION_KEY:
+ err = _("window functions are not allowed in partition key expressions");
+ break;
/*
* There is intentionally no default: case here, so that the
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index ebda55d..70a663c 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -1715,6 +1715,9 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
case EXPR_KIND_TRIGGER_WHEN:
err = _("cannot use subquery in trigger WHEN condition");
break;
+ case EXPR_KIND_PARTITION_KEY:
+ err = _("cannot use subquery in partition key expressions");
+ break;
/*
* There is intentionally no default: case here, so that the
@@ -3225,6 +3228,8 @@ ParseExprKindName(ParseExprKind exprKind)
return "EXECUTE";
case EXPR_KIND_TRIGGER_WHEN:
return "WHEN";
+ case EXPR_KIND_PARTITION_KEY:
+ return "partition key expression";
/*
* There is intentionally no default: case here, so that the
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 6528494..e267f74 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -87,6 +87,7 @@ typedef struct
List *alist; /* "after list" of things to do after creating
* the table */
IndexStmt *pkey; /* PRIMARY KEY index, if any */
+ bool ispartitioned; /* true if table is partitioned */
} CreateStmtContext;
/* State shared by transformCreateSchemaStmt and its subroutines */
@@ -229,6 +230,7 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
cxt.blist = NIL;
cxt.alist = NIL;
cxt.pkey = NULL;
+ cxt.ispartitioned = stmt->partby != NULL;
/*
* Notice that we allow OIDs here only for plain tables, even though
@@ -247,6 +249,24 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
if (stmt->ofTypename)
transformOfType(&cxt, stmt->ofTypename);
+ if (stmt->partby)
+ {
+ int partnatts = list_length(stmt->partby->partParams);
+
+ if (partnatts > PARTITION_MAX_KEYS)
+ ereport(ERROR,
+ (errcode(ERRCODE_TOO_MANY_COLUMNS),
+ errmsg("cannot use more than %d columns in partition key",
+ PARTITION_MAX_KEYS)));
+
+ if (stmt->partby->strategy == PARTITION_STRAT_LIST &&
+ partnatts > 1)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("cannot use more than one column in partition key"),
+ errdetail("Only one column allowed with list partitioning.")));
+ }
+
/*
* Run through each primary element in the table creation clause. Separate
* column defs from constraints, and do preliminary analysis. We have to
@@ -582,6 +602,12 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
errmsg("primary key constraints are not supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
+ if (cxt->ispartitioned)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("primary key constraints are not supported on partitioned tables"),
+ parser_errposition(cxt->pstate,
+ constraint->location)));
/* FALL THRU */
case CONSTR_UNIQUE:
@@ -591,6 +617,12 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
errmsg("unique constraints are not supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
+ if (cxt->ispartitioned)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("unique constraints are not supported on partitioned tables"),
+ parser_errposition(cxt->pstate,
+ constraint->location)));
if (constraint->keys == NIL)
constraint->keys = list_make1(makeString(column->colname));
cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
@@ -608,6 +640,12 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
errmsg("foreign key constraints are not supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
+ if (cxt->ispartitioned)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("foreign key constraints are not supported on partitioned tables"),
+ parser_errposition(cxt->pstate,
+ constraint->location)));
/*
* Fill in the current attribute's name and throw it into the
@@ -673,6 +711,12 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
errmsg("primary key constraints are not supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
+ if (cxt->ispartitioned)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("primary key constraints are not supported on partitioned tables"),
+ parser_errposition(cxt->pstate,
+ constraint->location)));
cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
break;
@@ -683,6 +727,12 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
errmsg("unique constraints are not supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
+ if (cxt->ispartitioned)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("unique constraints are not supported on partitioned tables"),
+ parser_errposition(cxt->pstate,
+ constraint->location)));
cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
break;
@@ -693,6 +743,12 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
errmsg("exclusion constraints are not supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
+ if (cxt->ispartitioned)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("exclusion constraints are not supported on partitioned tables"),
+ parser_errposition(cxt->pstate,
+ constraint->location)));
cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
break;
@@ -707,6 +763,12 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
errmsg("foreign key constraints are not supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
+ if (cxt->ispartitioned)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("foreign key constraints are not supported on partitioned tables"),
+ parser_errposition(cxt->pstate,
+ constraint->location)));
cxt->fkconstraints = lappend(cxt->fkconstraints, constraint);
break;
@@ -2515,6 +2577,7 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
cxt.blist = NIL;
cxt.alist = NIL;
cxt.pkey = NULL;
+ cxt.ispartitioned = false;
/*
* The only subtypes that currently require parse transformation handling
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index b064eb4..28024f8 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -42,6 +42,8 @@ extern bool CheckIndexCompatible(Oid oldId,
List *attributeList,
List *exclusionOpNames);
extern Oid GetDefaultOpClass(Oid type_id, Oid am_id);
+extern Oid GetIndexOpClass(List *opclass, Oid attrType,
+ char *accessMethodName, Oid accessMethodId);
/* commands/functioncmds.c */
extern ObjectAddress CreateFunction(CreateFunctionStmt *stmt, const char *queryString);
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index ea65f2e..9e582a5 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -452,6 +452,8 @@ typedef enum NodeTag
T_OnConflictClause,
T_CommonTableExpr,
T_RoleSpec,
+ T_PartitionElem,
+ T_PartitionBy,
/*
* TAGS FOR REPLICATION GRAMMAR PARSE NODES (replnodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 714cf15..4f697b8 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -696,6 +696,40 @@ typedef struct XmlSerialize
int location; /* token location, or -1 if unknown */
} XmlSerialize;
+/* Partitioning related definitions */
+
+/*
+ * PartitionElem - a partition key column
+ *
+ * 'name' Name of the table column included in the key
+ * 'expr' Expression node tree of expressional key column
+ * 'opclass' Operator class name associated with the column
+ */
+typedef struct PartitionElem
+{
+ NodeTag type;
+ char *name; /* name of column to partition on, or NULL */
+ Node *expr; /* expression to partition on, or NULL */
+ List *opclass; /* name of desired opclass; NIL = default */
+ int location; /* token location, or -1 if unknown */
+} PartitionElem;
+
+/*
+ * PartitionBy - partition key definition including the strategy
+ *
+ * 'strategy' partition strategy to use (one of the below defined)
+ * 'partParams' List of PartitionElems, one for each key column
+ */
+#define PARTITION_STRAT_LIST 'l'
+#define PARTITION_STRAT_RANGE 'r'
+
+typedef struct PartitionBy
+{
+ NodeTag type;
+ char strategy;
+ List *partParams;
+ int location; /* token location, or -1 if unknown */
+} PartitionBy;
/****************************************************************************
* Nodes for a Query tree
@@ -1750,6 +1784,7 @@ typedef struct CreateStmt
List *tableElts; /* column definitions (list of ColumnDef) */
List *inhRelations; /* relations to inherit from (list of
* inhRelation) */
+ PartitionBy *partby; /* PARTITION BY clause */
TypeName *ofTypename; /* OF typename */
List *constraints; /* constraints (list of Constraint nodes) */
List *options; /* options from WITH clause */
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 17ffef5..40da67a 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -227,6 +227,7 @@ PG_KEYWORD("left", LEFT, TYPE_FUNC_NAME_KEYWORD)
PG_KEYWORD("level", LEVEL, UNRESERVED_KEYWORD)
PG_KEYWORD("like", LIKE, TYPE_FUNC_NAME_KEYWORD)
PG_KEYWORD("limit", LIMIT, RESERVED_KEYWORD)
+PG_KEYWORD("list", LIST, UNRESERVED_KEYWORD)
PG_KEYWORD("listen", LISTEN, UNRESERVED_KEYWORD)
PG_KEYWORD("load", LOAD, UNRESERVED_KEYWORD)
PG_KEYWORD("local", LOCAL, UNRESERVED_KEYWORD)
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index e3e359c..a13c6fb 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -64,7 +64,8 @@ typedef enum ParseExprKind
EXPR_KIND_ALTER_COL_TRANSFORM, /* transform expr in ALTER COLUMN TYPE */
EXPR_KIND_EXECUTE_PARAMETER, /* parameter value in EXECUTE */
EXPR_KIND_TRIGGER_WHEN, /* WHEN condition in CREATE TRIGGER */
- EXPR_KIND_POLICY /* USING or WITH CHECK expr in policy */
+ EXPR_KIND_POLICY, /* USING or WITH CHECK expr in policy */
+ EXPR_KIND_PARTITION_KEY /* partition key expression */
} ParseExprKind;
diff --git a/src/include/pg_config_manual.h b/src/include/pg_config_manual.h
index 8d5a378..850251b 100644
--- a/src/include/pg_config_manual.h
+++ b/src/include/pg_config_manual.h
@@ -46,6 +46,11 @@
#define INDEX_MAX_KEYS 32
/*
+ * Maximum number of columns in a partition key
+ */
+#define PARTITION_MAX_KEYS 32
+
+/*
* Set the upper and lower bounds of sequence values.
*/
#define SEQ_MAXVALUE PG_INT64_MAX
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 41ceb87..aaae9f9 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -253,3 +253,82 @@ DROP TABLE as_select1;
-- check that the oid column is added before the primary key is checked
CREATE TABLE oid_pk (f1 INT, PRIMARY KEY(oid)) WITH OIDS;
DROP TABLE oid_pk;
+--
+-- partitioned tables
+--
+-- cannot use system columns in partition key
+CREATE TABLE fail_oid_pk (
+ a int
+) PARTITION BY RANGE (oid) WITH OIDS; -- fail
+ERROR: cannot use system column "oid" in partition key
+-- cannot use more than 1 column as partition key for list partitioned table
+CREATE TABLE two_col_tab (
+ a1 int,
+ a2 int
+) PARTITION BY LIST (a1, a2); -- fail
+ERROR: cannot use more than one column in partition key
+DETAIL: Only one column allowed with list partitioning.
+-- cannot use more than 32 columns as partition key for range partitioned table
+CREATE TABLE wide_tab (
+ a01 int, a02 int, a03 int, a04 int, a05 int, a06 int, a07 int, a08 int,
+ a09 int, a10 int, a11 int, a12 int, a13 int, a14 int, a15 int, a16 int,
+ a17 int, a18 int, a19 int, a20 int, a21 int, a22 int, a23 int, a24 int,
+ a25 int, a26 int, a27 int, a28 int, a29 int, a30 int, a31 int, a32 int,
+ a33 int
+) PARTITION BY RANGE (a01, a02, a03, a04, a05, a06, a07, a08,
+ a09, a10, a11, a12, a13, a14, a15, a16,
+ a17, a18, a19, a20, a21, a22, a23, a24,
+ a25, a26, a27, a28, a29, a30, a31, a32, a33); -- fail
+ERROR: cannot use more than 32 columns in partition key
+-- Partitioning requires to use columns with data type having a suitable
+-- btree operator class
+CREATE TABLE point_tab (
+ a point
+) PARTITION BY LIST (a); -- fail
+ERROR: data type point has no default btree operator class
+HINT: You must specify an existing btree operator class or define one for the type.
+CREATE TABLE point_tab (
+ a point
+) PARTITION BY LIST (a USING point_ops); -- fail
+ERROR: operator class "point_ops" does not exist for access method "btree"
+CREATE TABLE point_tab (
+ a point
+) PARTITION BY RANGE (a); -- fail
+ERROR: data type point has no default btree operator class
+HINT: You must specify an existing btree operator class or define one for the type.
+CREATE TABLE point_tab (
+ a point
+) PARTITION BY RANGE (a USING point_ops); -- fail
+ERROR: operator class "point_ops" does not exist for access method "btree"
+-- fail to create a partitioned table with PRIMARY KEY constraint
+CREATE TABLE pk_fail (
+ a int PRIMARY KEY
+) PARTITION BY RANGE (a);
+ERROR: primary key constraints are not supported on partitioned tables
+LINE 2: a int PRIMARY KEY
+ ^
+-- fail to create a partitioned table with foreign key constraint
+CREATE TABLE pk_rel(
+ a int PRIMARY KEY
+);
+CREATE TABLE fk_fail (
+ a int REFERENCES pk_rel(a)
+) PARTITION BY RANGE (a);
+ERROR: foreign key constraints are not supported on partitioned tables
+LINE 2: a int REFERENCES pk_rel(a)
+ ^
+DROP TABLE pk_rel;
+-- fail to create a partitioned table with prohibited expressions in key
+CREATE TABLE fail_const_key (
+ a int
+) PARTITION BY RANGE (('a'));
+ERROR: cannot use a constant expression as partition key
+CREATE TABLE fail_agg_in_key (
+ a int
+) PARTITION BY RANGE ((avg(a)));
+ERROR: aggregate functions are not allowed in partition key expressions
+CREATE TABLE fail_window_fun_in_key (
+ a int,
+ b int
+) PARTITION BY RANGE ((avg(a) OVER (PARTITION BY b)));
+ERROR: window functions are not allowed in partition key expressions
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 78bdc8b..df47656 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -269,3 +269,71 @@ DROP TABLE as_select1;
-- check that the oid column is added before the primary key is checked
CREATE TABLE oid_pk (f1 INT, PRIMARY KEY(oid)) WITH OIDS;
DROP TABLE oid_pk;
+
+--
+-- partitioned tables
+--
+
+-- cannot use system columns in partition key
+CREATE TABLE fail_oid_pk (
+ a int
+) PARTITION BY RANGE (oid) WITH OIDS; -- fail
+
+-- cannot use more than 1 column as partition key for list partitioned table
+CREATE TABLE two_col_tab (
+ a1 int,
+ a2 int
+) PARTITION BY LIST (a1, a2); -- fail
+
+-- cannot use more than 32 columns as partition key for range partitioned table
+CREATE TABLE wide_tab (
+ a01 int, a02 int, a03 int, a04 int, a05 int, a06 int, a07 int, a08 int,
+ a09 int, a10 int, a11 int, a12 int, a13 int, a14 int, a15 int, a16 int,
+ a17 int, a18 int, a19 int, a20 int, a21 int, a22 int, a23 int, a24 int,
+ a25 int, a26 int, a27 int, a28 int, a29 int, a30 int, a31 int, a32 int,
+ a33 int
+) PARTITION BY RANGE (a01, a02, a03, a04, a05, a06, a07, a08,
+ a09, a10, a11, a12, a13, a14, a15, a16,
+ a17, a18, a19, a20, a21, a22, a23, a24,
+ a25, a26, a27, a28, a29, a30, a31, a32, a33); -- fail
+
+-- Partitioning requires to use columns with data type having a suitable
+-- btree operator class
+CREATE TABLE point_tab (
+ a point
+) PARTITION BY LIST (a); -- fail
+CREATE TABLE point_tab (
+ a point
+) PARTITION BY LIST (a USING point_ops); -- fail
+CREATE TABLE point_tab (
+ a point
+) PARTITION BY RANGE (a); -- fail
+CREATE TABLE point_tab (
+ a point
+) PARTITION BY RANGE (a USING point_ops); -- fail
+
+-- fail to create a partitioned table with PRIMARY KEY constraint
+CREATE TABLE pk_fail (
+ a int PRIMARY KEY
+) PARTITION BY RANGE (a);
+
+-- fail to create a partitioned table with foreign key constraint
+CREATE TABLE pk_rel(
+ a int PRIMARY KEY
+);
+CREATE TABLE fk_fail (
+ a int REFERENCES pk_rel(a)
+) PARTITION BY RANGE (a);
+DROP TABLE pk_rel;
+
+-- fail to create a partitioned table with prohibited expressions in key
+CREATE TABLE fail_const_key (
+ a int
+) PARTITION BY RANGE (('a'));
+CREATE TABLE fail_agg_in_key (
+ a int
+) PARTITION BY RANGE ((avg(a)));
+CREATE TABLE fail_window_fun_in_key (
+ a int,
+ b int
+) PARTITION BY RANGE ((avg(a) OVER (PARTITION BY b)));
--
1.7.1
[text/x-diff] 0002-Infrastructure-for-creation-of-partitioned-tables-v2.patch (85.9K, ../../571759D8.7030205@lab.ntt.co.jp/3-0002-Infrastructure-for-creation-of-partitioned-tables-v2.patch)
download | inline diff:
From 3dcec3f88f88aad5b4bd45a2aa6914d2bbb3574e Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Mon, 22 Feb 2016 17:38:55 +0900
Subject: [PATCH 2/5] Infrastructure for creation of partitioned tables.
First, this adds a new system catalog pg_partitioned with:
partedrelid oid
partstrat char
partnatts int16
partattrs int2vector
partclass oidvector
partexprs pg_node_tree
There is a unique index on partedrelid and associated syscache PARTEDRELID.
Second, a new relkind RELKIND_PARTITIONED_REL is added to pg_class. Tables
created with CREATE TABLE PARTITION BY are of this relkind. Some special
properties:
* Has a pg_partitioned entry
* Access to them unconditionally overrides specified inheritance option
* Dropping or altering type of columns that are part of the partition key
is disallowed
* Changing inheritance (ALTER TABLE table INHERIT parent) and linking to
a type (ALTER TABLE table OF type) is disallowed
Partition key is represented using struct PartitionKey and built during the
first relcache build of a partitioned relation. It is stored in rd_partkey
using the cache context.
---
doc/src/sgml/catalogs.sgml | 99 ++++++
src/backend/access/common/reloptions.c | 2 +
src/backend/catalog/Makefile | 4 +-
src/backend/catalog/aclchk.c | 2 +
src/backend/catalog/heap.c | 19 +-
src/backend/catalog/objectaddress.c | 10 +-
src/backend/catalog/partition.c | 451 ++++++++++++++++++++++++++++
src/backend/commands/copy.c | 6 +
src/backend/commands/indexcmds.c | 3 +-
src/backend/commands/lockcmds.c | 2 +-
src/backend/commands/policy.c | 2 +-
src/backend/commands/seclabel.c | 1 +
src/backend/commands/sequence.c | 1 +
src/backend/commands/tablecmds.c | 168 ++++++++++-
src/backend/commands/trigger.c | 7 +-
src/backend/commands/vacuum.c | 11 +-
src/backend/executor/execMain.c | 1 +
src/backend/executor/nodeModifyTable.c | 1 +
src/backend/parser/parse_agg.c | 6 +-
src/backend/parser/parse_clause.c | 8 +
src/backend/parser/parse_expr.c | 2 +-
src/backend/parser/parse_relation.c | 6 +-
src/backend/parser/parse_utilcmd.c | 4 +-
src/backend/rewrite/rewriteDefine.c | 1 +
src/backend/rewrite/rewriteHandler.c | 1 +
src/backend/tcop/utility.c | 5 +-
src/backend/utils/adt/ruleutils.c | 147 +++++++++
src/backend/utils/cache/relcache.c | 14 +-
src/backend/utils/cache/syscache.c | 12 +
src/bin/pg_dump/pg_dump.c | 36 ++-
src/bin/pg_dump/pg_dump.h | 1 +
src/bin/psql/describe.c | 61 +++-
src/bin/psql/tab-complete.c | 6 +-
src/include/catalog/indexing.h | 3 +
src/include/catalog/partition.h | 55 ++++
src/include/catalog/pg_class.h | 1 +
src/include/catalog/pg_partitioned.h | 67 ++++
src/include/catalog/pg_proc.h | 2 +
src/include/commands/defrem.h | 4 +-
src/include/utils/builtins.h | 1 +
src/include/utils/rel.h | 2 +
src/include/utils/syscache.h | 1 +
src/test/regress/expected/create_table.out | 30 ++-
src/test/regress/expected/sanity_check.out | 1 +
src/test/regress/sql/create_table.sql | 21 ++
45 files changed, 1227 insertions(+), 61 deletions(-)
create mode 100644 src/backend/catalog/partition.c
create mode 100644 src/include/catalog/partition.h
create mode 100644 src/include/catalog/pg_partitioned.h
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index d6b60db..ba658b1 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -224,6 +224,11 @@
</row>
<row>
+ <entry><link linkend="catalog-pg-partitioned"><structname>pg_partitioned</structname></link></entry>
+ <entry>information about partitioned tables, including the partition key</entry>
+ </row>
+
+ <row>
<entry><link linkend="catalog-pg-policy"><structname>pg_policy</structname></link></entry>
<entry>row-security policies</entry>
</row>
@@ -4682,6 +4687,100 @@
</sect1>
+ <sect1 id="catalog-pg-partitioned">
+ <title><structname>pg_partitioned</structname></title>
+
+ <indexterm zone="catalog-pg-partitioned">
+ <primary>pg_partitioned</primary>
+ </indexterm>
+
+ <para>
+ The catalog <structname>pg_partitioned</structname> stores information
+ about the partition key of partitioned tables.
+ </para>
+
+ <table>
+ <title><structname>pg_partitioned</> Columns</title>
+
+ <tgroup cols="4">
+ <thead>
+ <row>
+ <entry>Name</entry>
+ <entry>Type</entry>
+ <entry>References</entry>
+ <entry>Description</entry>
+ </row>
+ </thead>
+
+ <tbody>
+
+ <row>
+ <entry><structfield>partedrelid</structfield></entry>
+ <entry><type>oid</type></entry>
+ <entry><literal><link linkend="catalog-pg-class"><structname>pg_class</structname></link>.oid</literal></entry>
+ <entry>The OID of the <structname>pg_class</> entry for this partitioned table</entry>
+ </row>
+
+ <row>
+ <entry><structfield>partstrat</structfield></entry>
+ <entry><type>char</type></entry>
+ <entry></entry>
+ <entry>
+ Partitioning strategy (or method); <literal>l</> = list partitioned table,
+ <literal>r</> = range partitioned table
+ </entry>
+ </row>
+
+ <row>
+ <entry><structfield>partnatts</structfield></entry>
+ <entry><type>int2</type></entry>
+ <entry></entry>
+ <entry>The number of columns in partition key</entry>
+ </row>
+
+ <row>
+ <entry><structfield>partattrs</structfield></entry>
+ <entry><type>int2vector</type></entry>
+ <entry><literal><link linkend="catalog-pg-attribute"><structname>pg_attribute</structname></link>.attnum</literal></entry>
+ <entry>
+ This is an array of <structfield>partnatts</structfield> values that
+ indicate which table columns are used as partition key. For example,
+ a value of <literal>1 3</literal> would mean that the first and the
+ third table columns make up the partition key. A zero in this array
+ indicates that the corresponding partition key column is an expression
+ over the table columns, rather than a simple column reference.
+ </entry>
+ </row>
+
+ <row>
+ <entry><structfield>partclass</structfield></entry>
+ <entry><type>oidvector</type></entry>
+ <entry><literal><link linkend="catalog-pg-opclass"><structname>pg_opclass</structname></link>.oid</literal></entry>
+ <entry>
+ For each column in the partition key, this contains the OID of
+ the operator class to use. See
+ <link linkend="catalog-pg-opclass"><structname>pg_opclass</structname></link> for details.
+ </entry>
+ </row>
+
+ <row>
+ <entry><structfield>partexprs</structfield></entry>
+ <entry><type>pg_node_tree</type></entry>
+ <entry></entry>
+ <entry>
+ Expression trees (in <function>nodeToString()</function>
+ representation) for partition key columns that are not simple column
+ references. This is a list with one element for each zero
+ entry in <structfield>partkey</>. Null if all partition key columns
+ are simple references.
+ </entry>
+ </row>
+
+ </tbody>
+ </tgroup>
+ </table>
+ </sect1>
+
<sect1 id="catalog-pg-policy">
<title><structname>pg_policy</structname></title>
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 797be63..bfccf9b 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -925,6 +925,7 @@ extractRelOptions(HeapTuple tuple, TupleDesc tupdesc,
case RELKIND_RELATION:
case RELKIND_TOASTVALUE:
case RELKIND_MATVIEW:
+ case RELKIND_PARTITIONED_REL:
options = heap_reloptions(classForm->relkind, datum, false);
break;
case RELKIND_VIEW:
@@ -1376,6 +1377,7 @@ heap_reloptions(char relkind, Datum reloptions, bool validate)
return (bytea *) rdopts;
case RELKIND_RELATION:
case RELKIND_MATVIEW:
+ case RELKIND_PARTITIONED_REL:
return default_reloptions(reloptions, validate, RELOPT_KIND_HEAP);
default:
/* other relkinds are not supported */
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index 1ce7610..4dc6300 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -11,7 +11,7 @@ top_builddir = ../../..
include $(top_builddir)/src/Makefile.global
OBJS = catalog.o dependency.o heap.o index.o indexing.o namespace.o aclchk.o \
- objectaccess.o objectaddress.o pg_aggregate.o pg_collation.o \
+ objectaccess.o objectaddress.o partition.o pg_aggregate.o pg_collation.o \
pg_constraint.o pg_conversion.o \
pg_depend.o pg_enum.o pg_inherits.o pg_largeobject.o pg_namespace.o \
pg_operator.o pg_proc.o pg_range.o pg_db_role_setting.o pg_shdepend.o \
@@ -41,7 +41,7 @@ POSTGRES_BKI_SRCS = $(addprefix $(top_srcdir)/src/include/catalog/,\
pg_foreign_data_wrapper.h pg_foreign_server.h pg_user_mapping.h \
pg_foreign_table.h pg_policy.h pg_replication_origin.h \
pg_default_acl.h pg_init_privs.h pg_seclabel.h pg_shseclabel.h \
- pg_collation.h pg_range.h pg_transform.h \
+ pg_collation.h pg_range.h pg_transform.h pg_partitioned.h\
toasting.h indexing.h \
)
diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
index 7d656d5..2070c7f 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -765,6 +765,8 @@ objectsInSchemaToOids(GrantObjectType objtype, List *nspnames)
case ACL_OBJECT_RELATION:
objs = getRelationsInNamespace(namespaceId, RELKIND_RELATION);
objects = list_concat(objects, objs);
+ objs = getRelationsInNamespace(namespaceId, RELKIND_PARTITIONED_REL);
+ objects = list_concat(objects, objs);
objs = getRelationsInNamespace(namespaceId, RELKIND_VIEW);
objects = list_concat(objects, objs);
objs = getRelationsInNamespace(namespaceId, RELKIND_MATVIEW);
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index e997b57..932a48b 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -41,6 +41,7 @@
#include "catalog/heap.h"
#include "catalog/index.h"
#include "catalog/objectaccess.h"
+#include "catalog/partition.h"
#include "catalog/pg_attrdef.h"
#include "catalog/pg_collation.h"
#include "catalog/pg_constraint.h"
@@ -1101,9 +1102,10 @@ heap_create_with_catalog(const char *relname,
{
/* Use binary-upgrade override for pg_class.oid/relfilenode? */
if (IsBinaryUpgrade &&
- (relkind == RELKIND_RELATION || relkind == RELKIND_SEQUENCE ||
- relkind == RELKIND_VIEW || relkind == RELKIND_MATVIEW ||
- relkind == RELKIND_COMPOSITE_TYPE || relkind == RELKIND_FOREIGN_TABLE))
+ (relkind == RELKIND_RELATION || relkind == RELKIND_PARTITIONED_REL ||
+ relkind == RELKIND_SEQUENCE || relkind == RELKIND_VIEW ||
+ relkind == RELKIND_MATVIEW || relkind == RELKIND_COMPOSITE_TYPE ||
+ relkind == RELKIND_FOREIGN_TABLE))
{
if (!OidIsValid(binary_upgrade_next_heap_pg_class_oid))
ereport(ERROR,
@@ -1134,6 +1136,7 @@ heap_create_with_catalog(const char *relname,
switch (relkind)
{
case RELKIND_RELATION:
+ case RELKIND_PARTITIONED_REL:
case RELKIND_VIEW:
case RELKIND_MATVIEW:
case RELKIND_FOREIGN_TABLE:
@@ -1178,6 +1181,7 @@ heap_create_with_catalog(const char *relname,
* such is an implementation detail: toast tables, sequences and indexes.
*/
if (IsUnderPostmaster && (relkind == RELKIND_RELATION ||
+ relkind == RELKIND_PARTITIONED_REL ||
relkind == RELKIND_VIEW ||
relkind == RELKIND_MATVIEW ||
relkind == RELKIND_FOREIGN_TABLE ||
@@ -1353,7 +1357,8 @@ heap_create_with_catalog(const char *relname,
if (relpersistence == RELPERSISTENCE_UNLOGGED)
{
Assert(relkind == RELKIND_RELATION || relkind == RELKIND_MATVIEW ||
- relkind == RELKIND_TOASTVALUE);
+ relkind == RELKIND_TOASTVALUE || relkind == RELKIND_PARTITIONED_REL);
+
heap_create_init_fork(new_rel_desc);
}
@@ -1800,6 +1805,12 @@ heap_drop_with_catalog(Oid relid)
}
/*
+ * If a partitioned table, delete the pg_partitioned tuples.
+ */
+ if (rel->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+ RemovePartitionKeyByRelId(relid);
+
+ /*
* Schedule unlinking of the relation's physical files at commit.
*/
if (rel->rd_rel->relkind != RELKIND_VIEW &&
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index 1324461..215375e 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -1204,7 +1204,8 @@ get_relation_by_qualified_name(ObjectType objtype, List *objname,
RelationGetRelationName(relation))));
break;
case OBJECT_TABLE:
- if (relation->rd_rel->relkind != RELKIND_RELATION)
+ if (relation->rd_rel->relkind != RELKIND_RELATION &&
+ relation->rd_rel->relkind != RELKIND_PARTITIONED_REL)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not a table",
@@ -3252,6 +3253,10 @@ getRelationDescription(StringInfo buffer, Oid relid)
appendStringInfo(buffer, _("table %s"),
relname);
break;
+ case RELKIND_PARTITIONED_REL:
+ appendStringInfo(buffer, _("partitioned table %s"),
+ relname);
+ break;
case RELKIND_INDEX:
appendStringInfo(buffer, _("index %s"),
relname);
@@ -3708,6 +3713,9 @@ getRelationTypeDescription(StringInfo buffer, Oid relid, int32 objectSubId)
case RELKIND_RELATION:
appendStringInfoString(buffer, "table");
break;
+ case RELKIND_PARTITIONED_REL:
+ appendStringInfoString(buffer, "partitioned table");
+ break;
case RELKIND_INDEX:
appendStringInfoString(buffer, "index");
break;
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
new file mode 100644
index 0000000..5e238dc
--- /dev/null
+++ b/src/backend/catalog/partition.c
@@ -0,0 +1,451 @@
+/*-------------------------------------------------------------------------
+ *
+ * partition.c
+ * Partitioning related utility functions.
+ *
+ * Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ * src/backend/utils/misc/partition.c
+ *
+ *-------------------------------------------------------------------------
+*/
+
+#include "postgres.h"
+
+#include "access/heapam.h"
+#include "access/htup_details.h"
+#include "access/nbtree.h"
+#include "catalog/dependency.h"
+#include "catalog/indexing.h"
+#include "catalog/objectaddress.h"
+#include "catalog/partition.h"
+#include "catalog/pg_collation.h"
+#include "catalog/pg_opclass.h"
+#include "catalog/pg_partitioned.h"
+#include "catalog/pg_type.h"
+#include "executor/executor.h"
+#include "miscadmin.h"
+#include "nodes/nodeFuncs.h"
+#include "optimizer/clauses.h"
+#include "optimizer/planmain.h"
+#include "storage/lmgr.h"
+#include "utils/array.h"
+#include "utils/builtins.h"
+#include "utils/datum.h"
+#include "utils/memutils.h"
+#include "utils/fmgroids.h"
+#include "utils/inval.h"
+#include "utils/lsyscache.h"
+#include "utils/syscache.h"
+
+static PartitionKey CopyPartitionKey(PartitionKey fromkey);
+static KeyTypeCollInfo *copy_key_type_coll_info(int nkeycols,
+ KeyTypeCollInfo *tcinfo);
+static void free_key_type_coll_info(KeyTypeCollInfo *tcinfo);
+
+/*
+ * StorePartitionKey
+ * Store the partition keys of rel into pg_partitioned catalog
+ */
+void
+StorePartitionKey(Relation rel,
+ char strategy,
+ int16 partnatts,
+ AttrNumber *partattrs,
+ List *partexprs,
+ Oid *partopclass)
+{
+ int i;
+ int2vector *partattrs_vec;
+ oidvector *partopclass_vec;
+ Datum partexprsDatum;
+ Relation pg_partitioned;
+ HeapTuple tuple;
+ Datum values[Natts_pg_partitioned];
+ bool nulls[Natts_pg_partitioned];
+ ObjectAddress myself;
+ ObjectAddress referenced;
+
+ Assert(rel->rd_rel->relkind == RELKIND_PARTITIONED_REL);
+
+ tuple = SearchSysCache1(PARTEDRELID,
+ ObjectIdGetDatum(RelationGetRelid(rel)));
+ /* Cannot already exist */
+ Assert(!HeapTupleIsValid(tuple));
+
+ /*
+ * Copy the partition key, opclass info into arrays (should we
+ * make the caller pass them like this to start with?)
+ */
+ partattrs_vec = buildint2vector(partattrs, partnatts);
+ partopclass_vec = buildoidvector(partopclass, partnatts);
+
+ /* Convert the partition key expressions (if any) to a text datum */
+ if (partexprs)
+ {
+ char *exprsString;
+
+ exprsString = nodeToString(partexprs);
+ partexprsDatum = CStringGetTextDatum(exprsString);
+ pfree(exprsString);
+ }
+ else
+ partexprsDatum = (Datum) 0;
+
+ pg_partitioned = heap_open(PartitionedRelationId, RowExclusiveLock);
+
+ MemSet(nulls, false, sizeof(nulls));
+
+ /* Only this can ever be NULL */
+ if (!partexprsDatum)
+ nulls[Anum_pg_partitioned_partexprs - 1] = true;
+
+ values[Anum_pg_partitioned_partedrelid - 1] = ObjectIdGetDatum(RelationGetRelid(rel));
+ values[Anum_pg_partitioned_partstrat - 1] = CharGetDatum(strategy);
+ values[Anum_pg_partitioned_partnatts - 1] = Int16GetDatum(partnatts);
+ values[Anum_pg_partitioned_partattrs - 1] = PointerGetDatum(partattrs_vec);
+ values[Anum_pg_partitioned_partclass - 1] = PointerGetDatum(partopclass_vec);
+ values[Anum_pg_partitioned_partexprs - 1] = partexprsDatum;
+
+ tuple = heap_form_tuple(RelationGetDescr(pg_partitioned), values, nulls);
+
+ simple_heap_insert(pg_partitioned, tuple);
+
+ /* Update the indexes on pg_partitioned */
+ CatalogUpdateIndexes(pg_partitioned, tuple);
+
+ /* Make this relation dependent on a few things: */
+ myself.classId = RelationRelationId;
+ myself.objectId = RelationGetRelid(rel);;
+ myself.objectSubId = 0;
+
+ /* Operator class per key column */
+ for (i = 0; i < partnatts; i++)
+ {
+ referenced.classId = OperatorClassRelationId;
+ referenced.objectId = partopclass[i];
+ referenced.objectSubId = 0;
+
+ recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+ }
+
+ /*
+ * Things inside key expressions
+ *
+ * An ugliness: normal dependencies also created on attribute references
+ * as part of the following.
+ */
+ if (partexprs)
+ recordDependencyOnSingleRelExpr(&myself,
+ (Node *) partexprs,
+ RelationGetRelid(rel),
+ DEPENDENCY_NORMAL,
+ DEPENDENCY_NORMAL);
+ /* Tell world about the key */
+ CacheInvalidateRelcache(rel);
+
+ heap_close(pg_partitioned, RowExclusiveLock);
+ heap_freetuple(tuple);
+}
+
+/*
+ * RemovePartitionKeyByRelId
+ * Remove pg_partitioned entry for a relation
+ */
+void
+RemovePartitionKeyByRelId(Oid relid)
+{
+ Relation rel;
+ HeapTuple tuple;
+
+ rel = heap_open(PartitionedRelationId, RowExclusiveLock);
+
+ tuple = SearchSysCache1(PARTEDRELID, ObjectIdGetDatum(relid));
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "cache lookup failed for partition key of relation %u", relid);
+
+ simple_heap_delete(rel, &tuple->t_self);
+
+ /* Update the indexes on pg_partitioned */
+ CatalogUpdateIndexes(rel, tuple);
+
+ ReleaseSysCache(tuple);
+ heap_close(rel, RowExclusiveLock);
+}
+
+/*
+ * RelationBuildPartitionKey
+ * Build and attach to relcache partition key data of relation
+ *
+ * Note that the partition key data attached to a relcache entry must be
+ * stored CacheMemoryContext to ensure it survives as long as the relcache
+ * entry. But we should be running in a less long-lived working context.
+ * To avoid leaking cache memory if this routine fails partway through,
+ * we build in working memory and then copy the completed structure into
+ * cache memory.
+ */
+void
+RelationBuildPartitionKey(Relation relation)
+{
+ Form_pg_partitioned form;
+ Relation catalog;
+ HeapTuple tuple;
+ bool isnull;
+ int i;
+ PartitionKey key;
+ int2vector *partattrs;
+ oidvector *opclass;
+ KeyTypeCollInfo *tcinfo;
+ ListCell *partexprs_item;
+ Datum datum;
+ MemoryContext oldcxt;
+
+ tuple = SearchSysCache1(PARTEDRELID,
+ ObjectIdGetDatum(RelationGetRelid(relation)));
+ /*
+ * Happend when relcache is built before the partition key is stored in
+ * system catalog
+ */
+ if (!HeapTupleIsValid(tuple))
+ return;
+
+ form = (Form_pg_partitioned) GETSTRUCT(tuple);
+
+ /* Allocate in the hopefully short-lived working context */
+ key = (PartitionKey) palloc0(sizeof(PartitionKeyData));
+ key->strategy = form->partstrat;
+ key->partnatts = form->partnatts;
+
+ /* Open the catalog for its tuple descriptor */
+ catalog = heap_open(PartitionedRelationId, AccessShareLock);
+ datum = fastgetattr(tuple, Anum_pg_partitioned_partattrs,
+ RelationGetDescr(catalog),
+ &isnull);
+ Assert(!isnull);
+ partattrs = (int2vector *) DatumGetPointer(datum);
+
+ datum = fastgetattr(tuple, Anum_pg_partitioned_partclass,
+ RelationGetDescr(catalog),
+ &isnull);
+ Assert(!isnull);
+ opclass = (oidvector *) DatumGetPointer(datum);
+
+ datum = heap_getattr(tuple,
+ Anum_pg_partitioned_partexprs,
+ RelationGetDescr(catalog),
+ &isnull);
+
+ if (!isnull)
+ {
+ char *exprsString;
+ Node *exprs;
+
+ exprsString = TextDatumGetCString(datum);
+ exprs = stringToNode(exprsString);
+ pfree(exprsString);
+
+ /*
+ * Run the expressions through eval_const_expressions. This is
+ * not just an optimization, but is necessary, because eventually
+ * the planner will be comparing them to similarly-processed qual
+ * clauses, and may fail to detect valid matches without this.
+ * We don't bother with canonicalize_qual, however.
+ */
+ exprs = eval_const_expressions(NULL, (Node *) exprs);
+
+ /* May as well fix opfuncids too */
+ fix_opfuncids((Node *) exprs);
+ key->partexprs = (List *) exprs;
+ }
+
+ key->partattrs = (AttrNumber *) palloc0(key->partnatts * sizeof(AttrNumber));
+ key->partopfamily = (Oid *) palloc0(key->partnatts * sizeof(Oid));
+ key->partsupfunc = (FmgrInfo *) palloc0(key->partnatts * sizeof(FmgrInfo));
+
+ /* Gather type and collation info as well */
+ key->tcinfo = tcinfo = (KeyTypeCollInfo *) palloc0(sizeof(KeyTypeCollInfo));
+ tcinfo->typid = (Oid *) palloc0(key->partnatts * sizeof(Oid));
+ tcinfo->typmod = (int32 *) palloc0(key->partnatts * sizeof(int32));
+ tcinfo->typlen = (int16 *) palloc0(key->partnatts * sizeof(int16));
+ tcinfo->typbyval = (bool *) palloc0(key->partnatts * sizeof(bool));
+ tcinfo->typalign = (char *) palloc0(key->partnatts * sizeof(char));
+ tcinfo->typcoll = (Oid *) palloc0(key->partnatts * sizeof(Oid));
+
+ /*
+ * Copy partattrs. Further, determine and store the opfamily and
+ * btree support function per attribute.
+ */
+ partexprs_item = list_head(key->partexprs);
+ for (i = 0; i < key->partnatts; i++)
+ {
+ HeapTuple tuple;
+ AttrNumber attno;
+ Form_pg_opclass form;
+ Oid funcid;
+
+ key->partattrs[i] = attno = partattrs->values[i];
+
+ /* Collect type information */
+ if (attno != InvalidAttrNumber)
+ {
+ tcinfo->typid[i] = relation->rd_att->attrs[attno - 1]->atttypid;
+ tcinfo->typmod[i] = relation->rd_att->attrs[attno - 1]->atttypmod;
+ tcinfo->typcoll[i] = relation->rd_att->attrs[attno - 1]->attcollation;
+ }
+ else
+ {
+ tcinfo->typid[i] = exprType(lfirst(partexprs_item));
+ tcinfo->typmod[i] = exprTypmod(lfirst(partexprs_item));
+ tcinfo->typcoll[i] = exprCollation(lfirst(partexprs_item));
+ partexprs_item = lnext(partexprs_item);
+ }
+ get_typlenbyvalalign(tcinfo->typid[i],
+ &tcinfo->typlen[i],
+ &tcinfo->typbyval[i],
+ &tcinfo->typalign[i]);
+
+ tuple = SearchSysCache1(CLAOID, ObjectIdGetDatum(opclass->values[i]));
+
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "cache lookup failed for opclass %u", opclass->values[i]);
+
+ form = (Form_pg_opclass) GETSTRUCT(tuple);
+ key->partopfamily[i] = form->opcfamily;
+
+ /*
+ * Btree support function is good to cover the list and range
+ * partitionig related needs.
+ */
+ funcid = get_opfamily_proc(form->opcfamily,
+ form->opcintype, form->opcintype,
+ BTORDER_PROC);
+
+ fmgr_info(funcid, &key->partsupfunc[i]);
+ ReleaseSysCache(tuple);
+ }
+
+ ReleaseSysCache(tuple);
+ heap_close(catalog, AccessShareLock);
+
+ /* Success --- now copy to the cache memory */
+ oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
+ relation->rd_partkey = CopyPartitionKey(key);
+ MemoryContextSwitchTo(oldcxt);
+
+ FreePartitionKey(key);
+}
+
+/*
+ * CopyPartitionKey
+ *
+ * The copy is allocated in the current memory context.
+ */
+static PartitionKey
+CopyPartitionKey(PartitionKey fromkey)
+{
+ PartitionKey newkey;
+
+ newkey = (PartitionKey) palloc0(sizeof(PartitionKeyData));
+
+ newkey->strategy = fromkey->strategy;
+ newkey->partnatts = fromkey->partnatts;
+
+ newkey->partattrs = (AttrNumber *)
+ palloc(newkey->partnatts * sizeof(AttrNumber));
+ memcpy(newkey->partattrs, fromkey->partattrs,
+ newkey->partnatts * sizeof(AttrNumber));
+
+ newkey->partopfamily = (Oid *) palloc(newkey->partnatts * sizeof(Oid));
+ memcpy(newkey->partopfamily, fromkey->partopfamily,
+ newkey->partnatts * sizeof(Oid));
+
+ newkey->partsupfunc = (FmgrInfo *)
+ palloc(newkey->partnatts * sizeof(FmgrInfo));
+ memcpy(newkey->partsupfunc, fromkey->partsupfunc,
+ newkey->partnatts * sizeof(FmgrInfo));
+
+ newkey->partexprs = copyObject(fromkey->partexprs);
+ newkey->tcinfo = copy_key_type_coll_info(newkey->partnatts,
+ fromkey->tcinfo);
+
+ return newkey;
+}
+
+/*
+ * copy_key_type_coll_info
+ *
+ * The copy is allocated in the current memory context.
+ */
+static KeyTypeCollInfo *
+copy_key_type_coll_info(int nkeycols, KeyTypeCollInfo *tcinfo)
+{
+ KeyTypeCollInfo *result = (KeyTypeCollInfo *)
+ palloc0(sizeof(KeyTypeCollInfo));
+
+ result->typid = (Oid *) palloc0(nkeycols * sizeof(Oid));
+ memcpy(result->typid, tcinfo->typid, nkeycols * sizeof(Oid));
+
+ result->typmod = (int32 *) palloc0(nkeycols * sizeof(int32));
+ memcpy(result->typmod, tcinfo->typmod, nkeycols * sizeof(int32));
+
+ result->typlen = (int16 *) palloc0(nkeycols * sizeof(int16));
+ memcpy(result->typlen, tcinfo->typlen, nkeycols * sizeof(int16));
+
+ result->typbyval = (bool *) palloc0(nkeycols * sizeof(bool));
+ memcpy(result->typbyval, tcinfo->typbyval, nkeycols * sizeof(bool));
+
+ result->typalign = (char *) palloc0(nkeycols * sizeof(bool));
+ memcpy(result->typalign, tcinfo->typalign, nkeycols * sizeof(char));
+
+ result->typcoll = (Oid *) palloc0(nkeycols * sizeof(Oid));
+ memcpy(result->typcoll, tcinfo->typcoll, nkeycols * sizeof(Oid));
+
+ return result;
+}
+
+/*
+ * free_key_type_info
+ */
+static void
+free_key_type_coll_info(KeyTypeCollInfo *tcinfo)
+{
+ Assert(tcinfo != NULL);
+
+ pfree(tcinfo->typid);
+ pfree(tcinfo->typmod);
+ pfree(tcinfo->typlen);
+ pfree(tcinfo->typbyval);
+ pfree(tcinfo->typalign);
+ pfree(tcinfo->typcoll);
+ pfree(tcinfo);
+}
+
+/*
+ * Does relid have a pg_partitioned entry?
+ */
+bool
+relid_is_partitioned(Oid relid)
+{
+ return SearchSysCacheExists1(PARTEDRELID, ObjectIdGetDatum(relid));
+}
+
+/*
+ * FreePartitionKeys
+ */
+void
+FreePartitionKey(PartitionKey key)
+{
+ if (key == NULL)
+ return;
+
+ pfree(key->partattrs);
+ pfree(key->partopfamily);
+ pfree(key->partsupfunc);
+ if (key->partexprs)
+ pfree(key->partexprs);
+ free_key_type_coll_info(key->tcinfo);
+ pfree(key);
+}
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 3201476..403738d 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -1718,6 +1718,12 @@ BeginCopyTo(Relation rel,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot copy from sequence \"%s\"",
RelationGetRelationName(rel))));
+ else if (rel->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot copy from partitioned table \"%s\"",
+ RelationGetRelationName(rel)),
+ errhint("Try the COPY (SELECT ...) TO variant.")));
else
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 4ef6b80..e8d25e8 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -369,7 +369,8 @@ DefineIndex(Oid relationId,
namespaceId = RelationGetNamespace(rel);
if (rel->rd_rel->relkind != RELKIND_RELATION &&
- rel->rd_rel->relkind != RELKIND_MATVIEW)
+ rel->rd_rel->relkind != RELKIND_MATVIEW &&
+ rel->rd_rel->relkind != RELKIND_PARTITIONED_REL)
{
if (rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
diff --git a/src/backend/commands/lockcmds.c b/src/backend/commands/lockcmds.c
index 175d1f3..62671a8 100644
--- a/src/backend/commands/lockcmds.c
+++ b/src/backend/commands/lockcmds.c
@@ -88,7 +88,7 @@ RangeVarCallbackForLockTable(const RangeVar *rv, Oid relid, Oid oldrelid,
* check */
/* Currently, we only allow plain tables to be locked */
- if (relkind != RELKIND_RELATION)
+ if (relkind != RELKIND_RELATION && relkind != RELKIND_PARTITIONED_REL)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not a table",
diff --git a/src/backend/commands/policy.c b/src/backend/commands/policy.c
index 146b36c..7449273 100644
--- a/src/backend/commands/policy.c
+++ b/src/backend/commands/policy.c
@@ -88,7 +88,7 @@ RangeVarCallbackForPolicy(const RangeVar *rv, Oid relid, Oid oldrelid,
rv->relname)));
/* Relation type MUST be a table. */
- if (relkind != RELKIND_RELATION)
+ if (relkind != RELKIND_RELATION && relkind != RELKIND_PARTITIONED_REL)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not a table", rv->relname)));
diff --git a/src/backend/commands/seclabel.c b/src/backend/commands/seclabel.c
index 5bd7e12..8bc8638 100644
--- a/src/backend/commands/seclabel.c
+++ b/src/backend/commands/seclabel.c
@@ -107,6 +107,7 @@ ExecSecLabelStmt(SecLabelStmt *stmt)
* are the only relkinds for which pg_dump will dump labels).
*/
if (relation->rd_rel->relkind != RELKIND_RELATION &&
+ relation->rd_rel->relkind != RELKIND_PARTITIONED_REL &&
relation->rd_rel->relkind != RELKIND_VIEW &&
relation->rd_rel->relkind != RELKIND_MATVIEW &&
relation->rd_rel->relkind != RELKIND_COMPOSITE_TYPE &&
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index f38126f..5ecad82 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -1467,6 +1467,7 @@ process_owned_by(Relation seqrel, List *owned_by)
/* Must be a regular or foreign table */
if (!(tablerel->rd_rel->relkind == RELKIND_RELATION ||
+ tablerel->rd_rel->relkind == RELKIND_PARTITIONED_REL ||
tablerel->rd_rel->relkind == RELKIND_FOREIGN_TABLE))
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index c881ff0..fcae323 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -29,6 +29,7 @@
#include "catalog/indexing.h"
#include "catalog/namespace.h"
#include "catalog/objectaccess.h"
+#include "catalog/partition.h"
#include "catalog/pg_am.h"
#include "catalog/pg_collation.h"
#include "catalog/pg_constraint.h"
@@ -262,6 +263,12 @@ struct DropRelationCallbackState
bool concurrent;
};
+/* for find_attr_reference_walker */
+typedef struct
+{
+ AttrNumber attnum;
+} find_attr_reference_context;
+
/* Alter table target-type flags for ATSimplePermissions */
#define ATT_TABLE 0x0001
#define ATT_VIEW 0x0002
@@ -438,6 +445,9 @@ static void ComputePartitionAttrs(Oid relid, List *partParams,
AttrNumber *partattrs,
List **partexprs,
Oid *partoplass);
+static bool find_attr_reference_walker(Node *node,
+ find_attr_reference_context *context);
+static bool is_partition_attr(Relation rel, AttrNumber attnum, bool *is_expr);
/* ----------------------------------------------------------------
@@ -601,7 +611,8 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
* affect other relkinds, but it would complicate interpretOidsOption().
*/
localHasOids = interpretOidsOption(stmt->options,
- (relkind == RELKIND_RELATION));
+ (relkind == RELKIND_RELATION ||
+ relkind == RELKIND_PARTITIONED_REL));
descriptor->tdhasoid = (localHasOids || parentOidCount > 0);
/*
@@ -715,7 +726,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
AddRelationNewConstraints(rel, rawDefaults, stmt->constraints,
true, true, false);
- /* Process partition key, if any */
+ /* Process and store partition key, if any */
if (stmt->partby)
{
PartitionBy *partby;
@@ -728,6 +739,8 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
partby = transformPartitionBy(rel, stmt->partby);
ComputePartitionAttrs(relationId, stmt->partby->partParams,
partattrs, &partexprs, partopclass);
+ StorePartitionKey(rel, partby->strategy, partnatts, partattrs,
+ partexprs, partopclass);
}
ObjectAddressSet(address, RelationRelationId, relationId);
@@ -975,7 +988,15 @@ RangeVarCallbackForDropRelation(const RangeVar *rel, Oid relOid, Oid oldRelOid,
return; /* concurrently dropped, so nothing to do */
classform = (Form_pg_class) GETSTRUCT(tuple);
- if (classform->relkind != relkind)
+ /*
+ * Both normal and partitioned tables are dropped using DROP TABLE.
+ * RemoveRelations however never passes RELKIND_PARTITIONED_REL as
+ * relkind for OBJECT_TABLE relations. A mismatch below may have
+ * to do with that. So, check.
+ */
+ if (classform->relkind != relkind &&
+ (relkind == RELKIND_RELATION &&
+ classform->relkind != RELKIND_PARTITIONED_REL))
DropErrorMsgWrongType(rel->relname, classform->relkind, relkind);
/* Allow DROP to either table owner or schema owner */
@@ -1313,7 +1334,8 @@ truncate_check_rel(Relation rel)
AclResult aclresult;
/* Only allow truncate on regular tables */
- if (rel->rd_rel->relkind != RELKIND_RELATION)
+ if (rel->rd_rel->relkind != RELKIND_RELATION &&
+ rel->rd_rel->relkind != RELKIND_PARTITIONED_REL)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not a table",
@@ -1541,8 +1563,16 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
*/
relation = heap_openrv(parent, ShareUpdateExclusiveLock);
+ /* Cannot inherit from partitioned tables */
+ if (relation->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot inherit from partitioned table \"%s\"",
+ parent->relname)));
+
if (relation->rd_rel->relkind != RELKIND_RELATION &&
- relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
+ relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE &&
+ relation->rd_rel->relkind != RELKIND_PARTITIONED_REL)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("inherited relation \"%s\" is not a table or foreign table",
@@ -2182,6 +2212,7 @@ renameatt_check(Oid myrelid, Form_pg_class classform, bool recursing)
* restriction.
*/
if (relkind != RELKIND_RELATION &&
+ relkind != RELKIND_PARTITIONED_REL &&
relkind != RELKIND_VIEW &&
relkind != RELKIND_MATVIEW &&
relkind != RELKIND_COMPOSITE_TYPE &&
@@ -4313,6 +4344,7 @@ ATSimplePermissions(Relation rel, int allowed_targets)
switch (rel->rd_rel->relkind)
{
case RELKIND_RELATION:
+ case RELKIND_PARTITIONED_REL:
actual_target = ATT_TABLE;
break;
case RELKIND_VIEW:
@@ -4549,6 +4581,7 @@ find_composite_type_dependencies(Oid typeOid, Relation origRelation,
att = rel->rd_att->attrs[pg_depend->objsubid - 1];
if (rel->rd_rel->relkind == RELKIND_RELATION ||
+ rel->rd_rel->relkind == RELKIND_PARTITIONED_REL ||
rel->rd_rel->relkind == RELKIND_MATVIEW)
{
if (origTypeName)
@@ -5439,6 +5472,7 @@ ATPrepSetStatistics(Relation rel, const char *colName, Node *newValue, LOCKMODE
* allowSystemTableMods to be turned on.
*/
if (rel->rd_rel->relkind != RELKIND_RELATION &&
+ rel->rd_rel->relkind != RELKIND_PARTITIONED_REL &&
rel->rd_rel->relkind != RELKIND_MATVIEW &&
rel->rd_rel->relkind != RELKIND_INDEX &&
rel->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
@@ -5713,6 +5747,73 @@ ATPrepDropColumn(List **wqueue, Relation rel, bool recurse, bool recursing,
cmd->subtype = AT_DropColumnRecurse;
}
+/* Checks if a Var node is for a given attnum */
+static bool
+find_attr_reference_walker(Node *node, find_attr_reference_context *context)
+{
+ if (node == NULL)
+ return false;
+
+ if (IsA(node, Var))
+ {
+ Var *variable = (Var *) node;
+ AttrNumber attnum = variable->varattno;
+
+ if (attnum == context->attnum)
+ return true;
+ }
+
+ return expression_tree_walker(node, find_attr_reference_walker, context);
+}
+
+/*
+ * Checks if attnum is a partition attribute for rel
+ *
+ * Sets *is_expr if attnum is found to be referenced in some partition key
+ * expression.
+ */
+static bool
+is_partition_attr(Relation rel, AttrNumber attnum, bool *is_expr)
+{
+ PartitionKey key;
+ ListCell *partexpr_item;
+ int i;
+
+ if (rel->rd_rel->relkind != RELKIND_PARTITIONED_REL)
+ {
+ *is_expr = false;
+ return false;
+ }
+
+ key = rel->rd_partkey;
+
+ partexpr_item = list_head(key->partexprs);
+ for (i = 0; i < key->partnatts; i++)
+ {
+ AttrNumber partatt = key->partattrs[i];
+
+ if(partatt != 0)
+ {
+ *is_expr = false;
+ if (attnum == partatt)
+ return true;
+ }
+ else
+ {
+ find_attr_reference_context context;
+
+ *is_expr = true;
+ context.attnum = attnum;
+ if (find_attr_reference_walker(lfirst(partexpr_item), &context))
+ return true;
+
+ partexpr_item = lnext(partexpr_item);
+ }
+ }
+
+ return false;
+}
+
/*
* Return value is the address of the dropped column.
*/
@@ -5727,6 +5828,7 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
AttrNumber attnum;
List *children;
ObjectAddress object;
+ bool is_expr;
/* At top level, permission check was done in ATPrepCmd, else do it */
if (recursing)
@@ -5771,6 +5873,19 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
errmsg("cannot drop inherited column \"%s\"",
colName)));
+ /* Don't drop columns used in partition key */
+ if (is_partition_attr(rel, attnum, &is_expr))
+ {
+ if (!is_expr)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("cannot drop column named in partition key")));
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("cannot drop column referenced in partition key expression")));
+ }
+
ReleaseSysCache(tuple);
/*
@@ -6289,6 +6404,12 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
* Validity checks (permission checks wait till we have the column
* numbers)
*/
+ if (pkrel->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot reference relation \"%s\"", RelationGetRelationName(pkrel)),
+ errdetail("Referencing partitioned tables in foreign key constraints is not supported.")));
+
if (pkrel->rd_rel->relkind != RELKIND_RELATION)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
@@ -7883,6 +8004,7 @@ ATPrepAlterColumnType(List **wqueue,
NewColumnValue *newval;
ParseState *pstate = make_parsestate(NULL);
AclResult aclresult;
+ bool is_expr;
if (rel->rd_rel->reloftype && !recursing)
ereport(ERROR,
@@ -7913,6 +8035,19 @@ ATPrepAlterColumnType(List **wqueue,
errmsg("cannot alter inherited column \"%s\"",
colName)));
+ /* Don't alter columns used in partition key */
+ if (is_partition_attr(rel, attnum, &is_expr))
+ {
+ if (!is_expr)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("cannot alter column named in partition key")));
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("cannot alter column referenced in partition key expression")));
+ }
+
/* Look up the target type */
typenameTypeIdAndMod(NULL, typeName, &targettype, &targettypmod);
@@ -7928,7 +8063,8 @@ ATPrepAlterColumnType(List **wqueue,
list_make1_oid(rel->rd_rel->reltype),
false);
- if (tab->relkind == RELKIND_RELATION)
+ if (tab->relkind == RELKIND_RELATION ||
+ tab->relkind == RELKIND_PARTITIONED_REL)
{
/*
* Set up an expression to transform the old data value to the new
@@ -8955,6 +9091,7 @@ ATExecChangeOwner(Oid relationOid, Oid newOwnerId, bool recursing, LOCKMODE lock
switch (tuple_class->relkind)
{
case RELKIND_RELATION:
+ case RELKIND_PARTITIONED_REL:
case RELKIND_VIEW:
case RELKIND_MATVIEW:
case RELKIND_FOREIGN_TABLE:
@@ -9417,6 +9554,7 @@ ATExecSetRelOptions(Relation rel, List *defList, AlterTableType operation,
switch (rel->rd_rel->relkind)
{
case RELKIND_RELATION:
+ case RELKIND_PARTITIONED_REL:
case RELKIND_TOASTVALUE:
case RELKIND_MATVIEW:
(void) heap_reloptions(rel->rd_rel->relkind, newOptions, true);
@@ -9839,7 +9977,8 @@ AlterTableMoveAll(AlterTableMoveAllStmt *stmt)
/* Only move the object type requested */
if ((stmt->objtype == OBJECT_TABLE &&
- relForm->relkind != RELKIND_RELATION) ||
+ relForm->relkind != RELKIND_RELATION &&
+ relForm->relkind != RELKIND_PARTITIONED_REL) ||
(stmt->objtype == OBJECT_INDEX &&
relForm->relkind != RELKIND_INDEX) ||
(stmt->objtype == OBJECT_MATVIEW &&
@@ -10038,6 +10177,11 @@ ATPrepAddInherit(Relation child_rel)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot change inheritance of typed table")));
+
+ if (child_rel->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot change inheritance of partitioned table")));
}
/*
@@ -10089,6 +10233,12 @@ ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode)
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot inherit to temporary relation of another session")));
+ /* Prevent partitioned tables from becoming inheritance parents */
+ if (parent_rel->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot inherit from partitioned table \"%s\"", parent->relname)));
+
/*
* Check for duplicates in the list of parents, and determine the highest
* inhseqno already present; we'll use the next one for the new parent.
@@ -11467,6 +11617,7 @@ AlterTableNamespaceInternal(Relation rel, Oid oldNspOid, Oid nspOid,
/* Fix other dependent stuff */
if (rel->rd_rel->relkind == RELKIND_RELATION ||
+ rel->rd_rel->relkind == RELKIND_PARTITIONED_REL ||
rel->rd_rel->relkind == RELKIND_MATVIEW)
{
AlterIndexNamespaces(classRel, rel, oldNspOid, nspOid, objsMoved);
@@ -11916,7 +12067,7 @@ RangeVarCallbackOwnsTable(const RangeVar *relation,
if (!relkind)
return;
if (relkind != RELKIND_RELATION && relkind != RELKIND_TOASTVALUE &&
- relkind != RELKIND_MATVIEW)
+ relkind != RELKIND_MATVIEW && relkind != RELKIND_PARTITIONED_REL)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not a table or materialized view", relation->relname)));
@@ -12070,6 +12221,7 @@ RangeVarCallbackForAlterRelation(const RangeVar *rv, Oid relid, Oid oldrelid,
*/
if (IsA(stmt, AlterObjectSchemaStmt) &&
relkind != RELKIND_RELATION &&
+ relkind != RELKIND_PARTITIONED_REL &&
relkind != RELKIND_VIEW &&
relkind != RELKIND_MATVIEW &&
relkind != RELKIND_SEQUENCE &&
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index 33107e0..a02db8b 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -174,7 +174,8 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
* Triggers must be on tables or views, and there are additional
* relation-type-specific restrictions.
*/
- if (rel->rd_rel->relkind == RELKIND_RELATION)
+ if (rel->rd_rel->relkind == RELKIND_RELATION ||
+ rel->rd_rel->relkind == RELKIND_PARTITIONED_REL)
{
/* Tables can't have INSTEAD OF triggers */
if (stmt->timing != TRIGGER_TYPE_BEFORE &&
@@ -1112,6 +1113,7 @@ RemoveTriggerById(Oid trigOid)
rel = heap_open(relid, AccessExclusiveLock);
if (rel->rd_rel->relkind != RELKIND_RELATION &&
+ rel->rd_rel->relkind != RELKIND_PARTITIONED_REL &&
rel->rd_rel->relkind != RELKIND_VIEW &&
rel->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
ereport(ERROR,
@@ -1218,7 +1220,8 @@ RangeVarCallbackForRenameTrigger(const RangeVar *rv, Oid relid, Oid oldrelid,
/* only tables and views can have triggers */
if (form->relkind != RELKIND_RELATION && form->relkind != RELKIND_VIEW &&
- form->relkind != RELKIND_FOREIGN_TABLE)
+ form->relkind != RELKIND_FOREIGN_TABLE &&
+ form->relkind != RELKIND_PARTITIONED_REL)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not a table, view, or foreign table",
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 93361a0..003a30f 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -1301,9 +1301,14 @@ vacuum_rel(Oid relid, RangeVar *relation, int options, VacuumParams *params)
onerel->rd_rel->relkind != RELKIND_MATVIEW &&
onerel->rd_rel->relkind != RELKIND_TOASTVALUE)
{
- ereport(WARNING,
- (errmsg("skipping \"%s\" --- cannot vacuum non-tables or special system tables",
- RelationGetRelationName(onerel))));
+ if (onerel->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+ ereport(WARNING,
+ (errmsg("skipping \"%s\" --- cannot vacuum partitioned tables",
+ RelationGetRelationName(onerel))));
+ else
+ ereport(WARNING,
+ (errmsg("skipping \"%s\" --- cannot vacuum non-tables or special system tables",
+ RelationGetRelationName(onerel))));
relation_close(onerel, lmode);
PopActiveSnapshot();
CommitTransactionCommand();
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index ac02304..67492a5 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -1019,6 +1019,7 @@ CheckValidResultRel(Relation resultRel, CmdType operation)
switch (resultRel->rd_rel->relkind)
{
case RELKIND_RELATION:
+ case RELKIND_PARTITIONED_REL:
/* OK */
break;
case RELKIND_SEQUENCE:
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index e62c8aa..7ad1140 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1871,6 +1871,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
relkind = resultRelInfo->ri_RelationDesc->rd_rel->relkind;
if (relkind == RELKIND_RELATION ||
+ relkind == RELKIND_PARTITIONED_REL ||
relkind == RELKIND_MATVIEW)
{
j->jf_junkAttNo = ExecFindJunkAttribute(j, "ctid");
diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c
index b56fd4f..77ffd84 100644
--- a/src/backend/parser/parse_agg.c
+++ b/src/backend/parser/parse_agg.c
@@ -488,9 +488,9 @@ check_agglevels_and_constraints(ParseState *pstate, Node *expr)
break;
case EXPR_KIND_PARTITION_KEY:
if (isAgg)
- err = _("aggregate functions are not allowed in partition key expressions");
+ err = _("aggregate functions are not allowed in partition key expression");
else
- err = _("grouping operations are not allowed in partition key expressions");
+ err = _("grouping operations are not allowed in partition key expression");
break;
@@ -852,7 +852,7 @@ transformWindowFuncCall(ParseState *pstate, WindowFunc *wfunc,
err = _("window functions are not allowed in trigger WHEN conditions");
break;
case EXPR_KIND_PARTITION_KEY:
- err = _("window functions are not allowed in partition key expressions");
+ err = _("window functions are not allowed in partition key expression");
break;
/*
diff --git a/src/backend/parser/parse_clause.c b/src/backend/parser/parse_clause.c
index 751de4b..307e383 100644
--- a/src/backend/parser/parse_clause.c
+++ b/src/backend/parser/parse_clause.c
@@ -21,6 +21,7 @@
#include "access/tsmapi.h"
#include "catalog/catalog.h"
#include "catalog/heap.h"
+#include "catalog/partition.h"
#include "catalog/pg_am.h"
#include "catalog/pg_constraint_fn.h"
#include "catalog/pg_type.h"
@@ -200,6 +201,13 @@ setTargetTable(ParseState *pstate, RangeVar *relation,
relation->alias, inh, false);
pstate->p_target_rangetblentry = rte;
+ /*
+ * Override specified inheritance option, if relation is a partitioned
+ * table and it's a target of INSERT (ie, alsoSource == false).
+ */
+ if (alsoSource)
+ rte->inh |= relid_is_partitioned(rte->relid);
+
/* assume new rte is at end */
rtindex = list_length(pstate->p_rtable);
Assert(rte == rt_fetch(rtindex, pstate->p_rtable));
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 70a663c..6a74c28 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -1716,7 +1716,7 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
err = _("cannot use subquery in trigger WHEN condition");
break;
case EXPR_KIND_PARTITION_KEY:
- err = _("cannot use subquery in partition key expressions");
+ err = _("cannot use subquery in partition key expression");
break;
/*
diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c
index 81332b5..abcc1d6 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -20,6 +20,7 @@
#include "access/sysattr.h"
#include "catalog/heap.h"
#include "catalog/namespace.h"
+#include "catalog/partition.h"
#include "catalog/pg_type.h"
#include "funcapi.h"
#include "nodes/makefuncs.h"
@@ -1216,9 +1217,12 @@ addRangeTableEntry(ParseState *pstate,
*
* The initial default on access checks is always check-for-READ-access,
* which is the right thing for all except target tables.
+ *
+ * Note: we override the specified inheritance option, if relation is a
+ * partitioned table.
*/
rte->lateral = false;
- rte->inh = inh;
+ rte->inh = inh || relid_is_partitioned(RelationGetRelid(rel));
rte->inFromCl = inFromCl;
rte->requiredPerms = ACL_SELECT;
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e267f74..465597d 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -33,6 +33,7 @@
#include "catalog/heap.h"
#include "catalog/index.h"
#include "catalog/namespace.h"
+#include "catalog/partition.h"
#include "catalog/pg_am.h"
#include "catalog/pg_collation.h"
#include "catalog/pg_constraint.h"
@@ -821,6 +822,7 @@ transformTableLikeClause(CreateStmtContext *cxt, TableLikeClause *table_like_cla
relation = relation_openrv(table_like_clause->relation, AccessShareLock);
if (relation->rd_rel->relkind != RELKIND_RELATION &&
+ relation->rd_rel->relkind != RELKIND_PARTITIONED_REL &&
relation->rd_rel->relkind != RELKIND_VIEW &&
relation->rd_rel->relkind != RELKIND_MATVIEW &&
relation->rd_rel->relkind != RELKIND_COMPOSITE_TYPE &&
@@ -2577,7 +2579,7 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
cxt.blist = NIL;
cxt.alist = NIL;
cxt.pkey = NULL;
- cxt.ispartitioned = false;
+ cxt.ispartitioned = relid_is_partitioned(relid);
/*
* The only subtypes that currently require parse transformation handling
diff --git a/src/backend/rewrite/rewriteDefine.c b/src/backend/rewrite/rewriteDefine.c
index 96f3f37..db80c90 100644
--- a/src/backend/rewrite/rewriteDefine.c
+++ b/src/backend/rewrite/rewriteDefine.c
@@ -260,6 +260,7 @@ DefineQueryRewrite(char *rulename,
* blocks them for users. Don't mention them in the error message.
*/
if (event_relation->rd_rel->relkind != RELKIND_RELATION &&
+ event_relation->rd_rel->relkind != RELKIND_PARTITIONED_REL &&
event_relation->rd_rel->relkind != RELKIND_MATVIEW &&
event_relation->rd_rel->relkind != RELKIND_VIEW)
ereport(ERROR,
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index a22a11e..f429dd6 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -1222,6 +1222,7 @@ rewriteTargetListUD(Query *parsetree, RangeTblEntry *target_rte,
TargetEntry *tle;
if (target_relation->rd_rel->relkind == RELKIND_RELATION ||
+ target_relation->rd_rel->relkind == RELKIND_PARTITIONED_REL ||
target_relation->rd_rel->relkind == RELKIND_MATVIEW)
{
/*
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index ac50c2a..220cbfc 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -975,10 +975,13 @@ ProcessUtilitySlow(Node *parsetree,
{
Datum toast_options;
static char *validnsps[] = HEAP_RELOPT_NAMESPACES;
+ char relkind = ((CreateStmt *) stmt)->partby != NULL
+ ? RELKIND_PARTITIONED_REL
+ : RELKIND_RELATION;
/* Create the table itself */
address = DefineRelation((CreateStmt *) stmt,
- RELKIND_RELATION,
+ relkind,
InvalidOid, NULL);
EventTriggerCollectSimpleCommand(address,
secondaryObject,
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 2b47e95..49f6fad 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -33,6 +33,7 @@
#include "catalog/pg_language.h"
#include "catalog/pg_opclass.h"
#include "catalog/pg_operator.h"
+#include "catalog/pg_partitioned.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_trigger.h"
#include "catalog/pg_type.h"
@@ -315,6 +316,7 @@ static char *pg_get_indexdef_worker(Oid indexrelid, int colno,
const Oid *excludeOps,
bool attrsOnly, bool showTblSpc,
int prettyFlags);
+static char *pg_get_partkeydef_worker(Oid relId, int prettyFlags);
static char *pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
int prettyFlags);
static text *pg_get_expr_worker(text *expr, Oid relid, const char *relname,
@@ -1289,6 +1291,151 @@ pg_get_indexdef_worker(Oid indexrelid, int colno,
return buf.data;
}
+/*
+ * pg_get_partkeydef
+ *
+ * Returns the partition key specification, ie, the following:
+ *
+ * PARTITION BY { RANGE | LIST } (column [USING opclass_name] [, ...])
+ */
+Datum
+pg_get_partkeydef(PG_FUNCTION_ARGS)
+{
+ Oid relationId = PG_GETARG_OID(0);
+ int prettyFlags;
+
+ prettyFlags = PRETTYFLAG_INDENT;
+ PG_RETURN_TEXT_P(string_to_text(pg_get_partkeydef_worker(relationId,
+ prettyFlags)));
+}
+
+/*
+ * Internal workhorse to decompile a partition key definition.
+ */
+static char *
+pg_get_partkeydef_worker(Oid relId, int prettyFlags)
+{
+ Form_pg_partitioned form;
+ HeapTuple tuple;
+ oidvector *partclass;
+ List *partexprs;
+ ListCell *partexpr_item;
+ List *context;
+ Datum datum;
+ bool isnull;
+ StringInfoData buf;
+ int keyno;
+ char *str;
+ char *sep;
+
+ tuple = SearchSysCache1(PARTEDRELID, ObjectIdGetDatum(relId));
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "cache lookup failed for partition key of %u", relId);
+
+ form = (Form_pg_partitioned) GETSTRUCT(tuple);
+
+ Assert(form->partedrelid == relId);
+
+ /* Must get partclass, and partexprs the hard way */
+ datum = SysCacheGetAttr(PARTEDRELID, tuple,
+ Anum_pg_partitioned_partclass, &isnull);
+ Assert(!isnull);
+ partclass = (oidvector *) DatumGetPointer(datum);
+
+ /*
+ * Get the partition key expressions, if any. (NOTE: we do not use the
+ * relcache versions of the expressions, because we want to display
+ * non-const-folded expressions.)
+ */
+ if (!heap_attisnull(tuple, Anum_pg_partitioned_partexprs))
+ {
+ Datum exprsDatum;
+ bool isnull;
+ char *exprsString;
+
+ exprsDatum = SysCacheGetAttr(PARTEDRELID, tuple,
+ Anum_pg_partitioned_partexprs, &isnull);
+ Assert(!isnull);
+ exprsString = TextDatumGetCString(exprsDatum);
+ partexprs = (List *) stringToNode(exprsString);
+ pfree(exprsString);
+ }
+ else
+ partexprs = NIL;
+
+ partexpr_item = list_head(partexprs);
+ context = deparse_context_for(get_relation_name(relId), relId);
+
+ /*
+ * Start the partition key definition.
+ */
+ initStringInfo(&buf);
+
+ appendStringInfo(&buf, "PARTITION BY ");
+
+ switch (form->partstrat)
+ {
+ case 'l':
+ appendStringInfo(&buf, "LIST");
+ break;
+ case 'r':
+ appendStringInfo(&buf, "RANGE");
+ break;
+ }
+
+ /*
+ * Report the partition key columns
+ */
+ appendStringInfo(&buf, " (");
+ sep = "";
+ for (keyno = 0; keyno < form->partnatts; keyno++)
+ {
+ AttrNumber attnum = form->partattrs.values[keyno];
+ Oid keycoltype;
+
+ appendStringInfoString(&buf, sep);
+ sep = ", ";
+ if (attnum != 0)
+ {
+ /* Simple partition key column */
+ char *attname;
+
+ attname = get_relid_attribute_name(relId, attnum);
+ appendStringInfoString(&buf, quote_identifier(attname));
+ keycoltype = get_atttype(relId, attnum);
+ }
+ else
+ {
+ /* partition key expression */
+ Node *partkey;
+
+ if (partexpr_item == NULL)
+ elog(ERROR, "too few entries in partexprs list");
+ partkey = (Node *) lfirst(partexpr_item);
+ partexpr_item = lnext(partexpr_item);
+ /* Deparse */
+ str = deparse_expression_pretty(partkey, context, false, false,
+ prettyFlags, 0);
+
+ /* Need parens if it's not a bare function call */
+ if (partkey && IsA(partkey, FuncExpr) &&
+ ((FuncExpr *) partkey)->funcformat == COERCE_EXPLICIT_CALL)
+ appendStringInfoString(&buf, str);
+ else
+ appendStringInfo(&buf, "(%s)", str);
+ keycoltype = exprType(partkey);
+ }
+
+ /* Add the operator class name, if not default */
+ get_opclass_name(partclass->values[keyno], keycoltype, &buf);
+ }
+ appendStringInfoChar(&buf, ')');
+
+ /* Clean up */
+ ReleaseSysCache(tuple);
+
+ return buf.data;
+}
/*
* pg_get_constraintdef
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 432feef..2841745 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -40,6 +40,7 @@
#include "catalog/index.h"
#include "catalog/indexing.h"
#include "catalog/namespace.h"
+#include "catalog/partition.h"
#include "catalog/pg_am.h"
#include "catalog/pg_amproc.h"
#include "catalog/pg_attrdef.h"
@@ -431,6 +432,7 @@ RelationParseRelOptions(Relation relation, HeapTuple tuple)
switch (relation->rd_rel->relkind)
{
case RELKIND_RELATION:
+ case RELKIND_PARTITIONED_REL:
case RELKIND_TOASTVALUE:
case RELKIND_INDEX:
case RELKIND_VIEW:
@@ -1049,6 +1051,12 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
else
relation->rd_rsdesc = NULL;
+ /* if it's a partitioned table, initialize key info */
+ if (relation->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+ RelationBuildPartitionKey(relation);
+ else
+ relation->rd_partkey = NULL;
+
/*
* if it's an index, initialize index-related information
*/
@@ -2046,6 +2054,7 @@ RelationDestroyRelation(Relation relation, bool remember_tupdesc)
MemoryContextDelete(relation->rd_rulescxt);
if (relation->rd_rsdesc)
MemoryContextDelete(relation->rd_rsdesc->rscxt);
+ FreePartitionKey(relation->rd_partkey);
if (relation->rd_fdwroutine)
pfree(relation->rd_fdwroutine);
pfree(relation);
@@ -2987,7 +2996,9 @@ RelationBuildLocalRelation(const char *relname,
/* system relations and non-table objects don't have one */
if (!IsSystemNamespace(relnamespace) &&
- (relkind == RELKIND_RELATION || relkind == RELKIND_MATVIEW))
+ (relkind == RELKIND_RELATION ||
+ relkind == RELKIND_PARTITIONED_REL ||
+ relkind == RELKIND_MATVIEW))
rel->rd_rel->relreplident = REPLICA_IDENTITY_DEFAULT;
else
rel->rd_rel->relreplident = REPLICA_IDENTITY_NOTHING;
@@ -4972,6 +4983,7 @@ load_relcache_init_file(bool shared)
rel->rd_rulescxt = NULL;
rel->trigdesc = NULL;
rel->rd_rsdesc = NULL;
+ rel->rd_partkey = NULL;
rel->rd_indexprs = NIL;
rel->rd_indpred = NIL;
rel->rd_exclops = NULL;
diff --git a/src/backend/utils/cache/syscache.c b/src/backend/utils/cache/syscache.c
index 65ffe84..a6563fe 100644
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -48,6 +48,7 @@
#include "catalog/pg_opclass.h"
#include "catalog/pg_operator.h"
#include "catalog/pg_opfamily.h"
+#include "catalog/pg_partitioned.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_range.h"
#include "catalog/pg_rewrite.h"
@@ -568,6 +569,17 @@ static const struct cachedesc cacheinfo[] = {
},
8
},
+ {PartitionedRelationId, /* PARTEDRELID */
+ PartitionedRelidIndexId,
+ 1,
+ {
+ Anum_pg_partitioned_partedrelid,
+ 0,
+ 0,
+ 0
+ },
+ 128
+ },
{ProcedureRelationId, /* PROCNAMEARGSNSP */
ProcedureNameArgsNspIndexId,
3,
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index e1e5bee..4c95aca 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -1234,9 +1234,10 @@ expand_table_name_patterns(Archive *fout,
"SELECT c.oid"
"\nFROM pg_catalog.pg_class c"
"\n LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace"
- "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c')\n",
+ "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c')\n",
RELKIND_RELATION, RELKIND_SEQUENCE, RELKIND_VIEW,
- RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+ RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+ RELKIND_PARTITIONED_REL);
processSQLNamePattern(GetConnection(fout), query, cell->val, true,
false, "n.nspname", "c.relname", NULL,
"pg_catalog.pg_table_is_visible(c.oid)");
@@ -2064,6 +2065,9 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo, bool oids)
/* Skip FOREIGN TABLEs (no data to dump) */
if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
return;
+ /* Skip partitioned tables (data in partitions) */
+ if (tbinfo->relkind == RELKIND_PARTITIONED_REL)
+ return;
/* Don't dump data in unlogged tables, if so requested */
if (tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED &&
@@ -5131,6 +5135,7 @@ getTables(Archive *fout, int *numTables)
int i_toastreloptions;
int i_reloftype;
int i_relpages;
+ int i_partkeydef;
/* Make sure we are in proper schema */
selectSourceSchema(fout, "pg_catalog");
@@ -5196,7 +5201,8 @@ getTables(Archive *fout, int *numTables)
"array_remove(array_remove(c.reloptions,'check_option=local'),'check_option=cascaded') AS reloptions, "
"CASE WHEN 'check_option=local' = ANY (c.reloptions) THEN 'LOCAL'::text "
"WHEN 'check_option=cascaded' = ANY (c.reloptions) THEN 'CASCADED'::text ELSE NULL END AS checkoption, "
- "tc.reloptions AS toast_reloptions "
+ "tc.reloptions AS toast_reloptions, "
+ "CASE WHEN c.relkind = 'P' THEN pg_catalog.pg_get_partkeydef(c.oid) ELSE NULL END AS partkeydef "
"FROM pg_class c "
"LEFT JOIN pg_depend d ON "
"(c.relkind = '%c' AND "
@@ -5208,7 +5214,7 @@ getTables(Archive *fout, int *numTables)
"(c.oid = pip.objoid AND pip.classoid = "
"(SELECT oid FROM pg_class "
"WHERE relname = 'pg_class') AND pip.objsubid = 0) "
- "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c') "
+ "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c', '%c') "
"ORDER BY c.oid",
acl_subquery->data,
racl_subquery->data,
@@ -5218,7 +5224,8 @@ getTables(Archive *fout, int *numTables)
RELKIND_SEQUENCE,
RELKIND_RELATION, RELKIND_SEQUENCE,
RELKIND_VIEW, RELKIND_COMPOSITE_TYPE,
- RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+ RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+ RELKIND_PARTITIONED_REL);
destroyPQExpBuffer(acl_subquery);
destroyPQExpBuffer(racl_subquery);
@@ -5762,6 +5769,7 @@ getTables(Archive *fout, int *numTables)
i_checkoption = PQfnumber(res, "checkoption");
i_toastreloptions = PQfnumber(res, "toast_reloptions");
i_reloftype = PQfnumber(res, "reloftype");
+ i_partkeydef = PQfnumber(res, "partkeydef");
if (dopt->lockWaitTimeout && fout->remoteVersion >= 70300)
{
@@ -5832,6 +5840,7 @@ getTables(Archive *fout, int *numTables)
else
tblinfo[i].checkoption = pg_strdup(PQgetvalue(res, i, i_checkoption));
tblinfo[i].toast_reloptions = pg_strdup(PQgetvalue(res, i, i_toastreloptions));
+ tblinfo[i].partkeydef = pg_strdup(PQgetvalue(res, i, i_partkeydef));
/* other fields were zeroed above */
@@ -5858,7 +5867,9 @@ getTables(Archive *fout, int *numTables)
* NOTE: it'd be kinda nice to lock other relations too, not only
* plain tables, but the backend doesn't presently allow that.
*/
- if (tblinfo[i].dobj.dump && tblinfo[i].relkind == RELKIND_RELATION)
+ if (tblinfo[i].dobj.dump &&
+ (tblinfo[i].relkind == RELKIND_RELATION ||
+ tblinfo->relkind == RELKIND_PARTITIONED_REL))
{
resetPQExpBuffer(query);
appendPQExpBuffer(query,
@@ -5945,7 +5956,10 @@ getInherits(Archive *fout, int *numInherits)
/* find all the inheritance information */
- appendPQExpBufferStr(query, "SELECT inhrelid, inhparent FROM pg_inherits");
+ appendPQExpBufferStr(query,
+ "SELECT inhrelid, inhparent "
+ "FROM pg_inherits "
+ "WHERE inhparent NOT IN (SELECT oid FROM pg_class WHERE relkind = 'P')");
res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
@@ -15268,6 +15282,9 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
appendPQExpBufferChar(q, ')');
}
+ if (tbinfo->relkind == RELKIND_PARTITIONED_REL)
+ appendPQExpBuffer(q, "\n%s", tbinfo->partkeydef);
+
if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
appendPQExpBuffer(q, "\nSERVER %s", fmtId(srvname));
}
@@ -15327,6 +15344,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
*/
if (dopt->binary_upgrade &&
(tbinfo->relkind == RELKIND_RELATION ||
+ tbinfo->relkind == RELKIND_PARTITIONED_REL ||
tbinfo->relkind == RELKIND_FOREIGN_TABLE))
{
for (j = 0; j < tbinfo->numatts; j++)
@@ -15345,7 +15363,8 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
appendStringLiteralAH(q, fmtId(tbinfo->dobj.name), fout);
appendPQExpBufferStr(q, "::pg_catalog.regclass;\n");
- if (tbinfo->relkind == RELKIND_RELATION)
+ if (tbinfo->relkind == RELKIND_RELATION ||
+ tbinfo->relkind == RELKIND_PARTITIONED_REL)
appendPQExpBuffer(q, "ALTER TABLE ONLY %s ",
fmtId(tbinfo->dobj.name));
else
@@ -15562,6 +15581,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
* dump properties we only have ALTER TABLE syntax for
*/
if ((tbinfo->relkind == RELKIND_RELATION ||
+ tbinfo->relkind == RELKIND_PARTITIONED_REL ||
tbinfo->relkind == RELKIND_MATVIEW) &&
tbinfo->relreplident != REPLICA_IDENTITY_DEFAULT)
{
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 7314cbe..3094425 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -283,6 +283,7 @@ typedef struct _tableInfo
bool *inhNotNull; /* true if NOT NULL is inherited */
struct _attrDefInfo **attrdefs; /* DEFAULT expressions */
struct _constraintInfo *checkexprs; /* CHECK constraints */
+ char *partkeydef; /* partition key definition */
/*
* Stuff computed only for dumpable tables.
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 0a771bd..78a1fdb 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -764,6 +764,7 @@ permissionsList(const char *pattern)
" WHEN 'm' THEN '%s'"
" WHEN 'S' THEN '%s'"
" WHEN 'f' THEN '%s'"
+ " WHEN 'P' THEN '%s'"
" END as \"%s\",\n"
" ",
gettext_noop("Schema"),
@@ -773,6 +774,7 @@ permissionsList(const char *pattern)
gettext_noop("materialized view"),
gettext_noop("sequence"),
gettext_noop("foreign table"),
+ gettext_noop("partitioned table"),
gettext_noop("Type"));
printACLColumn(&buf, "c.relacl");
@@ -819,7 +821,7 @@ permissionsList(const char *pattern)
appendPQExpBufferStr(&buf, "\nFROM pg_catalog.pg_class c\n"
" LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace\n"
- "WHERE c.relkind IN ('r', 'v', 'm', 'S', 'f')\n");
+ "WHERE c.relkind IN ('r', 'v', 'm', 'S', 'f', 'P')\n");
/*
* Unless a schema pattern is specified, we suppress system and temp
@@ -1463,8 +1465,8 @@ describeOneTableDetails(const char *schemaname,
* types, and foreign tables (c.f. CommentObject() in comment.c).
*/
if (tableinfo.relkind == 'r' || tableinfo.relkind == 'v' ||
- tableinfo.relkind == 'm' ||
- tableinfo.relkind == 'f' || tableinfo.relkind == 'c')
+ tableinfo.relkind == 'm' || tableinfo.relkind == 'f' ||
+ tableinfo.relkind == 'c' || tableinfo.relkind == 'P')
appendPQExpBufferStr(&buf, ", pg_catalog.col_description(a.attrelid, a.attnum)");
}
@@ -1529,6 +1531,14 @@ describeOneTableDetails(const char *schemaname,
printfPQExpBuffer(&title, _("Foreign table \"%s.%s\""),
schemaname, relationname);
break;
+ case 'P':
+ if (tableinfo.relpersistence == 'u')
+ printfPQExpBuffer(&title, _("Unlogged partitioned table \"%s.%s\""),
+ schemaname, relationname);
+ else
+ printfPQExpBuffer(&title, _("Partitioned table \"%s.%s\""),
+ schemaname, relationname);
+ break;
default:
/* untranslated unknown relkind */
printfPQExpBuffer(&title, "?%c? \"%s.%s\"",
@@ -1542,8 +1552,8 @@ describeOneTableDetails(const char *schemaname,
cols = 2;
if (tableinfo.relkind == 'r' || tableinfo.relkind == 'v' ||
- tableinfo.relkind == 'm' ||
- tableinfo.relkind == 'f' || tableinfo.relkind == 'c')
+ tableinfo.relkind == 'm' || tableinfo.relkind == 'f' ||
+ tableinfo.relkind == 'c' || tableinfo.relkind == 'P')
{
show_modifiers = true;
headers[cols++] = gettext_noop("Modifiers");
@@ -1563,12 +1573,12 @@ describeOneTableDetails(const char *schemaname,
{
headers[cols++] = gettext_noop("Storage");
if (tableinfo.relkind == 'r' || tableinfo.relkind == 'm' ||
- tableinfo.relkind == 'f')
+ tableinfo.relkind == 'f' || tableinfo.relkind == 'P')
headers[cols++] = gettext_noop("Stats target");
/* Column comments, if the relkind supports this feature. */
if (tableinfo.relkind == 'r' || tableinfo.relkind == 'v' ||
- tableinfo.relkind == 'm' ||
- tableinfo.relkind == 'c' || tableinfo.relkind == 'f')
+ tableinfo.relkind == 'm' || tableinfo.relkind == 'c' ||
+ tableinfo.relkind == 'f' || tableinfo.relkind == 'P')
headers[cols++] = gettext_noop("Description");
}
@@ -1668,7 +1678,7 @@ describeOneTableDetails(const char *schemaname,
/* Statistics target, if the relkind supports this feature */
if (tableinfo.relkind == 'r' || tableinfo.relkind == 'm' ||
- tableinfo.relkind == 'f')
+ tableinfo.relkind == 'f' || tableinfo.relkind == 'P')
{
printTableAddCell(&cont, PQgetvalue(res, i, firstvcol + 1),
false, false);
@@ -1676,14 +1686,33 @@ describeOneTableDetails(const char *schemaname,
/* Column comments, if the relkind supports this feature. */
if (tableinfo.relkind == 'r' || tableinfo.relkind == 'v' ||
- tableinfo.relkind == 'm' ||
- tableinfo.relkind == 'c' || tableinfo.relkind == 'f')
+ tableinfo.relkind == 'm' || tableinfo.relkind == 'c' ||
+ tableinfo.relkind == 'f' || tableinfo.relkind == 'P')
printTableAddCell(&cont, PQgetvalue(res, i, firstvcol + 2),
false, false);
}
}
/* Make footers */
+ if (pset.sversion >= 90600 && tableinfo.relkind == 'P')
+ {
+ /* Get the partition key information */
+ PGresult *result;
+ char *partkeydef;
+
+ printfPQExpBuffer(&buf,
+ "SELECT pg_catalog.pg_get_partkeydef('%s'::pg_catalog.oid);",
+ oid);
+ result = PSQLexec(buf.data);
+ if (!result || PQntuples(result) != 1)
+ goto error_return;
+
+ partkeydef = PQgetvalue(result, 0, 0);
+ printfPQExpBuffer(&tmpbuf, _("Partition Key: %s"), partkeydef);
+ printTableAddFooter(&cont, tmpbuf.data);
+ PQclear(result);
+ }
+
if (tableinfo.relkind == 'i')
{
/* Footer information about an index */
@@ -1822,7 +1851,7 @@ describeOneTableDetails(const char *schemaname,
PQclear(result);
}
else if (tableinfo.relkind == 'r' || tableinfo.relkind == 'm' ||
- tableinfo.relkind == 'f')
+ tableinfo.relkind == 'f' || tableinfo.relkind == 'P')
{
/* Footer information about a table */
PGresult *result = NULL;
@@ -2381,7 +2410,7 @@ describeOneTableDetails(const char *schemaname,
* Finish printing the footer information about a table.
*/
if (tableinfo.relkind == 'r' || tableinfo.relkind == 'm' ||
- tableinfo.relkind == 'f')
+ tableinfo.relkind == 'f' || tableinfo.relkind == 'P')
{
PGresult *result;
int tuples;
@@ -2592,7 +2621,7 @@ add_tablespace_footer(printTableContent *const cont, char relkind,
Oid tablespace, const bool newline)
{
/* relkinds for which we support tablespaces */
- if (relkind == 'r' || relkind == 'm' || relkind == 'i')
+ if (relkind == 'r' || relkind == 'm' || relkind == 'i' || relkind == 'P')
{
/*
* We ignore the database default tablespace so that users not using
@@ -2926,6 +2955,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
" WHEN 'S' THEN '%s'"
" WHEN 's' THEN '%s'"
" WHEN 'f' THEN '%s'"
+ " WHEN 'P' THEN '%s'"
" END as \"%s\",\n"
" pg_catalog.pg_get_userbyid(c.relowner) as \"%s\"",
gettext_noop("Schema"),
@@ -2937,6 +2967,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
gettext_noop("sequence"),
gettext_noop("special"),
gettext_noop("foreign table"),
+ gettext_noop("partitioned table"),
gettext_noop("Type"),
gettext_noop("Owner"));
@@ -2975,7 +3006,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
appendPQExpBufferStr(&buf, "\nWHERE c.relkind IN (");
if (showTables)
- appendPQExpBufferStr(&buf, "'r',");
+ appendPQExpBufferStr(&buf, "'r', 'P',");
if (showViews)
appendPQExpBufferStr(&buf, "'v',");
if (showMatViews)
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index a62ffe6..741ac46 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -427,7 +427,7 @@ static const SchemaQuery Query_for_list_of_tables = {
/* catname */
"pg_catalog.pg_class c",
/* selcondition */
- "c.relkind IN ('r')",
+ "c.relkind IN ('r', 'P')",
/* viscondition */
"pg_catalog.pg_table_is_visible(c.oid)",
/* namespace */
@@ -458,7 +458,7 @@ static const SchemaQuery Query_for_list_of_updatables = {
/* catname */
"pg_catalog.pg_class c",
/* selcondition */
- "c.relkind IN ('r', 'f', 'v')",
+ "c.relkind IN ('r', 'f', 'v', 'P')",
/* viscondition */
"pg_catalog.pg_table_is_visible(c.oid)",
/* namespace */
@@ -488,7 +488,7 @@ static const SchemaQuery Query_for_list_of_tsvmf = {
/* catname */
"pg_catalog.pg_class c",
/* selcondition */
- "c.relkind IN ('r', 'S', 'v', 'm', 'f')",
+ "c.relkind IN ('r', 'S', 'v', 'm', 'f', 'P')",
/* viscondition */
"pg_catalog.pg_table_is_visible(c.oid)",
/* namespace */
diff --git a/src/include/catalog/indexing.h b/src/include/catalog/indexing.h
index ca5eb3d..cadb741 100644
--- a/src/include/catalog/indexing.h
+++ b/src/include/catalog/indexing.h
@@ -319,6 +319,9 @@ DECLARE_UNIQUE_INDEX(pg_replication_origin_roiident_index, 6001, on pg_replicati
DECLARE_UNIQUE_INDEX(pg_replication_origin_roname_index, 6002, on pg_replication_origin using btree(roname text_pattern_ops));
#define ReplicationOriginNameIndex 6002
+DECLARE_UNIQUE_INDEX(pg_partitioned_partedrelid_index, 3351, on pg_partitioned using btree(partedrelid oid_ops));
+#define PartitionedRelidIndexId 3351
+
/* last step of initialization script: build the indexes declared above */
BUILD_INDICES
diff --git a/src/include/catalog/partition.h b/src/include/catalog/partition.h
new file mode 100644
index 0000000..2c876c4
--- /dev/null
+++ b/src/include/catalog/partition.h
@@ -0,0 +1,55 @@
+/*-------------------------------------------------------------------------
+ *
+ * partition.h
+ * Header file for structures and utility functions related to
+ * partitioning
+ *
+ * Copyright (c) 2007-2016, PostgreSQL Global Development Group
+ *
+ * src/include/utils/partition.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PARTITION_H
+#define PARTITION_H
+
+#include "fmgr.h"
+#include "utils/relcache.h"
+
+/* Type and collation information for partition key columns */
+typedef struct
+{
+ Oid *typid;
+ int32 *typmod;
+ int16 *typlen;
+ bool *typbyval;
+ char *typalign;
+ Oid *typcoll;
+} KeyTypeCollInfo;
+
+/* Partition key information for one level */
+typedef struct PartitionKeyData
+{
+ char strategy; /* partition strategy */
+ int16 partnatts; /* number of partition attributes */
+ AttrNumber *partattrs; /* partition attnums */
+ Oid *partopfamily; /* OIDs of opfamily per col */
+ FmgrInfo *partsupfunc; /* lookup info for support funcs */
+ List *partexprs; /* partition key expressions, if any */
+ KeyTypeCollInfo *tcinfo; /* type and collation info (all columns) */
+} PartitionKeyData;
+
+typedef struct PartitionKeyData *PartitionKey;
+
+extern void StorePartitionKey(Relation rel,
+ char strategy,
+ int16 partnatts,
+ AttrNumber *partattrs,
+ List *partexprs,
+ Oid *partopclass);
+extern void RemovePartitionKeyByRelId(Oid relid);
+extern void RelationBuildPartitionKey(Relation relation);
+extern void FreePartitionKey(PartitionKey key);
+extern bool relid_is_partitioned(Oid relid);
+
+#endif /* PARTITION_H */
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index fdc67c2..149f619 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -153,6 +153,7 @@ DESCR("");
#define RELKIND_RELATION 'r' /* ordinary table */
+#define RELKIND_PARTITIONED_REL 'P' /* partitioned table */
#define RELKIND_INDEX 'i' /* secondary index */
#define RELKIND_SEQUENCE 'S' /* sequence object */
#define RELKIND_TOASTVALUE 't' /* for out-of-line values */
diff --git a/src/include/catalog/pg_partitioned.h b/src/include/catalog/pg_partitioned.h
new file mode 100644
index 0000000..edc52db
--- /dev/null
+++ b/src/include/catalog/pg_partitioned.h
@@ -0,0 +1,67 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_partitioned.h
+ * definition of the system "partitioned" relation (pg_partitioned)
+ * along with the relation's initial contents.
+ *
+ *
+ * Portions Copyright (c) 1996-2015, PostgreSQL Global Development Group
+ *
+ * $PostgreSQL: pgsql/src/include/catalog/pg_partitioned.h $
+ *
+ * NOTES
+ * the genbki.sh script reads this file and generates .bki
+ * information from the DATA() statements.
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_PARTITIONED_REL_H
+#define PG_PARTITIONED_REL_H
+
+#include "catalog/genbki.h"
+
+/* ----------------
+ * pg_partitioned definition. cpp turns this into
+ * typedef struct FormData_pg_partitioned
+ * ----------------
+ */
+#define PartitionedRelationId 3350
+
+CATALOG(pg_partitioned,3350) BKI_WITHOUT_OIDS
+{
+ Oid partedrelid; /* partitioned table oid */
+ char partstrat; /* partition key strategy */
+ int16 partnatts; /* number of partition key columns */
+
+ /* variable-length fields start here, but we allow direct access to partattrs */
+ int2vector partattrs; /* attribute numbers of partition key
+ * columns */
+
+#ifdef CATALOG_VARLEN
+ oidvector partclass; /* operator class to compare keys */
+ pg_node_tree partexprs; /* expression trees for partition key members
+ * that are not simple column references; one
+ * for each zero entry in partkey[] */
+#endif
+} FormData_pg_partitioned;
+
+/* ----------------
+ * Form_pg_partitioned corresponds to a pointer to a tuple with
+ * the format of pg_partitioned relation.
+ * ----------------
+ */
+typedef FormData_pg_partitioned *Form_pg_partitioned;
+
+/* ----------------
+ * compiler constants for pg_partitioned
+ * ----------------
+ */
+#define Natts_pg_partitioned 6
+#define Anum_pg_partitioned_partedrelid 1
+#define Anum_pg_partitioned_partstrat 2
+#define Anum_pg_partitioned_partnatts 3
+#define Anum_pg_partitioned_partattrs 4
+#define Anum_pg_partitioned_partclass 5
+#define Anum_pg_partitioned_partexprs 6
+
+#endif /* PG_PARTITIONED_REL_H */
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index bb539d4..92a7260 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -1969,6 +1969,8 @@ DATA(insert OID = 1642 ( pg_get_userbyid PGNSP PGUID 12 1 0 0 0 f f f f t f
DESCR("role name by OID (with fallback)");
DATA(insert OID = 1643 ( pg_get_indexdef PGNSP PGUID 12 1 0 0 0 f f f f t f s s 1 0 25 "26" _null_ _null_ _null_ _null_ _null_ pg_get_indexdef _null_ _null_ _null_ ));
DESCR("index description");
+DATA(insert OID = 3352 ( pg_get_partkeydef PGNSP PGUID 12 1 0 0 0 f f f f t f s s 1 0 25 "26" _null_ _null_ _null_ _null_ _null_ pg_get_partkeydef _null_ _null_ _null_ ));
+DESCR("partition key description");
DATA(insert OID = 1662 ( pg_get_triggerdef PGNSP PGUID 12 1 0 0 0 f f f f t f s s 1 0 25 "26" _null_ _null_ _null_ _null_ _null_ pg_get_triggerdef _null_ _null_ _null_ ));
DESCR("trigger description");
DATA(insert OID = 1387 ( pg_get_constraintdef PGNSP PGUID 12 1 0 0 0 f f f f t f s s 1 0 25 "26" _null_ _null_ _null_ _null_ _null_ pg_get_constraintdef _null_ _null_ _null_ ));
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index 28024f8..ceeb41f 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -42,8 +42,8 @@ extern bool CheckIndexCompatible(Oid oldId,
List *attributeList,
List *exclusionOpNames);
extern Oid GetDefaultOpClass(Oid type_id, Oid am_id);
-extern Oid GetIndexOpClass(List *opclass, Oid attrType,
- char *accessMethodName, Oid accessMethodId);
+extern Oid GetIndexOpClass(List *opclass, Oid attrType,
+ char *accessMethodName, Oid accessMethodId);
/* commands/functioncmds.c */
extern ObjectAddress CreateFunction(CreateFunctionStmt *stmt, const char *queryString);
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
index 01976a1..027665c 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -720,6 +720,7 @@ extern Datum pg_get_viewdef_wrap(PG_FUNCTION_ARGS);
extern Datum pg_get_viewdef_name(PG_FUNCTION_ARGS);
extern Datum pg_get_viewdef_name_ext(PG_FUNCTION_ARGS);
extern Datum pg_get_indexdef(PG_FUNCTION_ARGS);
+extern Datum pg_get_partkeydef(PG_FUNCTION_ARGS);
extern Datum pg_get_indexdef_ext(PG_FUNCTION_ARGS);
extern Datum pg_get_triggerdef(PG_FUNCTION_ARGS);
extern Datum pg_get_triggerdef_ext(PG_FUNCTION_ARGS);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b5d82d6..cf91ef8 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -90,6 +90,8 @@ typedef struct RelationData
/* use "struct" here to avoid needing to include rowsecurity.h: */
struct RowSecurityDesc *rd_rsdesc; /* row security policies, or NULL */
+ struct PartitionKeyData *rd_partkey; /* partition key, or NULL */
+
/* data managed by RelationGetIndexList: */
List *rd_indexlist; /* list of OIDs of indexes on relation */
Oid rd_oidindex; /* OID of unique index on OID, if any */
diff --git a/src/include/utils/syscache.h b/src/include/utils/syscache.h
index 256615b..e727842 100644
--- a/src/include/utils/syscache.h
+++ b/src/include/utils/syscache.h
@@ -72,6 +72,7 @@ enum SysCacheIdentifier
OPEROID,
OPFAMILYAMNAMENSP,
OPFAMILYOID,
+ PARTEDRELID,
PROCNAMEARGSNSP,
PROCOID,
RANGETYPE,
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index aaae9f9..a4cc948 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -326,9 +326,35 @@ ERROR: cannot use a constant expression as partition key
CREATE TABLE fail_agg_in_key (
a int
) PARTITION BY RANGE ((avg(a)));
-ERROR: aggregate functions are not allowed in partition key expressions
+ERROR: aggregate functions are not allowed in partition key expression
CREATE TABLE fail_window_fun_in_key (
a int,
b int
) PARTITION BY RANGE ((avg(a) OVER (PARTITION BY b)));
-ERROR: window functions are not allowed in partition key expressions
+ERROR: window functions are not allowed in partition key expression
+-- check relkind
+CREATE TABLE check_relkind (
+ a int
+) PARTITION BY RANGE (a);
+SELECT relkind FROM pg_class WHERE relname = 'check_relkind';
+ relkind
+---------
+ P
+(1 row)
+
+-- cannot drop column that is part of the partition key
+CREATE TABLE no_drop_alter_partcol (
+ a int
+) PARTITION BY RANGE (a);
+ALTER TABLE no_drop_alter_partcol DROP COLUMN a;
+ERROR: cannot drop column named in partition key
+ALTER TABLE no_drop_alter_partcol ALTER COLUMN a TYPE char(5);
+ERROR: cannot alter column named in partition key
+CREATE TABLE no_drop_alter_partexpr (
+ a text
+) PARTITION BY RANGE ((substring(a from 1 for 1)));
+ALTER TABLE no_drop_alter_partexpr DROP COLUMN a;
+ERROR: cannot drop column referenced in partition key expression
+ALTER TABLE no_drop_alter_partcol ALTER COLUMN a TYPE char(5);
+ERROR: cannot alter column named in partition key
+DROP TABLE check_relkind, no_drop_alter_partcol, no_drop_alter_partexpr;
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index 1c087a3..b1e473d 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -118,6 +118,7 @@ pg_namespace|t
pg_opclass|t
pg_operator|t
pg_opfamily|t
+pg_partitioned|t
pg_pltemplate|t
pg_policy|t
pg_proc|t
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index df47656..022f38b 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -337,3 +337,24 @@ CREATE TABLE fail_window_fun_in_key (
a int,
b int
) PARTITION BY RANGE ((avg(a) OVER (PARTITION BY b)));
+
+-- check relkind
+CREATE TABLE check_relkind (
+ a int
+) PARTITION BY RANGE (a);
+SELECT relkind FROM pg_class WHERE relname = 'check_relkind';
+
+-- cannot drop column that is part of the partition key
+CREATE TABLE no_drop_alter_partcol (
+ a int
+) PARTITION BY RANGE (a);
+ALTER TABLE no_drop_alter_partcol DROP COLUMN a;
+ALTER TABLE no_drop_alter_partcol ALTER COLUMN a TYPE char(5);
+
+CREATE TABLE no_drop_alter_partexpr (
+ a text
+) PARTITION BY RANGE ((substring(a from 1 for 1)));
+ALTER TABLE no_drop_alter_partexpr DROP COLUMN a;
+ALTER TABLE no_drop_alter_partcol ALTER COLUMN a TYPE char(5);
+
+DROP TABLE check_relkind, no_drop_alter_partcol, no_drop_alter_partexpr;
--
1.7.1
[text/x-diff] 0003-Add-syntax-to-create-partitions-v2.patch (78.0K, ../../571759D8.7030205@lab.ntt.co.jp/4-0003-Add-syntax-to-create-partitions-v2.patch)
download | inline diff:
From e4ecf805fb04a451580d48c2e54f376cc78af39b Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Thu, 3 Mar 2016 18:29:15 +0900
Subject: [PATCH 3/5] Add syntax to create partitions.
As follows:
CREATE TABLE name PARTITION OF parent
[ ( { column_name WITH OPTION column_constraint | table_constraint } [, ...] ) ]
partition_bound_spec [ PARTITION BY partition_key_spec ]
ALTER TABLE parent ATTACH PARTITION name
partition_bound_spec [ VALIDATE | NO VALIDATE ]
ALTER TABLE parent DETACH PARTITION name
partition_bound_spec refers to either list partition bound of form:
FOR VALUES IN (list-of-expressions)
or range partition bound of form:
FOR VALUES range_spec
where range_spec is of one of:
START (lower-bound) [ INCLUSIVE | EXCLUSIVE ]
END (upper-bound) [ INCLUSIVE | EXCLUSIVE ]
START (lower-bound) [ INCLUSIVE | EXCLUSIVE ] END (upper-bound) [ INCLUSIVE | EXCLUSIVE ]
In the above, each of lower-bound and upper-bound is either a single
expression for a single column key or list of expressions for a multi-
column key or empty. Note that in the absence of explicit specification,
lower-bound is inclusive and upper-bound is exclusive.
A partition is created as inheritance child of the parent with certain
differences from regular inheritance behavior. For example, when a new
partition is created, it cannot contain its own column definitions or
constraints (unlike CREATE TABLE child(...) INHERITS(parent)). When
"attaching" a table as partition of parent, it cannot contain columns
that are not contained in the parent (unlike ALTER TABLE child INHERIT
parent). No dependency is created between a partition and the parent.
One cannot create regular inheritance children of a partitioned table.
Whereas this commit enables inheritance, it does not yet store partition
bound info anywhere, IOW, the fact that a given inheritance child is a
"partition" is not persisted.
---
doc/src/sgml/ref/alter_table.sgml | 57 +++++
doc/src/sgml/ref/create_table.sgml | 79 ++++++-
src/backend/commands/createas.c | 1 +
src/backend/commands/sequence.c | 1 +
src/backend/commands/tablecmds.c | 490 +++++++++++++++++++++++++++---------
src/backend/commands/view.c | 1 +
src/backend/nodes/copyfuncs.c | 48 ++++
src/backend/nodes/equalfuncs.c | 42 +++
src/backend/nodes/outfuncs.c | 27 ++
src/backend/parser/gram.y | 167 ++++++++++++-
src/backend/parser/parse_agg.c | 7 +-
src/backend/parser/parse_expr.c | 23 ++
src/backend/parser/parse_utilcmd.c | 318 +++++++++++++++++++++++
src/include/catalog/partition.h | 1 +
src/include/nodes/nodes.h | 3 +
src/include/nodes/parsenodes.h | 43 +++-
src/include/parser/kwlist.h | 3 +
src/include/parser/parse_node.h | 3 +-
18 files changed, 1184 insertions(+), 130 deletions(-)
diff --git a/doc/src/sgml/ref/alter_table.sgml b/doc/src/sgml/ref/alter_table.sgml
index 5ca211e..97744c9 100644
--- a/doc/src/sgml/ref/alter_table.sgml
+++ b/doc/src/sgml/ref/alter_table.sgml
@@ -77,12 +77,15 @@ ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable>
NOT OF
OWNER TO { <replaceable class="PARAMETER">new_owner</replaceable> | CURRENT_USER | SESSION_USER }
REPLICA IDENTITY { DEFAULT | USING INDEX <replaceable class="PARAMETER">index_name</replaceable> | FULL | NOTHING }
+ ATTACH PARTITION <replaceable class="PARAMETER">name</replaceable> <replaceable class="PARAMETER">partition_bound_spec</replaceable> [ VALIDATE | NO VALIDATE ]
+ DETACH PARTITION <replaceable class="PARAMETER">name</replaceable>
<phrase>and <replaceable class="PARAMETER">table_constraint_using_index</replaceable> is:</phrase>
[ CONSTRAINT <replaceable class="PARAMETER">constraint_name</replaceable> ]
{ UNIQUE | PRIMARY KEY } USING INDEX <replaceable class="PARAMETER">index_name</replaceable>
[ DEFERRABLE | NOT DEFERRABLE ] [ INITIALLY DEFERRED | INITIALLY IMMEDIATE ]
+
</synopsis>
</refsynopsisdiv>
@@ -704,6 +707,51 @@ ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable>
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>ATTACH PARTITION</literal></term>
+ <listitem>
+ <para>
+ This form attaches an existing table as partition of the target table,
+ provided the target table is partitioned. The partition bound
+ specification must correspond with partition method and key of the
+ partitioned table. The table being attached must have all the columns
+ as the target table and no more. Also, it must have all the matching
+ constraints as the target table. That includes <literal>NOT NULL</>
+ and <literal>CHECK</> constraints. <literal>UNIQUE</literal>,
+ <literal>PRIMARY KEY</literal>, and <literal>FOREIGN KEY</literal>
+ constraints are not considered, but this might change in the future.
+ </para>
+
+ <para>
+ If <literal>NO VALIDATE</literal> clause is specified, existing rows in
+ source table are are not checked to see if they violate the partition
+ bound specification. This allows to quickly load a large amount of data
+ into the partitioned table. The default behavior is to perform the
+ check, as if <literal>VALIDATE</literal> clause were specified.
+ </para>
+
+ <para>
+ Note that once the table becomes a partition, it will be dropped or
+ truncated when the parent table is dropped or truncated. Dropping
+ it directly using <literal>DROP TABLE</literal> will fail; it must
+ first be detached from the parent. However, truncating a partition
+ directly works.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><literal>DETACH PARTITION</literal></term>
+ <listitem>
+ <para>
+ This form detaches specified partition of the target table, provided the
+ target table is partitioned and the specified partition exists. The
+ resulting table continues to exist as a standalone table with no ties
+ remaining with the parent partitioned table.
+ </para>
+ </listitem>
+ </varlistentry>
+
</variablelist>
</para>
@@ -936,6 +984,15 @@ ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable>
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><replaceable class="PARAMETER">partition_bound_spec</replaceable></term>
+ <listitem>
+ <para>
+ The partition bound specification for a new partition.
+ </para>
+ </listitem>
+ </varlistentry>
+
</variablelist>
</refsect1>
diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml
index 70a7579..b377c07 100644
--- a/doc/src/sgml/ref/create_table.sgml
+++ b/doc/src/sgml/ref/create_table.sgml
@@ -44,6 +44,17 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
[ ON COMMIT { PRESERVE ROWS | DELETE ROWS | DROP } ]
[ TABLESPACE <replaceable class="PARAMETER">tablespace_name</replaceable> ]
+CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name</replaceable>
+ PARTITION OF <replaceable class="PARAMETER">parent_table</replaceable> [ (
+ { <replaceable class="PARAMETER">column_name</replaceable> WITH OPTIONS [ <replaceable class="PARAMETER">column_constraint</replaceable> [ ... ] ]
+ | <replaceable>table_constraint</replaceable> }
+ [, ... ]
+) ] <replaceable class="PARAMETER">partition_bound_spec</replaceable>
+[ PARTITION BY {RANGE | LIST} ( { <replaceable class="parameter">column_name</replaceable> | ( <replaceable class="parameter">expression</replaceable> ) } [ <replaceable class="parameter">opclass</replaceable> ] [, ...] )
+[ WITH ( <replaceable class="PARAMETER">storage_parameter</replaceable> [= <replaceable class="PARAMETER">value</replaceable>] [, ... ] ) | WITH OIDS | WITHOUT OIDS ]
+[ ON COMMIT { PRESERVE ROWS | DELETE ROWS | DROP } ]
+[ TABLESPACE <replaceable class="PARAMETER">tablespace_name</replaceable> ]
+
<phrase>where <replaceable class="PARAMETER">column_constraint</replaceable> is:</phrase>
[ CONSTRAINT <replaceable class="PARAMETER">constraint_name</replaceable> ]
@@ -72,6 +83,10 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
{ INCLUDING | EXCLUDING } { DEFAULTS | CONSTRAINTS | INDEXES | STORAGE | COMMENTS | ALL }
+<phrase>and <replaceable class="PARAMETER">partition_bound_spec</replaceable> is:</phrase>
+
+FOR VALUES { <replaceable class="PARAMETER">list_spec</replaceable> | <replaceable class="PARAMETER">range_spec</replaceable> }
+
<phrase><replaceable class="PARAMETER">index_parameters</replaceable> in <literal>UNIQUE</literal>, <literal>PRIMARY KEY</literal>, and <literal>EXCLUDE</literal> constraints are:</phrase>
[ WITH ( <replaceable class="PARAMETER">storage_parameter</replaceable> [= <replaceable class="PARAMETER">value</replaceable>] [, ... ] ) ]
@@ -80,8 +95,22 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
<phrase><replaceable class="PARAMETER">exclude_element</replaceable> in an <literal>EXCLUDE</literal> constraint is:</phrase>
{ <replaceable class="parameter">column_name</replaceable> | ( <replaceable class="parameter">expression</replaceable> ) } [ <replaceable class="parameter">opclass</replaceable> ] [ ASC | DESC ] [ NULLS { FIRST | LAST } ]
-</synopsis>
+<phrase><replaceable class="PARAMETER">list_spec</replaceable> in <literal>FOR VALUES</literal> is:</phrase>
+
+{ IN ( <replaceable class="PARAMETER">expression</replaceable> [, ...] ) }
+
+<phrase><replaceable class="PARAMETER">range_spec</replaceable> in <literal>FOR VALUES</literal> is:</phrase>
+
+{ START (<replaceable class="PARAMETER">lower-bound</replaceable>) [ INCLUSIVE | EXCLUSIVE ] |
+ END (<replaceable class="PARAMETER">upper-bound</replaceable>) [ INCLUSIVE | EXCLUSIVE ] |
+ START (<replaceable class="PARAMETER">lower-bound</replaceable>) [ INCLUSIVE | EXCLUSIVE ] END (<replaceable class="PARAMETER">upper-bound</replaceable>) [ INCLUSIVE | EXCLUSIVE ] }
+
+<phrase>where <replaceable class="PARAMETER">lower-bound</replaceable> and <replaceable class="PARAMETER">upper-bound</replaceable> are:</phrase>
+
+{ <replaceable class="PARAMETER">expression</replaceable> [, ...] }
+
+</synopsis>
</refsynopsisdiv>
<refsect1 id="SQL-CREATETABLE-description">
@@ -232,6 +261,49 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
</varlistentry>
<varlistentry>
+ <term><literal>PARTITION OF <replaceable class="PARAMETER">parent_table</replaceable></literal></term>
+ <listitem>
+ <para>
+ Creates the table as <firstterm>partition</firstterm> of the specified
+ parent table (name optionally schema-qualified).
+ </para>
+
+ <para>
+ A partition bound specification must be present and must correspond with
+ partition method and key of the parent table. It is checked using the
+ specification that the new partition does not overlap with any existing
+ partitions of the parent.
+ </para>
+
+ <para>
+ A partition cannot have columns other than those inherited from the
+ parent. That includes the <structfield>oid</> column, which can be
+ specified using the <literal>WITH (OIDS)</literal> clause. On the other
+ hand, if parent has the <structfield>oid</> column, the partition
+ inherits the same, overriding the <literal>WITH (OIDS=FALSE)</literal>
+ clause, if any. Defaults and constraints can optionally be specified
+ for each of the inherited columns, which override those in the parent.
+ One can also specify table constraints, in addition to those inherited
+ from the parent. Note that all subsequent schema modifications to the
+ parent propagate to partition.
+ </para>
+
+ <para>
+ Any data row subsequently inserted into the parent table is mapped to
+ and stored in the partition, provided partition key of the row falls
+ within the partition bounds.
+ </para>
+
+ <para>
+ A partition is dropped or truncated when the parent table is dropped or
+ truncated. Dropping it directly using <literal>DROP TABLE</literal>
+ will fail; it must first be <firstterm>detached</> from the parent.
+ However, truncating a partition directly works.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><replaceable class="PARAMETER">column_name</replaceable></term>
<listitem>
<para>
@@ -340,6 +412,11 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
</para>
<para>
+ When specifying for a table being created as partition, one needs to
+ use column names from the parent table as part of the key.
+ </para>
+
+ <para>
Currently, there are following limitations on definition of partitioned
tables: one cannot specify any UNIQUE, PRIMARY KEY, EXCLUDE and/or
FOREIGN KEY constraints.
diff --git a/src/backend/commands/createas.c b/src/backend/commands/createas.c
index cb7a145..427470c 100644
--- a/src/backend/commands/createas.c
+++ b/src/backend/commands/createas.c
@@ -342,6 +342,7 @@ intorel_startup(DestReceiver *self, int operation, TupleDesc typeinfo)
col->is_local = true;
col->is_not_null = false;
col->is_from_type = false;
+ col->is_for_partition = false;
col->storage = 0;
col->raw_default = NULL;
col->cooked_default = NULL;
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index 5ecad82..d039dbd 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -159,6 +159,7 @@ DefineSequence(CreateSeqStmt *seq)
coldef->is_local = true;
coldef->is_not_null = true;
coldef->is_from_type = false;
+ coldef->is_for_partition = false;
coldef->storage = 0;
coldef->raw_default = NULL;
coldef->cooked_default = NULL;
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index fcae323..6e66405 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -279,7 +279,8 @@ typedef struct
static void truncate_check_rel(Relation rel);
static List *MergeAttributes(List *schema, List *supers, char relpersistence,
- List **supOids, List **supconstr, int *supOidCount);
+ List **supOids, List **supconstr, int *supOidCount,
+ bool is_partition);
static bool MergeCheckConstraint(List *constraints, char *name, Node *expr);
static void MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel);
static void MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel);
@@ -448,6 +449,11 @@ static void ComputePartitionAttrs(Oid relid, List *partParams,
static bool find_attr_reference_walker(Node *node,
find_attr_reference_context *context);
static bool is_partition_attr(Relation rel, AttrNumber attnum, bool *is_expr);
+static void CreateInheritance(Relation child_rel, Relation parent_rel);
+static void RemoveInheritance(Relation child_rel, Relation parent_rel);
+static ObjectAddress ATExecAttachPartition(List **wqueue, Relation rel,
+ PartitionCmd *cmd);
+static ObjectAddress ATExecDetachPartition(Relation rel, RangeVar *name);
/* ----------------------------------------------------------------
@@ -590,10 +596,16 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
/*
* Look up inheritance ancestors and generate relation schema, including
* inherited attributes.
+ *
+ * The last parameter implicitly specifies that the table is being created
+ * as partition and schema consists of columns definitions corresponding
+ * to non-dropped columns of the parent constructed such that each
+ * attribute of the table is created as inherited and non-local.
*/
schema = MergeAttributes(schema, stmt->inhRelations,
stmt->relation->relpersistence,
- &inheritOids, &old_constraints, &parentOidCount);
+ &inheritOids, &old_constraints, &parentOidCount,
+ stmt->partbound != NULL);
/*
* Create a tuple descriptor from the relation schema. Note that this
@@ -1452,7 +1464,8 @@ storage_name(char c)
*/
static List *
MergeAttributes(List *schema, List *supers, char relpersistence,
- List **supOids, List **supconstr, int *supOidCount)
+ List **supOids, List **supconstr, int *supOidCount,
+ bool is_partition)
{
ListCell *entry;
List *inhSchema = NIL;
@@ -1497,8 +1510,8 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
/*
* Typed table column option that does not belong to a column from
- * the type. This works because the columns from the type come
- * first in the list.
+ * the type or the partition parent. This works because the columns
+ * from the type or the partition parent come first in the list.
*/
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_COLUMN),
@@ -1525,6 +1538,18 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
coldef->is_from_type = false;
list_delete_cell(schema, rest, prev);
}
+ else if (coldef->is_for_partition)
+ {
+ /*
+ * merge the column options into the column from the parent
+ */
+ coldef->is_not_null = restdef->is_not_null;
+ coldef->raw_default = restdef->raw_default;
+ coldef->cooked_default = restdef->cooked_default;
+ coldef->constraints = restdef->constraints;
+ coldef->is_for_partition = false; /* job is done */
+ list_delete_cell(schema, rest, prev);
+ }
else
ereport(ERROR,
(errcode(ERRCODE_DUPLICATE_COLUMN),
@@ -1563,8 +1588,12 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
*/
relation = heap_openrv(parent, ShareUpdateExclusiveLock);
- /* Cannot inherit from partitioned tables */
- if (relation->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+ /*
+ * Cannot inherit from a partitioned table unless being created as
+ * partition.
+ */
+ if (relation->rd_rel->relkind == RELKIND_PARTITIONED_REL &&
+ !is_partition)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot inherit from partitioned table \"%s\"",
@@ -1710,6 +1739,7 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
def->is_local = false;
def->is_not_null = attribute->attnotnull;
def->is_from_type = false;
+ def->is_for_partition = false;
def->storage = attribute->attstorage;
def->raw_default = NULL;
def->cooked_default = NULL;
@@ -1838,6 +1868,9 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
* If we had no inherited attributes, the result schema is just the
* explicitly declared columns. Otherwise, we need to merge the declared
* columns into the inherited schema list.
+ *
+ * Note: In case of a partition, there are only inherited attributes that
+ * we copied from the parent as so, in transformPartitionOf().
*/
if (inhSchema != NIL)
{
@@ -1867,16 +1900,20 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
/*
* Yes, try to merge the two column definitions. They must
- * have the same type, typmod, and collation.
+ * have the same type, typmod, and collation. Don't output
+ * the notices, if partition, per note above.
*/
- if (exist_attno == schema_attno)
- ereport(NOTICE,
- (errmsg("merging column \"%s\" with inherited definition",
- attributeName)));
- else
- ereport(NOTICE,
- (errmsg("moving and merging column \"%s\" with inherited definition", attributeName),
- errdetail("User-specified column moved to the position of the inherited column.")));
+ if (!is_partition)
+ {
+ if (exist_attno == schema_attno)
+ ereport(NOTICE,
+ (errmsg("merging column \"%s\" with inherited definition",
+ attributeName)));
+ else
+ ereport(NOTICE,
+ (errmsg("moving and merging column \"%s\" with inherited definition", attributeName),
+ errdetail("User-specified column moved to the position of the inherited column.")));
+ }
def = (ColumnDef *) list_nth(inhSchema, exist_attno - 1);
typenameTypeIdAndMod(NULL, def->typeName, &defTypeId, &deftypmod);
typenameTypeIdAndMod(NULL, newdef->typeName, &newTypeId, &newtypmod);
@@ -1913,8 +1950,9 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
storage_name(def->storage),
storage_name(newdef->storage))));
- /* Mark the column as locally defined */
- def->is_local = true;
+ /* Mark the column as locally defined, unless partition */
+ if (!is_partition)
+ def->is_local = true;
/* Merge of NOT NULL constraints = OR 'em together */
def->is_not_null |= newdef->is_not_null;
/* If new def has a default, override previous default */
@@ -2093,16 +2131,20 @@ StoreCatalogInheritance1(Oid relationId, Oid parentOid,
heap_freetuple(tuple);
/*
- * Store a dependency too
+ * Store a dependency too, iff a regular inheritance relationship, which
+ * it's not, if parent is a partitioned table.
*/
- parentobject.classId = RelationRelationId;
- parentobject.objectId = parentOid;
- parentobject.objectSubId = 0;
- childobject.classId = RelationRelationId;
- childobject.objectId = relationId;
- childobject.objectSubId = 0;
+ if (!relid_is_partitioned(parentOid))
+ {
+ parentobject.classId = RelationRelationId;
+ parentobject.objectId = parentOid;
+ parentobject.objectSubId = 0;
+ childobject.classId = RelationRelationId;
+ childobject.objectId = relationId;
+ childobject.objectSubId = 0;
- recordDependencyOn(&childobject, &parentobject, DEPENDENCY_NORMAL);
+ recordDependencyOn(&childobject, &parentobject, DEPENDENCY_NORMAL);
+ }
/*
* Post creation hook of this inheritance. Since object_access_hook
@@ -3108,6 +3150,11 @@ AlterTableGetLockLevel(List *cmds)
cmd_lockmode = AlterTableGetRelOptionsLockLevel((List *) cmd->def);
break;
+ case AT_AttachPartition:
+ case AT_DetachPartition:
+ cmd_lockmode = AccessExclusiveLock;
+ break;
+
default: /* oops */
elog(ERROR, "unrecognized alter table type: %d",
(int) cmd->subtype);
@@ -3425,6 +3472,12 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
/* No command-specific prep needed */
pass = AT_PASS_MISC;
break;
+ case AT_AttachPartition:
+ case AT_DetachPartition:
+ ATSimplePermissions(rel, ATT_TABLE);
+ /* No command-specific prep needed */
+ pass = AT_PASS_MISC;
+ break;
default: /* oops */
elog(ERROR, "unrecognized alter table type: %d",
(int) cmd->subtype);
@@ -3746,6 +3799,12 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
case AT_GenericOptions:
ATExecGenericOptions(rel, (List *) cmd->def);
break;
+ case AT_AttachPartition:
+ ATExecAttachPartition(wqueue, rel, (PartitionCmd *) cmd->def);
+ break;
+ case AT_DetachPartition:
+ ATExecDetachPartition(rel, ((PartitionCmd *) cmd->def)->name);
+ break;
default: /* oops */
elog(ERROR, "unrecognized alter table type: %d",
(int) cmd->subtype);
@@ -10190,12 +10249,7 @@ ATPrepAddInherit(Relation child_rel)
static ObjectAddress
ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode)
{
- Relation parent_rel,
- catalogRelation;
- SysScanDesc scan;
- ScanKeyData key;
- HeapTuple inheritsTuple;
- int32 inhseqno;
+ Relation parent_rel;
List *children;
ObjectAddress address;
@@ -10240,38 +10294,6 @@ ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode)
errmsg("cannot inherit from partitioned table \"%s\"", parent->relname)));
/*
- * Check for duplicates in the list of parents, and determine the highest
- * inhseqno already present; we'll use the next one for the new parent.
- * (Note: get RowExclusiveLock because we will write pg_inherits below.)
- *
- * Note: we do not reject the case where the child already inherits from
- * the parent indirectly; CREATE TABLE doesn't reject comparable cases.
- */
- catalogRelation = heap_open(InheritsRelationId, RowExclusiveLock);
- ScanKeyInit(&key,
- Anum_pg_inherits_inhrelid,
- BTEqualStrategyNumber, F_OIDEQ,
- ObjectIdGetDatum(RelationGetRelid(child_rel)));
- scan = systable_beginscan(catalogRelation, InheritsRelidSeqnoIndexId,
- true, NULL, 1, &key);
-
- /* inhseqno sequences start at 1 */
- inhseqno = 0;
- while (HeapTupleIsValid(inheritsTuple = systable_getnext(scan)))
- {
- Form_pg_inherits inh = (Form_pg_inherits) GETSTRUCT(inheritsTuple);
-
- if (inh->inhparent == RelationGetRelid(parent_rel))
- ereport(ERROR,
- (errcode(ERRCODE_DUPLICATE_TABLE),
- errmsg("relation \"%s\" would be inherited from more than once",
- RelationGetRelationName(parent_rel))));
- if (inh->inhseqno > inhseqno)
- inhseqno = inh->inhseqno;
- }
- systable_endscan(scan);
-
- /*
* Prevent circularity by seeing if proposed parent inherits from child.
* (In particular, this disallows making a rel inherit from itself.)
*
@@ -10304,6 +10326,69 @@ ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode)
RelationGetRelationName(child_rel),
RelationGetRelationName(parent_rel))));
+ /* OK to create inheritance */
+ CreateInheritance(child_rel, parent_rel);
+
+ ObjectAddressSet(address, RelationRelationId,
+ RelationGetRelid(parent_rel));
+
+ /* keep our lock on the parent relation until commit */
+ heap_close(parent_rel, NoLock);
+
+ return address;
+}
+
+/*
+ * CreateInheritance
+ * Catalog manipulation portion of creating inheritance between a child
+ * table and a parent table.
+ *
+ * Common to ATExecAddInherit() and ATExecAttachPartition().
+ */
+static void
+CreateInheritance(Relation child_rel, Relation parent_rel)
+{
+ Relation catalogRelation;
+ SysScanDesc scan;
+ ScanKeyData key;
+ HeapTuple inheritsTuple;
+ int32 inhseqno;
+
+ /* Note: get RowExclusiveLock because we will write pg_inherits below. */
+ catalogRelation = heap_open(InheritsRelationId, RowExclusiveLock);
+
+ /*
+ * Check for duplicates in the list of parents, and determine the highest
+ * inhseqno already present; we'll use the next one for the new parent.
+ * Also, if proposed child is a partition, it cannot already be inheriting.
+ *
+ * Note: we do not reject the case where the child already inherits from
+ * the parent indirectly; CREATE TABLE doesn't reject comparable cases.
+ */
+ ScanKeyInit(&key,
+ Anum_pg_inherits_inhrelid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(RelationGetRelid(child_rel)));
+ scan = systable_beginscan(catalogRelation, InheritsRelidSeqnoIndexId,
+ true, NULL, 1, &key);
+
+ /* inhseqno sequences start at 1 */
+ inhseqno = 0;
+ while (HeapTupleIsValid(inheritsTuple = systable_getnext(scan)))
+ {
+ Form_pg_inherits inh = (Form_pg_inherits) GETSTRUCT(inheritsTuple);
+
+ if (inh->inhparent == RelationGetRelid(parent_rel))
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_TABLE),
+ errmsg("relation \"%s\" would be inherited from more than once",
+ RelationGetRelationName(parent_rel))));
+
+ if (inh->inhseqno > inhseqno)
+ inhseqno = inh->inhseqno;
+ }
+ systable_endscan(scan);
+
/* Match up the columns and bump attinhcount as needed */
MergeAttributesIntoExisting(child_rel, parent_rel);
@@ -10318,16 +10403,8 @@ ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode)
inhseqno + 1,
catalogRelation);
- ObjectAddressSet(address, RelationRelationId,
- RelationGetRelid(parent_rel));
-
/* Now we're done with pg_inherits */
heap_close(catalogRelation, RowExclusiveLock);
-
- /* keep our lock on the parent relation until commit */
- heap_close(parent_rel, NoLock);
-
- return address;
}
/*
@@ -10378,7 +10455,7 @@ constraints_equivalent(HeapTuple a, HeapTuple b, TupleDesc tupleDesc)
* Check columns in child table match up with columns in parent, and increment
* their attinhcount.
*
- * Called by ATExecAddInherit
+ * Called by CreateInheritance
*
* Currently all parent columns must be found in child. Missing columns are an
* error. One day we might consider creating new columns like CREATE TABLE
@@ -10396,11 +10473,13 @@ MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel)
int parent_natts;
TupleDesc tupleDesc;
HeapTuple tuple;
+ bool is_attach_partition;
attrrel = heap_open(AttributeRelationId, RowExclusiveLock);
tupleDesc = RelationGetDescr(parent_rel);
parent_natts = tupleDesc->natts;
+ is_attach_partition = relid_is_partitioned(RelationGetRelid(parent_rel));
for (parent_attno = 1; parent_attno <= parent_natts; parent_attno++)
{
@@ -10423,14 +10502,16 @@ MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel)
attribute->atttypmod != childatt->atttypmod)
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
- errmsg("child table \"%s\" has different type for column \"%s\"",
+ errmsg("%s table \"%s\" has different type for column \"%s\"",
+ is_attach_partition ? "source" : "child",
RelationGetRelationName(child_rel),
attributeName)));
if (attribute->attcollation != childatt->attcollation)
ereport(ERROR,
(errcode(ERRCODE_COLLATION_MISMATCH),
- errmsg("child table \"%s\" has different collation for column \"%s\"",
+ errmsg("%s table \"%s\" has different collation for column \"%s\"",
+ is_attach_partition ? "source" : "child",
RelationGetRelationName(child_rel),
attributeName)));
@@ -10441,8 +10522,9 @@ MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel)
if (attribute->attnotnull && !childatt->attnotnull)
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
- errmsg("column \"%s\" in child table must be marked NOT NULL",
- attributeName)));
+ errmsg("column \"%s\" in %s table must be marked NOT NULL",
+ attributeName,
+ is_attach_partition ? "source" : "child")));
/*
* OK, bump the child column's inheritance count. (If we fail
@@ -10457,7 +10539,8 @@ MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel)
{
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
- errmsg("child table is missing column \"%s\"",
+ errmsg("%s table is missing column \"%s\"",
+ is_attach_partition ? "source" : "child",
attributeName)));
}
}
@@ -10471,7 +10554,7 @@ MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel)
*
* Constraints that are marked ONLY in the parent are ignored.
*
- * Called by ATExecAddInherit
+ * Called by CreateInheritance
*
* Currently all constraints in parent must be present in the child. One day we
* may consider adding new constraints like CREATE TABLE does.
@@ -10490,9 +10573,11 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
SysScanDesc parent_scan;
ScanKeyData parent_key;
HeapTuple parent_tuple;
+ bool is_attach_partition;
catalog_relation = heap_open(ConstraintRelationId, RowExclusiveLock);
tuple_desc = RelationGetDescr(catalog_relation);
+ is_attach_partition = relid_is_partitioned(RelationGetRelid(parent_rel));
/* Outer loop scans through the parent's constraint definitions */
ScanKeyInit(&parent_key,
@@ -10540,7 +10625,8 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
if (!constraints_equivalent(parent_tuple, child_tuple, tuple_desc))
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
- errmsg("child table \"%s\" has different definition for check constraint \"%s\"",
+ errmsg("%s table \"%s\" has different definition for check constraint \"%s\"",
+ is_attach_partition ? "source" : "child",
RelationGetRelationName(child_rel),
NameStr(parent_con->conname))));
@@ -10548,8 +10634,9 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
if (child_con->connoinherit)
ereport(ERROR,
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
- errmsg("constraint \"%s\" conflicts with non-inherited constraint on child table \"%s\"",
+ errmsg("constraint \"%s\" conflicts with non-inherited constraint on %s table \"%s\"",
NameStr(child_con->conname),
+ is_attach_partition ? "source" : "child",
RelationGetRelationName(child_rel))));
/*
@@ -10572,7 +10659,8 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
if (!found)
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
- errmsg("child table is missing constraint \"%s\"",
+ errmsg("%s table is missing constraint \"%s\"",
+ is_attach_partition ? "source" : "child",
NameStr(parent_con->conname))));
}
@@ -10583,6 +10671,41 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
/*
* ALTER TABLE NO INHERIT
*
+ * Return value is the address of the relation that is no longer parent.
+ */
+static ObjectAddress
+ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
+{
+ ObjectAddress address;
+ Relation parent_rel;
+
+ /*
+ * AccessShareLock on the parent is probably enough, seeing that DROP
+ * TABLE doesn't lock parent tables at all. We need some lock since we'll
+ * be inspecting the parent's schema.
+ */
+ parent_rel = heap_openrv(parent, AccessShareLock);
+
+ /*
+ * We don't bother to check ownership of the parent table --- ownership of
+ * the child is presumed enough rights.
+ */
+
+ /* Off to RemoveInheritance() where most of the work happens */
+ RemoveInheritance(rel, parent_rel);
+
+ /* keep our lock on the parent relation until commit */
+ heap_close(parent_rel, NoLock);
+
+ ObjectAddressSet(address, RelationRelationId,
+ RelationGetRelid(parent_rel));
+
+ return address;
+}
+
+/*
+ * RemoveInheritance
+ *
* Drop a parent from the child's parents. This just adjusts the attinhcount
* and attislocal of the columns and removes the pg_inherit and pg_depend
* entries.
@@ -10596,13 +10719,11 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
* coninhcount and conislocal for inherited constraints are adjusted in
* exactly the same way.
*
- * Return value is the address of the relation that is no longer parent.
+ * Common to ATExecDropInherit() and ATExecDetachPartition().
*/
-static ObjectAddress
-ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
+static void
+RemoveInheritance(Relation child_rel, Relation parent_rel)
{
- Relation parent_rel;
- Oid parent_oid;
Relation catalogRelation;
SysScanDesc scan;
ScanKeyData key[3];
@@ -10611,19 +10732,9 @@ ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
constraintTuple;
List *connames;
bool found = false;
- ObjectAddress address;
+ bool is_detach_partition;
- /*
- * AccessShareLock on the parent is probably enough, seeing that DROP
- * TABLE doesn't lock parent tables at all. We need some lock since we'll
- * be inspecting the parent's schema.
- */
- parent_rel = heap_openrv(parent, AccessShareLock);
-
- /*
- * We don't bother to check ownership of the parent table --- ownership of
- * the child is presumed enough rights.
- */
+ is_detach_partition = relid_is_partitioned(RelationGetRelid(parent_rel));
/*
* Find and destroy the pg_inherits entry linking the two, or error out if
@@ -10633,7 +10744,7 @@ ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
ScanKeyInit(&key[0],
Anum_pg_inherits_inhrelid,
BTEqualStrategyNumber, F_OIDEQ,
- ObjectIdGetDatum(RelationGetRelid(rel)));
+ ObjectIdGetDatum(RelationGetRelid(child_rel)));
scan = systable_beginscan(catalogRelation, InheritsRelidSeqnoIndexId,
true, NULL, 1, key);
@@ -10654,11 +10765,20 @@ ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
heap_close(catalogRelation, RowExclusiveLock);
if (!found)
- ereport(ERROR,
- (errcode(ERRCODE_UNDEFINED_TABLE),
- errmsg("relation \"%s\" is not a parent of relation \"%s\"",
- RelationGetRelationName(parent_rel),
- RelationGetRelationName(rel))));
+ {
+ if (!is_detach_partition)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_TABLE),
+ errmsg("relation \"%s\" is not a parent of relation \"%s\"",
+ RelationGetRelationName(parent_rel),
+ RelationGetRelationName(child_rel))));
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_TABLE),
+ errmsg("relation \"%s\" is not a partition of relation \"%s\"",
+ RelationGetRelationName(child_rel),
+ RelationGetRelationName(parent_rel))));
+ }
/*
* Search through child columns looking for ones matching parent rel
@@ -10667,7 +10787,7 @@ ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
ScanKeyInit(&key[0],
Anum_pg_attribute_attrelid,
BTEqualStrategyNumber, F_OIDEQ,
- ObjectIdGetDatum(RelationGetRelid(rel)));
+ ObjectIdGetDatum(RelationGetRelid(child_rel)));
scan = systable_beginscan(catalogRelation, AttributeRelidNumIndexId,
true, NULL, 1, key);
while (HeapTupleIsValid(attributeTuple = systable_getnext(scan)))
@@ -10729,7 +10849,7 @@ ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
ScanKeyInit(&key[0],
Anum_pg_constraint_conrelid,
BTEqualStrategyNumber, F_OIDEQ,
- ObjectIdGetDatum(RelationGetRelid(rel)));
+ ObjectIdGetDatum(RelationGetRelid(child_rel)));
scan = systable_beginscan(catalogRelation, ConstraintRelidIndexId,
true, NULL, 1, key);
@@ -10760,7 +10880,7 @@ ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
if (copy_con->coninhcount <= 0) /* shouldn't happen */
elog(ERROR, "relation %u has non-inherited constraint \"%s\"",
- RelationGetRelid(rel), NameStr(copy_con->conname));
+ RelationGetRelid(child_rel), NameStr(copy_con->conname));
copy_con->coninhcount--;
if (copy_con->coninhcount == 0)
@@ -10772,30 +10892,22 @@ ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
}
}
- parent_oid = RelationGetRelid(parent_rel);
-
systable_endscan(scan);
heap_close(catalogRelation, RowExclusiveLock);
- drop_parent_dependency(RelationGetRelid(rel),
- RelationRelationId,
- RelationGetRelid(parent_rel));
-
+ /* Remember that dependency exists iff regular inheritance. */
+ if (!is_detach_partition)
+ drop_parent_dependency(RelationGetRelid(child_rel),
+ RelationRelationId,
+ RelationGetRelid(parent_rel));
/*
* Post alter hook of this inherits. Since object_access_hook doesn't take
* multiple object identifiers, we relay oid of parent relation using
* auxiliary_id argument.
*/
InvokeObjectPostAlterHookArg(InheritsRelationId,
- RelationGetRelid(rel), 0,
+ RelationGetRelid(child_rel), 0,
RelationGetRelid(parent_rel), false);
-
- /* keep our lock on the parent relation until commit */
- heap_close(parent_rel, NoLock);
-
- ObjectAddressSet(address, RelationRelationId, parent_oid);
-
- return address;
}
/*
@@ -12428,3 +12540,137 @@ ComputePartitionAttrs(Oid relid, List *partParams, AttrNumber *partattrs,
partopclass[attn++] = opclassOid;
}
}
+
+
+/*
+ * ALTER TABLE <name> ATTACH PARTITION <partition-name> FOR VALUES
+ *
+ * Return the address of the newly attached partition.
+ */
+static ObjectAddress
+ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
+{
+ Relation attachRel,
+ inheritsRelation;
+ SysScanDesc scan;
+ ScanKeyData key;
+ HeapTuple tuple;
+ AttrNumber attno;
+ int natts;
+ TupleDesc tupleDesc;
+ ObjectAddress address;
+
+ attachRel = heap_openrv(cmd->name, AccessShareLock);
+
+ /*
+ * Must be owner of both parent and source table -- parent was checked by
+ * ATSimplePermissions call in ATPrepCmd
+ */
+ ATSimplePermissions(attachRel, ATT_TABLE);
+
+ if (attachRel->rd_rel->reloftype)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot attach a typed table as partition")));
+
+ if (attachRel->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot attach a partitioned table as partition")));
+
+ /* attachRel should not already be a inheritance child of some relation */
+ inheritsRelation = heap_open(InheritsRelationId, AccessShareLock);
+ ScanKeyInit(&key,
+ Anum_pg_inherits_inhrelid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(RelationGetRelid(attachRel)));
+ scan = systable_beginscan(inheritsRelation, InheritsRelidSeqnoIndexId,
+ true, NULL, 1, &key);
+ if (HeapTupleIsValid(systable_getnext(scan)))
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot attach table that inherits as partition")));
+ systable_endscan(scan);
+ heap_close(inheritsRelation, AccessShareLock);
+
+ /* If attachRel is temp, it must belong to this session */
+ if (attachRel->rd_rel->relpersistence == RELPERSISTENCE_TEMP &&
+ !attachRel->rd_islocaltemp)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot attach a temporary relation of another session as partition ")));
+
+ /* If parent has OIDs, attachRel must have OIDs */
+ if (rel->rd_rel->relhasoids && !attachRel->rd_rel->relhasoids)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot attach table \"%s\" without OIDs as partition of"
+ " table \"%s\" with OIDs", RelationGetRelationName(attachRel),
+ RelationGetRelationName(rel))));
+
+ /* OTOH, if parent doesn't have them, do not allow in attachRel either */
+ if (attachRel->rd_rel->relhasoids && !rel->rd_rel->relhasoids)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot attach table \"%s\" with OIDs as partition of table"
+ " \"%s\" without OIDs", RelationGetRelationName(attachRel),
+ RelationGetRelationName(rel))));
+
+ /* Check if there are any columns in attachRel that aren't in the parent */
+ tupleDesc = RelationGetDescr(attachRel);
+ natts = tupleDesc->natts;
+ for (attno = 1; attno <= natts; attno++)
+ {
+ Form_pg_attribute attribute = tupleDesc->attrs[attno - 1];
+ char *attributeName = NameStr(attribute->attname);
+
+ /* Ignore dropped */
+ if (attribute->attisdropped)
+ continue;
+
+ /* Find same column in parent (matching on column name). */
+ tuple = SearchSysCacheCopyAttName(RelationGetRelid(rel), attributeName);
+ if (!HeapTupleIsValid(tuple))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("table \"%s\" contains column \"%s\" not found in parent \"%s\"",
+ RelationGetRelationName(attachRel), attributeName,
+ RelationGetRelationName(rel)),
+ errdetail("Table being attached should contain only the columns"
+ " present in parent.")));
+ }
+
+ /* OK to create inheritance. Rest of the checks performed there */
+ CreateInheritance(attachRel, rel);
+
+ ObjectAddressSet(address, RelationRelationId, RelationGetRelid(attachRel));
+
+ /* keep our lock until commit */
+ heap_close(attachRel, NoLock);
+
+ return address;
+}
+
+/*
+ * ALTER TABLE DETACH PARTITION
+ *
+ * Return the address of the relation that is no longer a partition of rel.
+ */
+static ObjectAddress
+ATExecDetachPartition(Relation rel, RangeVar *name)
+{
+ Relation partRel;
+ ObjectAddress address;
+
+ partRel = heap_openrv(name, AccessShareLock);
+
+ /* All inheritance related checks are performed within the function */
+ RemoveInheritance(partRel, rel);
+
+ ObjectAddressSet(address, RelationRelationId, RelationGetRelid(partRel));
+
+ /* keep our lock until commit */
+ heap_close(partRel, NoLock);
+
+ return address;
+}
diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c
index e9d9ba2..c9c72d9 100644
--- a/src/backend/commands/view.c
+++ b/src/backend/commands/view.c
@@ -95,6 +95,7 @@ DefineVirtualRelation(RangeVar *relation, List *tlist, bool replace,
def->is_local = true;
def->is_not_null = false;
def->is_from_type = false;
+ def->is_for_partition = false;
def->storage = 0;
def->raw_default = NULL;
def->cooked_default = NULL;
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 5f4f66f..e924ddb 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2604,6 +2604,7 @@ _copyColumnDef(const ColumnDef *from)
COPY_SCALAR_FIELD(is_local);
COPY_SCALAR_FIELD(is_not_null);
COPY_SCALAR_FIELD(is_from_type);
+ COPY_SCALAR_FIELD(is_for_partition);
COPY_SCALAR_FIELD(storage);
COPY_NODE_FIELD(raw_default);
COPY_NODE_FIELD(cooked_default);
@@ -2999,6 +3000,7 @@ CopyCreateStmtFields(const CreateStmt *from, CreateStmt *newnode)
COPY_NODE_FIELD(relation);
COPY_NODE_FIELD(tableElts);
COPY_NODE_FIELD(inhRelations);
+ COPY_NODE_FIELD(partbound);
COPY_NODE_FIELD(partby);
COPY_NODE_FIELD(ofTypename);
COPY_NODE_FIELD(constraints);
@@ -4181,6 +4183,43 @@ _copyPartitionElem(const PartitionElem *from)
return newnode;
}
+static PartitionListSpec *
+_copyPartitionListSpec(const PartitionListSpec *from)
+{
+ PartitionListSpec *newnode = makeNode(PartitionListSpec);
+
+ COPY_NODE_FIELD(values);
+ COPY_LOCATION_FIELD(location);
+
+ return newnode;
+}
+
+static PartitionRangeSpec *
+_copyPartitionRangeSpec(const PartitionRangeSpec *from)
+{
+ PartitionRangeSpec *newnode = makeNode(PartitionRangeSpec);
+
+ COPY_SCALAR_FIELD(lowerinc);
+ COPY_NODE_FIELD(lower);
+ COPY_SCALAR_FIELD(upperinc);
+ COPY_NODE_FIELD(upper);
+ COPY_LOCATION_FIELD(location);
+
+ return newnode;
+}
+
+static PartitionCmd *
+_copyPartitionCmd(const PartitionCmd *from)
+{
+ PartitionCmd *newnode = makeNode(PartitionCmd);
+
+ COPY_NODE_FIELD(name);
+ COPY_NODE_FIELD(bound);
+ COPY_SCALAR_FIELD(skip_validate);
+
+ return newnode;
+}
+
/* ****************************************************************
* pg_list.h copy functions
* ****************************************************************
@@ -5080,6 +5119,15 @@ copyObject(const void *from)
case T_PartitionElem:
retval = _copyPartitionElem(from);
break;
+ case T_PartitionListSpec:
+ retval = _copyPartitionListSpec(from);
+ break;
+ case T_PartitionRangeSpec:
+ retval = _copyPartitionRangeSpec(from);
+ break;
+ case T_PartitionCmd:
+ retval = _copyPartitionCmd(from);
+ break;
default:
elog(ERROR, "unrecognized node type: %d", (int) nodeTag(from));
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 001a468..51b1e86 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1154,6 +1154,7 @@ _equalCreateStmt(const CreateStmt *a, const CreateStmt *b)
COMPARE_NODE_FIELD(relation);
COMPARE_NODE_FIELD(tableElts);
COMPARE_NODE_FIELD(inhRelations);
+ COMPARE_NODE_FIELD(partbound);
COMPARE_NODE_FIELD(partby);
COMPARE_NODE_FIELD(ofTypename);
COMPARE_NODE_FIELD(constraints);
@@ -2361,6 +2362,7 @@ _equalColumnDef(const ColumnDef *a, const ColumnDef *b)
COMPARE_SCALAR_FIELD(is_local);
COMPARE_SCALAR_FIELD(is_not_null);
COMPARE_SCALAR_FIELD(is_from_type);
+ COMPARE_SCALAR_FIELD(is_for_partition);
COMPARE_SCALAR_FIELD(storage);
COMPARE_NODE_FIELD(raw_default);
COMPARE_NODE_FIELD(cooked_default);
@@ -2639,6 +2641,37 @@ _equalPartitionElem(const PartitionElem *a, const PartitionElem *b)
return true;
}
+static bool
+_equalPartitionListSpec(const PartitionListSpec *a, const PartitionListSpec *b)
+{
+ COMPARE_NODE_FIELD(values);
+ COMPARE_LOCATION_FIELD(location);
+
+ return true;
+}
+
+static bool
+_equalPartitionRangeSpec(const PartitionRangeSpec *a, const PartitionRangeSpec *b)
+{
+ COMPARE_SCALAR_FIELD(lowerinc);
+ COMPARE_NODE_FIELD(lower);
+ COMPARE_SCALAR_FIELD(upperinc);
+ COMPARE_NODE_FIELD(upper);
+ COMPARE_LOCATION_FIELD(location);
+
+ return true;
+}
+
+static bool
+_equalPartitionCmd(const PartitionCmd *a, const PartitionCmd *b)
+{
+ COMPARE_NODE_FIELD(name);
+ COMPARE_NODE_FIELD(bound);
+ COMPARE_SCALAR_FIELD(skip_validate);
+
+ return true;
+}
+
/*
* Stuff from pg_list.h
*/
@@ -3395,6 +3428,15 @@ equal(const void *a, const void *b)
case T_PartitionElem:
retval = _equalPartitionElem(a, b);
break;
+ case T_PartitionListSpec:
+ retval = _equalPartitionListSpec(a, b);
+ break;
+ case T_PartitionRangeSpec:
+ retval = _equalPartitionRangeSpec(a, b);
+ break;
+ case T_PartitionCmd:
+ retval = _equalPartitionCmd(a, b);
+ break;
default:
elog(ERROR, "unrecognized node type: %d",
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 4e70a69..b531af3 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2367,6 +2367,7 @@ _outCreateStmtInfo(StringInfo str, const CreateStmt *node)
WRITE_NODE_FIELD(relation);
WRITE_NODE_FIELD(tableElts);
WRITE_NODE_FIELD(inhRelations);
+ WRITE_NODE_FIELD(partbound);
WRITE_NODE_FIELD(partby);
WRITE_NODE_FIELD(ofTypename);
WRITE_NODE_FIELD(constraints);
@@ -2547,6 +2548,7 @@ _outColumnDef(StringInfo str, const ColumnDef *node)
WRITE_BOOL_FIELD(is_local);
WRITE_BOOL_FIELD(is_not_null);
WRITE_BOOL_FIELD(is_from_type);
+ WRITE_BOOL_FIELD(is_for_partition);
WRITE_CHAR_FIELD(storage);
WRITE_NODE_FIELD(raw_default);
WRITE_NODE_FIELD(cooked_default);
@@ -3237,6 +3239,25 @@ _outPartitionElem(StringInfo str, const PartitionElem *node)
WRITE_LOCATION_FIELD(location);
}
+static void
+_outPartitionListSpec(StringInfo str, const PartitionListSpec *node)
+{
+ WRITE_NODE_TYPE("PARTITIONLISTVALUES");
+
+ WRITE_NODE_FIELD(values);
+}
+
+static void
+_outPartitionRangeSpec(StringInfo str, const PartitionRangeSpec *node)
+{
+ WRITE_NODE_TYPE("PARTITIONRANGE");
+
+ WRITE_BOOL_FIELD(lowerinc);
+ WRITE_NODE_FIELD(lower);
+ WRITE_BOOL_FIELD(upperinc);
+ WRITE_NODE_FIELD(upper);
+}
+
/*
* outNode -
* converts a Node into ascii string and append it to 'str'
@@ -3821,6 +3842,12 @@ outNode(StringInfo str, const void *obj)
case T_PartitionElem:
_outPartitionElem(str, obj);
break;
+ case T_PartitionListSpec:
+ _outPartitionListSpec(str, obj);
+ break;
+ case T_PartitionRangeSpec:
+ _outPartitionRangeSpec(str, obj);
+ break;
default:
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 0607439..79fbb32 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -229,6 +229,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
VariableSetStmt *vsetstmt;
PartitionElem *partelem;
PartitionBy *partby;
+ PartitionRangeSpec *partrange;
}
%type <node> stmt schema_stmt
@@ -544,6 +545,14 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <partby> PartitionBy OptPartitionBy
%type <partelem> part_elem
%type <list> part_params
+%type <list> OptPartitionElementList PartitionElementList
+%type <node> PartitionElement
+%type <node> ForValues
+%type <node> partvalue
+%type <list> partvalue_list
+%type <boolean> lb_inc ub_inc
+%type <list> RangeBound
+%type <boolean> opt_validate_spec
/*
* Non-keyword token types. These are hard-wired into the "flex" lexer.
@@ -569,7 +578,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
/* ordinary key words in alphabetical order */
%token <keyword> ABORT_P ABSOLUTE_P ACCESS ACTION ADD_P ADMIN AFTER
AGGREGATE ALL ALSO ALTER ALWAYS ANALYSE ANALYZE AND ANY ARRAY AS ASC
- ASSERTION ASSIGNMENT ASYMMETRIC AT ATTRIBUTE AUTHORIZATION
+ ASSERTION ASSIGNMENT ASYMMETRIC AT ATTACH ATTRIBUTE AUTHORIZATION
BACKWARD BEFORE BEGIN_P BETWEEN BIGINT BINARY BIT
BOOLEAN_P BOTH BY
@@ -585,7 +594,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
DATA_P DATABASE DAY_P DEALLOCATE DEC DECIMAL_P DECLARE DEFAULT DEFAULTS
DEFERRABLE DEFERRED DEFINER DELETE_P DELIMITER DELIMITERS DEPENDS DESC
- DICTIONARY DISABLE_P DISCARD DISTINCT DO DOCUMENT_P DOMAIN_P DOUBLE_P DROP
+ DETACH DICTIONARY DISABLE_P DISCARD DISTINCT DO DOCUMENT_P DOMAIN_P
+ DOUBLE_P DROP
EACH ELSE ENABLE_P ENCODING ENCRYPTED END_P ENUM_P ESCAPE EVENT EXCEPT
EXCLUDE EXCLUDING EXCLUSIVE EXECUTE EXISTS EXPLAIN
@@ -599,7 +609,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
HANDLER HAVING HEADER_P HOLD HOUR_P
IDENTITY_P IF_P ILIKE IMMEDIATE IMMUTABLE IMPLICIT_P IMPORT_P IN_P
- INCLUDING INCREMENT INDEX INDEXES INHERIT INHERITS INITIALLY INLINE_P
+ INCLUDING INCLUSIVE INCREMENT INDEX INDEXES INHERIT INHERITS INITIALLY INLINE_P
INNER_P INOUT INPUT_P INSENSITIVE INSERT INSTEAD INT_P INTEGER
INTERSECT INTERVAL INTO INVOKER IS ISNULL ISOLATION
@@ -2373,6 +2383,38 @@ alter_table_cmd:
n->def = (Node *)$1;
$$ = (Node *) n;
}
+ /* ALTER TABLE <name> ATTACH PARTITION <table_name> FOR VALUES */
+ | ATTACH PARTITION qualified_name ForValues opt_validate_spec
+ {
+ AlterTableCmd *n = makeNode(AlterTableCmd);
+ PartitionCmd *cmd = makeNode(PartitionCmd);
+
+ n->subtype = AT_AttachPartition;
+ cmd->name = $3;
+ cmd->bound = (Node *) $4;
+ cmd->skip_validate = $5;
+ n->def = (Node *) cmd;
+
+ $$ = (Node *) n;
+ }
+ /* ALTER TABLE <name> DETACH PARTITION <partition_name> */
+ | DETACH PARTITION qualified_name
+ {
+ AlterTableCmd *n = makeNode(AlterTableCmd);
+ PartitionCmd *cmd = makeNode(PartitionCmd);
+
+ n->subtype = AT_DetachPartition;
+ cmd->name = $3;
+ n->def = (Node *) cmd;
+
+ $$ = (Node *) n;
+ }
+ ;
+
+opt_validate_spec:
+ NO VALIDATE { $$ = true; }
+ | VALIDATE { $$ = false; }
+ | /* EMPTY */ { $$ = false; }
;
alter_column_default:
@@ -2468,6 +2510,60 @@ reloption_elem:
}
;
+ForValues:
+ /* a LIST partition */
+ FOR VALUES IN_P '(' partvalue_list ')'
+ {
+ PartitionListSpec *n = makeNode(PartitionListSpec);
+
+ n->values = $5;
+ n->location = @1;
+
+ $$ = (Node *) n;
+ }
+
+ /* a RANGE partition */
+ | FOR VALUES START RangeBound lb_inc END_P RangeBound ub_inc
+ {
+ PartitionRangeSpec *n = makeNode(PartitionRangeSpec);
+
+ n->lowerinc = $5;
+ n->lower = $4;
+ n->upperinc = $8;
+ n->upper = $7;
+ n->location = @1;
+
+ $$ = (Node *) n;
+ }
+ ;
+
+RangeBound:
+ UNBOUNDED { $$ = NIL; }
+ | '(' partvalue_list ')' { $$ = $2; }
+ ;
+
+lb_inc:
+ EXCLUSIVE { $$ = false;}
+ | INCLUSIVE { $$ = true; }
+ | /* EMPTY */ { $$ = true; }
+ ;
+
+ub_inc:
+ INCLUSIVE { $$ = true; }
+ | EXCLUSIVE { $$ = false; }
+ | /* EMPTY */ { $$ = false; }
+ ;
+
+partvalue:
+ Sconst { $$ = makeStringConst($1, @1); }
+ | NumericOnly { $$ = makeAConst($1, @1); }
+ | NULL_P { $$ = makeNullAConst(@1); }
+ ;
+
+partvalue_list:
+ partvalue { $$ = list_make1($1); }
+ | partvalue_list ',' partvalue { $$ = lappend($1, $3); }
+ ;
/*****************************************************************************
*
@@ -2885,6 +2981,44 @@ CreateStmt: CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
n->if_not_exists = true;
$$ = (Node *)n;
}
+ | CREATE OptTemp TABLE qualified_name PARTITION OF qualified_name
+ OptPartitionElementList ForValues OptPartitionBy OptWith
+ OnCommitOption OptTableSpace
+ {
+ CreateStmt *n = makeNode(CreateStmt);
+ $4->relpersistence = $2;
+ n->relation = $4;
+ n->tableElts = $8;
+ n->inhRelations = list_make1($7);
+ n->partbound = (Node *) $9;
+ n->partby = $10;
+ n->ofTypename = NULL;
+ n->constraints = NIL;
+ n->options = $11;
+ n->oncommit = $12;
+ n->tablespacename = $13;
+ n->if_not_exists = false;
+ $$ = (Node *)n;
+ }
+ | CREATE OptTemp TABLE IF_P NOT EXISTS qualified_name PARTITION OF
+ qualified_name OptPartitionElementList ForValues OptPartitionBy
+ OptWith OnCommitOption OptTableSpace
+ {
+ CreateStmt *n = makeNode(CreateStmt);
+ $7->relpersistence = $2;
+ n->relation = $7;
+ n->tableElts = $11;
+ n->inhRelations = list_make1($10);
+ n->partbound = (Node *) $12;
+ n->partby = $13;
+ n->ofTypename = NULL;
+ n->constraints = NIL;
+ n->options = $14;
+ n->oncommit = $15;
+ n->tablespacename = $16;
+ n->if_not_exists = true;
+ $$ = (Node *)n;
+ }
;
/*
@@ -2930,6 +3064,11 @@ OptTypedTableElementList:
| /*EMPTY*/ { $$ = NIL; }
;
+OptPartitionElementList:
+ '(' PartitionElementList ')' { $$ = $2; }
+ | /*EMPTY*/ { $$ = NIL; }
+ ;
+
TableElementList:
TableElement
{
@@ -2952,6 +3091,17 @@ TypedTableElementList:
}
;
+PartitionElementList:
+ PartitionElement
+ {
+ $$ = list_make1($1);
+ }
+ | PartitionElementList ',' PartitionElement
+ {
+ $$ = lappend($1, $3);
+ }
+ ;
+
TableElement:
columnDef { $$ = $1; }
| TableLikeClause { $$ = $1; }
@@ -2963,6 +3113,11 @@ TypedTableElement:
| TableConstraint { $$ = $1; }
;
+PartitionElement:
+ columnOptions { $$ = $1; }
+ | TableConstraint { $$ = $1; }
+ ;
+
columnDef: ColId Typename create_generic_options ColQualList
{
ColumnDef *n = makeNode(ColumnDef);
@@ -2972,6 +3127,7 @@ columnDef: ColId Typename create_generic_options ColQualList
n->is_local = true;
n->is_not_null = false;
n->is_from_type = false;
+ n->is_for_partition = false;
n->storage = 0;
n->raw_default = NULL;
n->cooked_default = NULL;
@@ -2993,6 +3149,7 @@ columnOptions: ColId WITH OPTIONS ColQualList
n->is_local = true;
n->is_not_null = false;
n->is_from_type = false;
+ n->is_for_partition = false;
n->storage = 0;
n->raw_default = NULL;
n->cooked_default = NULL;
@@ -11122,6 +11279,7 @@ TableFuncElement: ColId Typename opt_collate_clause
n->is_local = true;
n->is_not_null = false;
n->is_from_type = false;
+ n->is_for_partition = false;
n->storage = 0;
n->raw_default = NULL;
n->cooked_default = NULL;
@@ -13805,6 +13963,7 @@ unreserved_keyword:
| ASSERTION
| ASSIGNMENT
| AT
+ | ATTACH
| ATTRIBUTE
| BACKWARD
| BEFORE
@@ -13851,6 +14010,7 @@ unreserved_keyword:
| DELIMITER
| DELIMITERS
| DEPENDS
+ | DETACH
| DICTIONARY
| DISABLE_P
| DISCARD
@@ -13893,6 +14053,7 @@ unreserved_keyword:
| IMPLICIT_P
| IMPORT_P
| INCLUDING
+ | INCLUSIVE
| INCREMENT
| INDEX
| INDEXES
diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c
index 77ffd84..d71fdfe 100644
--- a/src/backend/parser/parse_agg.c
+++ b/src/backend/parser/parse_agg.c
@@ -493,7 +493,9 @@ check_agglevels_and_constraints(ParseState *pstate, Node *expr)
err = _("grouping operations are not allowed in partition key expression");
break;
-
+ case EXPR_KIND_PARTITION_FOR_VALUES:
+ errkind = true;
+ break;
/*
* There is intentionally no default: case here, so that the
@@ -854,6 +856,9 @@ transformWindowFuncCall(ParseState *pstate, WindowFunc *wfunc,
case EXPR_KIND_PARTITION_KEY:
err = _("window functions are not allowed in partition key expression");
break;
+ case EXPR_KIND_PARTITION_FOR_VALUES:
+ errkind = true;
+ break;
/*
* There is intentionally no default: case here, so that the
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 6a74c28..85d7828 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -352,6 +352,26 @@ transformExprRecurse(ParseState *pstate, Node *expr)
result = (Node *) expr;
break;
}
+ case T_PartitionListSpec:
+ {
+ PartitionListSpec *list = (PartitionListSpec *) expr;
+
+ list->values = transformExpressionList(pstate, list->values,
+ pstate->p_expr_kind);
+ result = expr;
+ break;
+ }
+ case T_PartitionRangeSpec:
+ {
+ PartitionRangeSpec *range = (PartitionRangeSpec *) expr;
+
+ range->lower = transformExpressionList(pstate, range->lower,
+ pstate->p_expr_kind);
+ range->upper = transformExpressionList(pstate, range->upper,
+ pstate->p_expr_kind);
+ result = expr;
+ break;
+ }
default:
/* should not reach here */
@@ -1690,6 +1710,7 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
case EXPR_KIND_OFFSET:
case EXPR_KIND_RETURNING:
case EXPR_KIND_VALUES:
+ case EXPR_KIND_PARTITION_FOR_VALUES:
/* okay */
break;
case EXPR_KIND_CHECK_CONSTRAINT:
@@ -3230,6 +3251,8 @@ ParseExprKindName(ParseExprKind exprKind)
return "WHEN";
case EXPR_KIND_PARTITION_KEY:
return "partition key expression";
+ case EXPR_KIND_PARTITION_FOR_VALUES:
+ return "FOR VALUES";
/*
* There is intentionally no default: case here, so that the
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 465597d..c0a1bb0 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -48,8 +48,10 @@
#include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "nodes/nodeFuncs.h"
+#include "optimizer/planner.h"
#include "parser/analyze.h"
#include "parser/parse_clause.h"
+#include "parser/parse_coerce.h"
#include "parser/parse_collate.h"
#include "parser/parse_expr.h"
#include "parser/parse_relation.h"
@@ -89,6 +91,7 @@ typedef struct
* the table */
IndexStmt *pkey; /* PRIMARY KEY index, if any */
bool ispartitioned; /* true if table is partitioned */
+ Node *partbound; /* transformed FOR VALUES */
} CreateStmtContext;
/* State shared by transformCreateSchemaStmt and its subroutines */
@@ -131,6 +134,10 @@ static void transformConstraintAttrs(CreateStmtContext *cxt,
List *constraintList);
static void transformColumnType(CreateStmtContext *cxt, ColumnDef *column);
static void setSchemaName(char *context_schema, char **stmt_schema_name);
+static void transformPartitionOf(CreateStmtContext *cxt, Node *bound);
+static void transformAttachPartition(CreateStmtContext *cxt, PartitionCmd *cmd);
+static Node *transformPartitionBound(CreateStmtContext *cxt, Relation parent,
+ Node *bound);
/*
@@ -232,6 +239,7 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
cxt.alist = NIL;
cxt.pkey = NULL;
cxt.ispartitioned = stmt->partby != NULL;
+ cxt.partbound = NULL;
/*
* Notice that we allow OIDs here only for plain tables, even though
@@ -250,6 +258,9 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
if (stmt->ofTypename)
transformOfType(&cxt, stmt->ofTypename);
+ if (stmt->partbound)
+ transformPartitionOf(&cxt, stmt->partbound);
+
if (stmt->partby)
{
int partnatts = list_length(stmt->partby->partParams);
@@ -355,6 +366,7 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
*/
stmt->tableElts = cxt.columns;
stmt->constraints = cxt.ckconstraints;
+ stmt->partbound = cxt.partbound;
result = lappend(cxt.blist, stmt);
result = list_concat(result, cxt.alist);
@@ -894,6 +906,7 @@ transformTableLikeClause(CreateStmtContext *cxt, TableLikeClause *table_like_cla
def->is_local = true;
def->is_not_null = attribute->attnotnull;
def->is_from_type = false;
+ def->is_for_partition = false;
def->storage = 0;
def->raw_default = NULL;
def->cooked_default = NULL;
@@ -1113,6 +1126,7 @@ transformOfType(CreateStmtContext *cxt, TypeName *ofTypename)
n->is_local = true;
n->is_not_null = false;
n->is_from_type = true;
+ n->is_for_partition = false;
n->storage = 0;
n->raw_default = NULL;
n->cooked_default = NULL;
@@ -2580,6 +2594,7 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
cxt.alist = NIL;
cxt.pkey = NULL;
cxt.ispartitioned = relid_is_partitioned(relid);
+ cxt.partbound = NULL;
/*
* The only subtypes that currently require parse transformation handling
@@ -2668,6 +2683,22 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
break;
}
+ case AT_AttachPartition:
+ {
+ PartitionCmd *partcmd = (PartitionCmd *) cmd->def;
+
+ transformAttachPartition(&cxt, partcmd);
+
+ /* assign transformed values */
+ partcmd->bound = cxt.partbound;
+ }
+
+ newcmds = lappend(newcmds, cmd);
+ break;
+ case AT_DetachPartition:
+ newcmds = lappend(newcmds, cmd);
+ break;
+
default:
newcmds = lappend(newcmds, cmd);
break;
@@ -3032,3 +3063,290 @@ setSchemaName(char *context_schema, char **stmt_schema_name)
"different from the one being created (%s)",
*stmt_schema_name, context_schema)));
}
+
+/*
+ * transformPartitionOf
+ * Analyze PARTITION OF ... FOR VALUES ...
+ */
+static void
+transformPartitionOf(CreateStmtContext *cxt, Node *bound)
+{
+ TupleDesc tupdesc;
+ int i;
+ Relation parentRel;
+ RangeVar *partof = linitial(cxt->inhRelations);
+
+ parentRel = heap_openrv(partof, AccessShareLock);
+
+ /* Check if the target table is partitioned at all */
+ if (parentRel->rd_rel->relkind != RELKIND_PARTITIONED_REL)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("\"%s\" is not partitioned",
+ RelationGetRelationName(parentRel))));
+ Assert(parentRel->rd_partkey != NULL);
+
+ /* If parent rel is temp, it must belong to this session */
+ if (parentRel->rd_rel->relpersistence == RELPERSISTENCE_TEMP &&
+ !parentRel->rd_islocaltemp)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot create as partition of temporary relation of another session")));
+
+ /*
+ * Do not allow OIDs in a partition, if not present in the parent. But
+ * force them in partition, if they are present in the parent.
+ */
+ if (cxt->hasoids && !parentRel->rd_rel->relhasoids)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot create table with OIDs as partition of table without OIDs")));
+
+ if (parentRel->rd_rel->relhasoids)
+ cxt->hasoids = true;
+
+ tupdesc = RelationGetDescr(parentRel);
+ for (i = 0; i < tupdesc->natts; i++)
+ {
+ Form_pg_attribute attribute = tupdesc->attrs[i];
+ ColumnDef *def;
+
+ if (attribute->attisdropped)
+ continue;
+
+ def = makeNode(ColumnDef);
+ def->colname = pstrdup(NameStr(attribute->attname));
+ def->typeName = makeTypeNameFromOid(attribute->atttypid,
+ attribute->atttypmod);
+ def->inhcount = 1;
+ def->is_local = false;
+ def->is_not_null = attribute->attnotnull;
+ def->is_from_type = false;
+ def->is_for_partition = true;
+ def->storage = attribute->attstorage;
+ def->raw_default = NULL;
+ def->cooked_default = NULL;
+ def->collClause = NULL;
+ def->collOid = attribute->attcollation;
+ def->constraints = NIL;
+ def->location = -1;
+
+ cxt->columns = lappend(cxt->columns, def);
+ }
+
+ /* tranform the values */
+ cxt->partbound = transformPartitionBound(cxt, parentRel, bound);
+
+ heap_close(parentRel, AccessShareLock);
+}
+
+/*
+ * transformAttachPartition
+ * Analyze ATTACH PARTITION ... FOR VALUES ...
+ */
+static void
+transformAttachPartition(CreateStmtContext *cxt, PartitionCmd *cmd)
+{
+ Relation parentRel = cxt->rel;
+
+ /* Check if the target table is partitioned at all */
+ if (parentRel->rd_rel->relkind != RELKIND_PARTITIONED_REL)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("\"%s\" is not partitioned", cxt->relation->relname)));
+ Assert(parentRel->rd_partkey != NULL);
+
+ /* tranform the values */
+ cxt->partbound = transformPartitionBound(cxt, parentRel, cmd->bound);
+}
+
+/*
+ * transformPartitionBound
+ *
+ * Transform partition bound per the partition key
+ */
+static Node *
+transformPartitionBound(CreateStmtContext *cxt, Relation parent, Node *bound)
+{
+ int i;
+ ListCell *cell;
+ Oid parentId = RelationGetRelid(parent);
+ PartitionKey key = parent->rd_partkey;
+ PartitionListSpec *list, *result_list;
+ PartitionRangeSpec *range, *result_range;
+
+ switch (key->strategy)
+ {
+ case PARTITION_STRAT_LIST:
+ if (!IsA(bound, PartitionListSpec))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("invalid bound specification for a list partition"),
+ parser_errposition(cxt->pstate, exprLocation(bound))));
+
+ list = (PartitionListSpec *) transformExpr(cxt->pstate, bound,
+ EXPR_KIND_PARTITION_FOR_VALUES);
+
+ result_list = makeNode(PartitionListSpec);
+
+ foreach(cell, list->values)
+ {
+ Node *value = (Node *) lfirst(cell);
+ Node *orig_value = value;
+ Oid valuetype;
+
+ valuetype = exprType(value);
+ value = coerce_to_target_type(cxt->pstate,
+ value, valuetype,
+ key->tcinfo->typid[0],
+ key->tcinfo->typmod[0],
+ COERCION_ASSIGNMENT,
+ COERCE_IMPLICIT_CAST,
+ -1);
+ if (value == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("specified value cannot be cast to type \"%s\""
+ " of key column \"%s\"",
+ format_type_be(key->tcinfo->typid[0]),
+ get_relid_attribute_name(parentId, key->partattrs[0])),
+ parser_errposition(cxt->pstate, exprLocation(orig_value))));
+
+ /* Simplify the expression */
+ value = (Node *) expression_planner((Expr *) value);
+
+ result_list->values = lappend(result_list->values, value);
+ }
+ return (Node *) result_list;
+
+ case PARTITION_STRAT_RANGE:
+ if (!IsA(bound, PartitionRangeSpec))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("invalid bound specification for a range partition"),
+ parser_errposition(cxt->pstate, exprLocation(bound))));
+
+ range = (PartitionRangeSpec *) transformExpr(cxt->pstate, bound,
+ EXPR_KIND_PARTITION_FOR_VALUES);
+
+ if (!range->lower && !range->upper)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("both START and END cannot be UNBOUNDED"),
+ parser_errposition(cxt->pstate, range->location)));
+
+ result_range = makeNode(PartitionRangeSpec);
+ result_range->lowerinc = range->lowerinc;
+ result_range->upperinc = range->upperinc;
+
+ if (range->lower)
+ {
+ if (list_length(range->lower) > key->partnatts)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("START has more values specified than number"
+ " of columns in the partition key"),
+ parser_errposition(cxt->pstate,
+ exprLocation(list_nth(range->lower,
+ list_length(range->lower) - 1)))));
+ else if (list_length(range->lower) < key->partnatts)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("START has fewer values specified than number"
+ " of columns in the partition key"),
+ parser_errposition(cxt->pstate,
+ exprLocation(list_nth(range->lower,
+ list_length(range->lower) - 1)))));
+
+ i = 0;
+ foreach (cell, range->lower)
+ {
+ Node *value = (Node *) lfirst(cell);
+ Node *orig_value = value;
+ Oid valuetype;
+
+ valuetype = exprType(value);
+ value = coerce_to_target_type(cxt->pstate,
+ value, valuetype,
+ key->tcinfo->typid[i],
+ key->tcinfo->typmod[i],
+ COERCION_ASSIGNMENT,
+ COERCE_IMPLICIT_CAST,
+ -1);
+ if (value == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("specified value cannot be cast to type"
+ " \"%s\" of key column \"%s\"",
+ format_type_be(key->tcinfo->typid[i]),
+ get_relid_attribute_name(parentId, key->partattrs[i])),
+ parser_errposition(cxt->pstate, exprLocation(orig_value))));
+
+ /* Simplify the expression */
+ value = (Node *) expression_planner((Expr *) value);
+
+ result_range->lower = lappend(result_range->lower, value);
+ ++i;
+ }
+ }
+ else
+ result_range->lowerinc = false;
+
+ if (range->upper)
+ {
+ if (list_length(range->upper) > key->partnatts)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("END has more values specified than number"
+ " of columns in the partition key"),
+ parser_errposition(cxt->pstate,
+ exprLocation(list_nth(range->upper,
+ list_length(range->upper) - 1)))));
+ else if (list_length(range->upper) < key->partnatts)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("END has fewer values specified than number"
+ " of columns in the partition key"),
+ parser_errposition(cxt->pstate,
+ exprLocation(list_nth(range->upper,
+ list_length(range->upper) - 1)))));
+
+ i = 0;
+ foreach (cell, range->upper)
+ {
+ Node *value = (Node *) lfirst(cell);
+ Node *orig_value = value;
+ Oid valuetype;
+
+ valuetype = exprType(value);
+ value = coerce_to_target_type(cxt->pstate,
+ value, valuetype,
+ key->tcinfo->typid[i],
+ key->tcinfo->typmod[i],
+ COERCION_ASSIGNMENT,
+ COERCE_IMPLICIT_CAST,
+ -1);
+ if (value == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("specified value cannot be cast to type"
+ " \"%s\" of key column \"%s\"",
+ format_type_be(key->tcinfo->typid[i]),
+ get_relid_attribute_name(parentId, key->partattrs[i])),
+ parser_errposition(cxt->pstate, exprLocation(orig_value))));
+
+ /* Simplify the expression */
+ value = (Node *) expression_planner((Expr *) value);
+
+ result_range->upper = lappend(result_range->upper, value);
+ ++i;
+ }
+ }
+ else
+ result_range->upperinc = false;
+
+ return (Node *) result_range;
+ }
+
+ return NULL;
+}
diff --git a/src/include/catalog/partition.h b/src/include/catalog/partition.h
index 2c876c4..31dbe17 100644
--- a/src/include/catalog/partition.h
+++ b/src/include/catalog/partition.h
@@ -14,6 +14,7 @@
#define PARTITION_H
#include "fmgr.h"
+#include "parser/parse_node.h"
#include "utils/relcache.h"
/* Type and collation information for partition key columns */
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 9e582a5..56bbe43 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -405,6 +405,7 @@ typedef enum NodeTag
T_AlterPolicyStmt,
T_CreateTransformStmt,
T_CreateAmStmt,
+ T_PartitionCmd,
/*
* TAGS FOR PARSE TREE NODES (parsenodes.h)
@@ -454,6 +455,8 @@ typedef enum NodeTag
T_RoleSpec,
T_PartitionElem,
T_PartitionBy,
+ T_PartitionListSpec,
+ T_PartitionRangeSpec,
/*
* TAGS FOR REPLICATION GRAMMAR PARSE NODES (replnodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 4f697b8..d9dff96 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -591,6 +591,7 @@ typedef struct ColumnDef
bool is_local; /* column has local (non-inherited) def'n */
bool is_not_null; /* NOT NULL constraint specified? */
bool is_from_type; /* column definition came from table type */
+ bool is_for_partition; /* column definition is for a partition */
char storage; /* attstorage setting, or 0 for default */
Node *raw_default; /* default value (untransformed parse tree) */
Node *cooked_default; /* default value (transformed expr tree) */
@@ -731,6 +732,40 @@ typedef struct PartitionBy
int location; /* token location, or -1 if unknown */
} PartitionBy;
+/*
+ * PartitionRangeSpec - range type syntax for range partition bound
+ */
+typedef struct PartitionListSpec
+{
+ NodeTag type;
+ List *values;
+ int location;
+} PartitionListSpec;
+
+/*
+ * PartitionRangeSpec - range type syntax for range partition bound
+ */
+typedef struct PartitionRangeSpec
+{
+ NodeTag type;
+ bool lowerinc;
+ List *lower;
+ bool upperinc;
+ List *upper;
+ int location; /* token location, or -1 if unknown */
+} PartitionRangeSpec;
+
+/*
+ * PartitionCmd - ALTER TABLE partition commands
+ */
+typedef struct PartitionCmd
+{
+ NodeTag type;
+ RangeVar *name;
+ Node *bound;
+ bool skip_validate;
+} PartitionCmd;
+
/****************************************************************************
* Nodes for a Query tree
****************************************************************************/
@@ -1558,7 +1593,9 @@ typedef enum AlterTableType
AT_DisableRowSecurity, /* DISABLE ROW SECURITY */
AT_ForceRowSecurity, /* FORCE ROW SECURITY */
AT_NoForceRowSecurity, /* NO FORCE ROW SECURITY */
- AT_GenericOptions /* OPTIONS (...) */
+ AT_GenericOptions, /* OPTIONS (...) */
+ AT_AttachPartition, /* ATTACH PARTITION */
+ AT_DetachPartition /* DETACH PARTITION */
} AlterTableType;
typedef struct ReplicaIdentityStmt
@@ -1783,7 +1820,9 @@ typedef struct CreateStmt
RangeVar *relation; /* relation to create */
List *tableElts; /* column definitions (list of ColumnDef) */
List *inhRelations; /* relations to inherit from (list of
- * inhRelation) */
+ * inhRelation); (ab)used also as partition
+ * parent */
+ Node *partbound; /* FOR VALUES clause */
PartitionBy *partby; /* PARTITION BY clause */
TypeName *ofTypename; /* OF typename */
List *constraints; /* constraints (list of Constraint nodes) */
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 40da67a..70c264c 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -49,6 +49,7 @@ PG_KEYWORD("assertion", ASSERTION, UNRESERVED_KEYWORD)
PG_KEYWORD("assignment", ASSIGNMENT, UNRESERVED_KEYWORD)
PG_KEYWORD("asymmetric", ASYMMETRIC, RESERVED_KEYWORD)
PG_KEYWORD("at", AT, UNRESERVED_KEYWORD)
+PG_KEYWORD("attach", ATTACH, UNRESERVED_KEYWORD)
PG_KEYWORD("attribute", ATTRIBUTE, UNRESERVED_KEYWORD)
PG_KEYWORD("authorization", AUTHORIZATION, TYPE_FUNC_NAME_KEYWORD)
PG_KEYWORD("backward", BACKWARD, UNRESERVED_KEYWORD)
@@ -127,6 +128,7 @@ PG_KEYWORD("delimiter", DELIMITER, UNRESERVED_KEYWORD)
PG_KEYWORD("delimiters", DELIMITERS, UNRESERVED_KEYWORD)
PG_KEYWORD("depends", DEPENDS, UNRESERVED_KEYWORD)
PG_KEYWORD("desc", DESC, RESERVED_KEYWORD)
+PG_KEYWORD("detach", DETACH, UNRESERVED_KEYWORD)
PG_KEYWORD("dictionary", DICTIONARY, UNRESERVED_KEYWORD)
PG_KEYWORD("disable", DISABLE_P, UNRESERVED_KEYWORD)
PG_KEYWORD("discard", DISCARD, UNRESERVED_KEYWORD)
@@ -191,6 +193,7 @@ PG_KEYWORD("implicit", IMPLICIT_P, UNRESERVED_KEYWORD)
PG_KEYWORD("import", IMPORT_P, UNRESERVED_KEYWORD)
PG_KEYWORD("in", IN_P, RESERVED_KEYWORD)
PG_KEYWORD("including", INCLUDING, UNRESERVED_KEYWORD)
+PG_KEYWORD("inclusive", INCLUSIVE, UNRESERVED_KEYWORD)
PG_KEYWORD("increment", INCREMENT, UNRESERVED_KEYWORD)
PG_KEYWORD("index", INDEX, UNRESERVED_KEYWORD)
PG_KEYWORD("indexes", INDEXES, UNRESERVED_KEYWORD)
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index a13c6fb..3d45663 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -65,7 +65,8 @@ typedef enum ParseExprKind
EXPR_KIND_EXECUTE_PARAMETER, /* parameter value in EXECUTE */
EXPR_KIND_TRIGGER_WHEN, /* WHEN condition in CREATE TRIGGER */
EXPR_KIND_POLICY, /* USING or WITH CHECK expr in policy */
- EXPR_KIND_PARTITION_KEY /* partition key expression */
+ EXPR_KIND_PARTITION_KEY, /* partition key expression */
+ EXPR_KIND_PARTITION_FOR_VALUES /* partition bound */
} ParseExprKind;
--
1.7.1
[text/x-diff] 0004-Infrastructure-for-partition-metadata-storage-and-ma-v2.patch (97.7K, ../../571759D8.7030205@lab.ntt.co.jp/5-0004-Infrastructure-for-partition-metadata-storage-and-ma-v2.patch)
download | inline diff:
From fbfcfc2f940f40f6f3017903bafbc1df568bcdcc Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Fri, 18 Mar 2016 15:54:17 +0900
Subject: [PATCH 4/5] Infrastructure for partition metadata storage and manipulation.
Catalog pg_partition with:
partrelid oid
partbound pg_node_tree
Now since partition metadata is stored in a dedicated catalog, it is possible
to use that knowledge to enforce special behaviors for inheritance children
(or relations in general) that are partitions. Some such behaviors include:
* Cannot drop using DROP TABLE without first "detaching" from its parent
* Cannot add, drop new columns
* Cannot rename, change type of existing columns
It also becomes now possible to check, from the FOR VALUES specification, that
the new partition (in CREATE TABLE PARTITION OF parent or ALTER TABLE parent
ATTACH PARTITION) does not overlap with any existing partition of parent.
One can get a partition's check expression (that is, an expression tree that
can be used as a check constraint for the rows in it). ExecConstraints() now
checks using that expression, whether a row being inserted or new row value in
update case, violate the partition boundary specification. Also, optimizer's
constraint exclusion uses it remove useless partitions. This expression tree
is cached in the partition's relcache.
When attaching a partition, rows in the table are checked whether any violates
the partition bound specification (AND'ed with the parent's check expression,
if parent itself is a partition), unless NO VALIDATE clause is specified with
the command.
This commit also invents an internal representation of partition bounds in
the form of structs PartitionListBound and PartitionRangeBound, which consist
of datums and information like number of values in a list, booleans for
whether a range bound is infinite, inclusive (vs. exclusive), lower bound
(vs. upper bound), etc. One can get "partition descriptor" for a partitioned
table, which comes in the form of a struct PartitionDesc. It consists of
of an array of oids of partitions, array of pointers to PartitionListBound
structs (NULL if not list partitioned, and 2 arrays of pointers to
PartitionRangeBound structs, (each NULL, if not range partitioned).
A pointer to this structure is also cached in relcache.
One more data structure that this commit invents for future use is struct
PartitionDescNode. It represents a node in partition tree where each node
of the tree corresponds to a partitioned table in the tree. The tree
structure is such that a parent node links to the first child node (which
corresponds with the first partition of the parent that is partitioned
itself), which in turn, links to its right sibling (the next partition
that is partitioned) and so on. Each node consists of some book-keeping
information including a pointer to PartitionDesc of the corresponding table
and recursive pointers just mentioned viz. downlink and next.
Support to route tuples inserted into a partitioned parent to the appropriate
leaf partition will be added shortly which leverages PartitionDescNode.
---
doc/src/sgml/catalogs.sgml | 56 +
src/backend/catalog/Makefile | 2 +-
src/backend/catalog/heap.c | 6 +
src/backend/catalog/partition.c | 1502 ++++++++++++++++++++++++++++
src/backend/commands/copy.c | 2 +-
src/backend/commands/lockcmds.c | 4 +-
src/backend/commands/tablecmds.c | 210 ++++-
src/backend/executor/execMain.c | 75 ++-
src/backend/executor/nodeModifyTable.c | 4 +-
src/backend/nodes/readfuncs.c | 33 +
src/backend/optimizer/util/plancat.c | 13 +
src/backend/parser/analyze.c | 1 +
src/backend/parser/parse_clause.c | 4 +-
src/backend/parser/parse_relation.c | 3 +-
src/backend/parser/parse_utilcmd.c | 6 +
src/backend/utils/adt/ruleutils.c | 78 ++
src/backend/utils/cache/relcache.c | 7 +
src/backend/utils/cache/syscache.c | 12 +
src/bin/pg_dump/common.c | 86 ++
src/bin/pg_dump/pg_dump.c | 98 ++-
src/bin/pg_dump/pg_dump.h | 12 +
src/bin/psql/describe.c | 46 +-
src/include/catalog/indexing.h | 3 +
src/include/catalog/partition.h | 57 ++
src/include/catalog/pg_partition.h | 54 +
src/include/nodes/execnodes.h | 4 +
src/include/utils/rel.h | 4 +
src/include/utils/syscache.h | 1 +
src/test/regress/expected/sanity_check.out | 1 +
29 files changed, 2348 insertions(+), 36 deletions(-)
create mode 100644 src/include/catalog/pg_partition.h
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index ba658b1..089fa5b 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -224,6 +224,11 @@
</row>
<row>
+ <entry><link linkend="catalog-pg-partition"><structname>pg_partition</structname></link></entry>
+ <entry>information about partitions</entry>
+ </row>
+
+ <row>
<entry><link linkend="catalog-pg-partitioned"><structname>pg_partitioned</structname></link></entry>
<entry>information about partitioned tables, including the partition key</entry>
</row>
@@ -4687,6 +4692,57 @@
</sect1>
+ <sect1 id="catalog-pg-partition">
+ <title><structname>pg_partition</structname></title>
+
+ <indexterm zone="catalog-pg-partition">
+ <primary>pg_partition</primary>
+ </indexterm>
+
+ <para>
+ The catalog <structname>pg_partition</structname> stores partition bounds
+ of tables that are partitions.
+ </para>
+
+ <table>
+ <title><structname>pg_partition</> Columns</title>
+
+ <tgroup cols="4">
+ <thead>
+ <row>
+ <entry>Name</entry>
+ <entry>Type</entry>
+ <entry>References</entry>
+ <entry>Description</entry>
+ </row>
+ </thead>
+
+ <tbody>
+
+ <row>
+ <entry><structfield>partrelid</structfield></entry>
+ <entry><type>oid</type></entry>
+ <entry><literal><link linkend="catalog-pg-class"><structname>pg_class</structname></link>.oid</literal></entry>
+ <entry>The OID of the <structname>pg_class</> entry for this partition</entry>
+ </row>
+
+ <row>
+ <entry><structfield>partbound</structfield></entry>
+ <entry><type>pg_node_tree</type></entry>
+ <entry></entry>
+ <entry>
+ Expression tree (in <function>nodeToString()</function> representation)
+ for partition boundary value. For list partitions, it is simply a list
+ of values. For range partitions, it consists of lower and upper bound
+ values along with their inclusivity.
+ </entry>
+ </row>
+
+ </tbody>
+ </tgroup>
+ </table>
+ </sect1>
+
<sect1 id="catalog-pg-partitioned">
<title><structname>pg_partitioned</structname></title>
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index 4dc6300..ee1be31 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -41,7 +41,7 @@ POSTGRES_BKI_SRCS = $(addprefix $(top_srcdir)/src/include/catalog/,\
pg_foreign_data_wrapper.h pg_foreign_server.h pg_user_mapping.h \
pg_foreign_table.h pg_policy.h pg_replication_origin.h \
pg_default_acl.h pg_init_privs.h pg_seclabel.h pg_shseclabel.h \
- pg_collation.h pg_range.h pg_transform.h pg_partitioned.h\
+ pg_collation.h pg_range.h pg_transform.h pg_partitioned.h pg_partition.h\
toasting.h indexing.h \
)
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 932a48b..2e99661 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -1808,7 +1808,13 @@ heap_drop_with_catalog(Oid relid)
* If a partitioned table, delete the pg_partitioned tuples.
*/
if (rel->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+ {
RemovePartitionKeyByRelId(relid);
+ RelationDropPartitions(relid);
+ }
+
+ if (relid_is_partition(relid))
+ RemovePartitionEntryByRelId(relid);
/*
* Schedule unlinking of the relation's physical files at commit.
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index 5e238dc..175a077 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -23,12 +23,17 @@
#include "catalog/objectaddress.h"
#include "catalog/partition.h"
#include "catalog/pg_collation.h"
+#include "catalog/pg_inherits.h"
+#include "catalog/pg_inherits_fn.h"
#include "catalog/pg_opclass.h"
#include "catalog/pg_partitioned.h"
+#include "catalog/pg_partition.h"
#include "catalog/pg_type.h"
#include "executor/executor.h"
#include "miscadmin.h"
+#include "nodes/makefuncs.h"
#include "nodes/nodeFuncs.h"
+#include "nodes/parsenodes.h"
#include "optimizer/clauses.h"
#include "optimizer/planmain.h"
#include "storage/lmgr.h"
@@ -41,10 +46,88 @@
#include "utils/lsyscache.h"
#include "utils/syscache.h"
+/*
+ * Information about a single partition
+ */
+typedef struct PartitionInfo
+{
+ Oid oid; /* partition OID */
+ PartitionListBound *list; /* list partition bound */
+ PartitionRangeBound *rangelower; /* range partition lower bound */
+ PartitionRangeBound *rangeupper; /* range partition upper bound */
+} PartitionInfo;
+
+typedef struct translate_var_attno_mutator_context
+{
+ AttrNumber old_attno;
+ AttrNumber new_attno;
+} translate_var_attno_mutator_context;
+
+/*
+ * Information about partitions of a partitioned table.
+ */
+typedef struct PartitionDescData
+{
+ int nparts; /* number of partitions */
+ Oid *oids; /* OIDs of the partitions */
+ PartitionListBound **lists; /* list bounds */
+ PartitionRangeBound **rangelowers; /* range lower bounds */
+ PartitionRangeBound **rangeuppers; /* range upper bounds */
+} PartitionDescData;
+
static PartitionKey CopyPartitionKey(PartitionKey fromkey);
static KeyTypeCollInfo *copy_key_type_coll_info(int nkeycols,
KeyTypeCollInfo *tcinfo);
static void free_key_type_coll_info(KeyTypeCollInfo *tcinfo);
+static bool list_overlaps_existing_partition(PartitionKey key,
+ PartitionListSpec *list_spec,
+ PartitionDesc pdesc,
+ Oid *with);
+static bool partition_range_empty(PartitionKey key,
+ PartitionRangeSpec *range_spec);
+static int32 compare_range_keys(PartitionKey key, Datum *val1, Datum *val2);
+static bool range_overlaps_existing_partition(PartitionKey key,
+ PartitionRangeSpec *range_spec,
+ PartitionDesc pdesc,
+ Oid *with);
+static PartitionListBound *make_list_from_spec(PartitionKey key, List *values);
+static PartitionRangeBound *make_range_from_spec(PartitionKey key, List *val,
+ bool inclusive, bool lower);
+static bool partition_range_overlaps(PartitionKey key,
+ PartitionRangeBound *part1_lower,
+ PartitionRangeBound *part1_upper,
+ PartitionRangeBound *part2_lower,
+ PartitionRangeBound *part2_upper);
+static int32 partition_range_cmp_bounds(PartitionKey key,
+ PartitionRangeBound *b1,
+ PartitionRangeBound *b2);
+
+static PartitionDesc RelationGetPartitionDesc(Relation relation);
+static PartitionDesc GetPartitionDescCopy(PartitionKey key, PartitionDesc src);
+static PartitionInfo **GetRelPartitions(Relation rel, int *nparts);
+static List *get_partitions(Oid relid, int lockmode);
+static int32 range_partition_cmp_upper(const void *a, const void *b,
+ void *arg);
+static PartitionListBound *copyPartitionListBound(PartitionListBound *src,
+ PartitionKey key);
+static PartitionRangeBound *copyPartitionRangeBound(PartitionRangeBound *src,
+ PartitionKey key);
+static void free_partition_info_array(PartitionInfo **p, int num);
+
+static Expr *get_check_expr_for_list(PartitionKey key, List *values);
+static Expr *get_check_expr_for_range(PartitionKey key,
+ List *lowervalues,
+ bool lowerinc,
+ List *uppervalues,
+ bool upperinc);
+static Node *translate_var_attno(Node *expr, AttrNumber attno,
+ AttrNumber new_attno);
+static Node *translate_var_attno_mutator(Node *node,
+ translate_var_attno_mutator_context *cxt);
+static Node *generate_partition_check_expr(Relation rel);
+
+static PartitionDescNode GetPartitionDescNodeRecurse(Relation rel, int offset);
+static int get_leaf_partition_count(PartitionDescNode pdnode);
/*
* StorePartitionKey
@@ -449,3 +532,1422 @@ FreePartitionKey(PartitionKey key)
free_key_type_coll_info(key->tcinfo);
pfree(key);
}
+
+/*
+ * StorePartitionBound
+ *
+ * Store partition boundary info in pg_partition after checking for its
+ * validity.
+ */
+void
+StorePartitionBound(Oid relid, Oid parentId, Node *bound)
+{
+ Relation pg_partition;
+ Relation parent;
+ PartitionKey key;
+ PartitionDesc pdesc;
+ Oid with;
+ HeapTuple tuple;
+ char *boundString;
+ Datum values[Natts_pg_partition];
+ bool nulls[Natts_pg_partition];
+ ParseState *pstate = make_parsestate(NULL);
+
+ parent = heap_open(parentId, AccessShareLock);
+ key = parent->rd_partkey;
+ pdesc = RelationGetPartitionDesc(parent);
+
+ /*
+ * Check if 'bound' happens to overlap with any of parent's existing
+ * partitions.
+ */
+ switch (key->strategy)
+ {
+ case PARTITION_STRAT_LIST:
+ {
+ PartitionListSpec *list;
+
+ Assert(IsA(bound, PartitionListSpec));
+ list = (PartitionListSpec *) bound;
+ if (list_overlaps_existing_partition(key, list, pdesc, &with))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("new partition's list of values overlaps with that"
+ " of partition \"%s\" of \"%s\"",
+ get_rel_name(with), get_rel_name(parentId)),
+ errhint("Please specify a list of values that does not"
+ " overlap with any existing list partition's list."),
+ parser_errposition(pstate, list->location)));
+ break;
+ }
+
+ case PARTITION_STRAT_RANGE:
+ {
+ PartitionRangeSpec *range;
+
+ Assert(IsA(bound, PartitionRangeSpec));
+ range = (PartitionRangeSpec *) bound;
+ if (partition_range_empty(key, range))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("new partition's range contains no element"),
+ errhint("Please specify a non-empty range."),
+ parser_errposition(pstate, range->location)));
+
+ if (range_overlaps_existing_partition(key, range, pdesc, &with))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("new partition's range overlaps with that of"
+ " partition \"%s\" of \"%s\"",
+ get_rel_name(with), get_rel_name(parentId)),
+ errhint("Please specify a range that does not overlap"
+ " with any existing partition's range."),
+ parser_errposition(pstate, range->location)));
+ break;
+ }
+ }
+
+ pg_partition = heap_open(PartitionRelationId, RowExclusiveLock);
+
+ boundString = nodeToString(bound);
+
+ /* Build the tuple - bound can never be null */
+ MemSet(nulls, false, sizeof(nulls));
+
+ values[Anum_pg_partition_partrelid - 1] = ObjectIdGetDatum(relid);
+ values[Anum_pg_partition_partbound - 1] = CStringGetTextDatum(boundString);
+
+ tuple = heap_form_tuple(RelationGetDescr(pg_partition), values, nulls);
+
+ simple_heap_insert(pg_partition, tuple);
+
+ /* Update the indexes on pg_partition */
+ CatalogUpdateIndexes(pg_partition, tuple);
+
+ /*
+ * Invalid the parent's relcache so that the partition is now included in
+ * its partition descriptor.
+ */
+ CacheInvalidateRelcache(parent);
+
+ heap_close(parent, AccessShareLock);
+ heap_close(pg_partition, RowExclusiveLock);
+}
+
+/*
+ * Does new list partition 'bound' overlap any of existing list partitions?
+ */
+static bool
+list_overlaps_existing_partition(PartitionKey key,
+ PartitionListSpec *list_spec,
+ PartitionDesc pdesc,
+ Oid *with)
+{
+ PartitionListBound *new_list;
+ int i;
+
+ if (pdesc->nparts == 0)
+ return false;
+
+ new_list = make_list_from_spec(key, list_spec->values);
+
+ for (i = 0; i < pdesc->nparts; i++)
+ {
+ int j;
+
+ if (pdesc->lists[i]->contains_null && new_list->contains_null)
+ return true;
+
+ for (j = 0; j < pdesc->lists[i]->nvalues; j++)
+ {
+ int k;
+
+ for (k = 0; k < new_list->nvalues; k++)
+ {
+ int32 result;
+
+ result = DatumGetInt32(FunctionCall2Coll(&key->partsupfunc[0],
+ key->tcinfo->typcoll[0],
+ pdesc->lists[i]->values[j],
+ new_list->values[k]));
+ if (!result)
+ {
+ *with = pdesc->oids[i];
+ return true;
+ }
+ }
+ }
+ }
+
+ return false;
+}
+
+/*
+ * Is new partition's range empty?
+ */
+static bool
+partition_range_empty(PartitionKey key, PartitionRangeSpec *range_spec)
+{
+ PartitionRangeBound *lower,
+ *upper;
+
+ lower = make_range_from_spec(key, range_spec->lower, range_spec->lowerinc,
+ true);
+ upper = make_range_from_spec(key, range_spec->upper, range_spec->upperinc,
+ false);
+
+ /*
+ * Only either lower or upper is allowed to be unbounded, which if
+ * one of them is, the range is not empty.
+ */
+ if (lower->infinite || upper->infinite)
+ return false;
+
+ /*
+ * If upper > lower, ouright empty or if lower = upper and either of
+ * them is exclusive.
+ */
+ if (compare_range_keys(key, upper->val, lower->val) < 0 ||
+ (compare_range_keys(key, lower->val, upper->val) == 0 &&
+ (!lower->inclusive || !upper->inclusive)))
+ return true;
+
+ return false;
+}
+
+/* Compare range composite keys using logic similar to RowCompareExpr */
+static int32
+compare_range_keys(PartitionKey key, Datum *val1, Datum *val2)
+{
+ int32 result;
+ int i;
+
+ for (i = 0; i < key->partnatts; i++)
+ {
+ result = DatumGetInt32(FunctionCall2Coll(&key->partsupfunc[i],
+ key->tcinfo->typcoll[i],
+ val1[i], val2[i]));
+ if (!result)
+ continue;
+ break;
+ }
+
+ return result;
+}
+
+/*
+ * Does new range partition's range overlap any of existing range partitions?
+ */
+static bool
+range_overlaps_existing_partition(PartitionKey key,
+ PartitionRangeSpec *range_spec,
+ PartitionDesc pdesc,
+ Oid *with)
+{
+ int i;
+ PartitionRangeBound *new_lower,
+ *new_upper;
+
+ if (pdesc->nparts == 0)
+ return false;
+
+ /*
+ * Create internal representation of rangelower and rangeupper from
+ * range_spec.
+ */
+ new_lower = make_range_from_spec(key, range_spec->lower, range_spec->lowerinc,
+ true);
+ new_upper = make_range_from_spec(key, range_spec->upper, range_spec->upperinc,
+ false);
+
+ /* Check with existing partitions for overlap */
+ for (i = 0; i < pdesc->nparts; i++)
+ {
+ if (partition_range_overlaps(key, new_lower, new_upper,
+ pdesc->rangelowers[i],
+ pdesc->rangeuppers[i]))
+ {
+ *with = pdesc->oids[i];
+ return true;
+ }
+ }
+
+ return false;
+}
+
+/*
+ * Make a PartitionListBound from list of values using the information in key
+ */
+static PartitionListBound *
+make_list_from_spec(PartitionKey key, List *values)
+{
+ PartitionListBound *list;
+ ListCell *cell;
+ int i;
+
+ list = (PartitionListBound *) palloc0(sizeof(PartitionListBound));
+ list->nvalues = list_length(values);
+ list->values = (Datum *) palloc(list->nvalues * sizeof(Datum));
+
+ i = 0;
+ foreach (cell, values)
+ {
+ Const *val = lfirst(cell);
+
+ if (val->constisnull)
+ list->contains_null = true;
+ else
+ list->values[i] = datumCopy(val->constvalue,
+ key->tcinfo->typbyval[0],
+ key->tcinfo->typlen[0]);
+ i++;
+ }
+
+ return list;
+}
+
+/*
+ * Make a PartitionRangeBound from arguments using the information in key
+ */
+static PartitionRangeBound *
+make_range_from_spec(PartitionKey key, List *val, bool inclusive, bool lower)
+{
+ PartitionRangeBound *range;
+ ListCell *cell;
+
+ range = (PartitionRangeBound *) palloc0(sizeof(PartitionRangeBound));
+ range->infinite = (val == NIL);
+ range->inclusive = inclusive;
+ range->lower = lower;
+
+ if (val)
+ {
+ int i;
+
+ range->val = (Datum *) palloc0(key->partnatts * sizeof(Datum));
+
+ i = 0;
+ foreach (cell, val)
+ {
+ Const *val = lfirst(cell);
+
+ if (val->constisnull)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("cannot specify NULL in range bound")));
+ else
+ range->val[i] = datumCopy(val->constvalue,
+ key->tcinfo->typbyval[i],
+ key->tcinfo->typlen[i]);
+ i++;
+ }
+ }
+
+ return range;
+}
+
+/* Check two range partitions for overlap */
+static bool
+partition_range_overlaps(PartitionKey key,
+ PartitionRangeBound *part1_lower,
+ PartitionRangeBound *part1_upper,
+ PartitionRangeBound *part2_lower,
+ PartitionRangeBound *part2_upper)
+{
+ if (partition_range_cmp_bounds(key, part1_lower, part2_lower) >= 0 &&
+ partition_range_cmp_bounds(key, part1_lower, part2_upper) <= 0)
+ return true;
+
+ if (partition_range_cmp_bounds(key, part2_lower, part1_lower) >= 0 &&
+ partition_range_cmp_bounds(key, part2_lower, part1_upper) <= 0)
+ return true;
+
+ return false;
+}
+
+/* Returns for two range partition bounds whether, b1 <=, =, >= b2 */
+static int32
+partition_range_cmp_bounds(PartitionKey key,
+ PartitionRangeBound *b1,
+ PartitionRangeBound *b2)
+{
+ int32 result;
+
+ /*
+ * First, handle cases involving infinity, which don't require invoking
+ * the comparison proc.
+ */
+ if (b1->infinite && b2->infinite)
+ {
+ /*
+ * Both are infinity, so they are equal unless one is lower and the
+ * other not.
+ */
+ if (b1->lower == b2->lower)
+ return 0;
+ else
+ return b1->lower ? -1 : 1;
+ }
+ else if (b1->infinite)
+ return b1->lower ? -1 : 1;
+ else if (b2->infinite)
+ return b2->lower ? 1 : -1;
+
+ /*
+ * Both boundaries are finite, so compare the held values.
+ */
+ result = compare_range_keys(key, b1->val, b2->val);
+
+ /*
+ * If the comparison is anything other than equal, we're done. If they
+ * compare equal though, we still have to consider whether the boundaries
+ * are inclusive or exclusive.
+ */
+ if (result == 0)
+ {
+ if (!b1->inclusive && !b2->inclusive)
+ {
+ /* both are exclusive */
+ if (b1->lower == b2->lower)
+ return 0;
+ else
+ return b1->lower ? 1 : -1;
+ }
+ else if (!b1->inclusive)
+ return b1->lower ? 1 : -1;
+ else if (!b2->inclusive)
+ return b2->lower ? -1 : 1;
+ else
+ {
+ /*
+ * Both are inclusive and the values held are equal, so they are
+ * equal regardless of whether they are upper or lower boundaries,
+ * or a mix.
+ */
+ return 0;
+ }
+ }
+
+ return result;
+}
+
+/*
+ * RemovePartitionByRelId
+ * Remove a pg_partition entry for a partition.
+ */
+void
+RemovePartitionEntryByRelId(Oid relid)
+{
+ Relation rel;
+ HeapTuple tuple;
+
+ rel = heap_open(PartitionRelationId, RowExclusiveLock);
+ tuple = SearchSysCache1(PARTRELID, ObjectIdGetDatum(relid));
+
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "cache lookup failed for partition entry of relation %u",
+ relid);
+
+ simple_heap_delete(rel, &tuple->t_self);
+
+ /* Update the indexes on pg_partitioned_rel */
+ CatalogUpdateIndexes(rel, tuple);
+
+ ReleaseSysCache(tuple);
+ heap_close(rel, RowExclusiveLock);
+}
+
+/*
+ * does relid have pg_partition entry?
+ */
+bool
+relid_is_partition(Oid relid)
+{
+ return SearchSysCacheExists1(PARTRELID, ObjectIdGetDatum(relid));
+}
+
+/*
+ * Returns inheritance parent of relid by scanning pg_inherits
+ */
+Oid
+get_partition_parent(Oid relid)
+{
+ Form_pg_inherits form;
+ Relation catalogRelation;
+ SysScanDesc scan;
+ ScanKeyData key[2];
+ HeapTuple tuple;
+
+ catalogRelation = heap_open(InheritsRelationId, AccessShareLock);
+
+ ScanKeyInit(&key[0],
+ Anum_pg_inherits_inhrelid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(relid));
+ ScanKeyInit(&key[1],
+ Anum_pg_inherits_inhseqno,
+ BTEqualStrategyNumber, F_INT4EQ,
+ Int32GetDatum(1));
+
+ scan = systable_beginscan(catalogRelation, InheritsRelidSeqnoIndexId, true,
+ NULL, 2, key);
+
+ tuple = systable_getnext(scan);
+ Assert(HeapTupleIsValid(tuple));
+
+ form = (Form_pg_inherits) GETSTRUCT(tuple);
+
+ systable_endscan(scan);
+ heap_close(catalogRelation, AccessShareLock);
+
+ return form->inhparent;
+}
+
+/*
+ * RelationDropPartitions
+ * Find and drop all partitions of relation with OID 'relid'
+ */
+void
+RelationDropPartitions(Oid relid)
+{
+ List *partitionOids;
+ ListCell *cell;
+
+ partitionOids = get_partitions(relid, AccessExclusiveLock);
+
+ foreach (cell, partitionOids)
+ {
+ Oid partitionOid = lfirst_oid(cell);
+ ObjectAddress obj;
+
+ obj.classId = RelationRelationId;
+ obj.objectId = partitionOid;
+ obj.objectSubId = 0;
+
+ performDeletion(&obj, DROP_CASCADE, 0);
+ }
+}
+
+/*
+ * RelationGetPartitionDesc
+ * Get the PartitionDesc of a partitioned table
+ *
+ * Memory for the struct is allocated in the caller's memory context while
+ * a copy is cached in rd_partdesc so that we don't compute the result
+ * unnecessarily (We don't want to return a pointer to the relcache copy
+ * though, since it could disappear due to relcache invalidation.)
+ *
+ * Never returns NULL. If there are no partitions yet, returns the struct
+ * with nparts set to 0.
+ */
+static PartitionDesc
+RelationGetPartitionDesc(Relation relation)
+{
+ int i,
+ nparts;
+ PartitionKey key = relation->rd_partkey;
+ MemoryContext oldcxt;
+ PartitionDesc result;
+ PartitionInfo **parts;
+
+ /* Quick copy and exit if already computed the descriptor */
+ if (relation->rd_partdesc != NULL)
+ return GetPartitionDescCopy(key, relation->rd_partdesc);
+
+ result = (PartitionDesc) palloc0(sizeof(PartitionDescData));
+
+ parts = GetRelPartitions(relation, &nparts);
+ result->nparts = nparts;
+
+ if (parts)
+ {
+ result->oids = (Oid *) palloc0(nparts * sizeof(Oid));
+
+ switch (key->strategy)
+ {
+ case PARTITION_STRAT_LIST:
+ result->lists = (PartitionListBound **) palloc0(nparts *
+ sizeof(PartitionListBound *));
+ break;
+
+ case PARTITION_STRAT_RANGE:
+ result->rangelowers = (PartitionRangeBound **) palloc0(nparts *
+ sizeof(PartitionRangeBound *));
+ result->rangeuppers = (PartitionRangeBound **) palloc0(nparts *
+ sizeof(PartitionRangeBound *));
+
+ /* Sort on range->upper */
+ qsort_arg(parts, nparts, sizeof(PartitionInfo *),
+ range_partition_cmp_upper, key);
+ break;
+ }
+
+ for (i = 0; i < nparts; i++)
+ {
+ result->oids[i] = parts[i]->oid;
+
+ if (parts[i]->list)
+ result->lists[i] = copyPartitionListBound(parts[i]->list, key);
+ else
+ {
+ result->rangelowers[i] = copyPartitionRangeBound(parts[i]->rangelower, key);
+ result->rangeuppers[i] = copyPartitionRangeBound(parts[i]->rangeupper, key);
+ }
+ }
+
+ /* Clean up */
+ free_partition_info_array(parts, nparts);
+ }
+
+ /* Save a copy in the relcache */
+ relation->rd_pdcxt = AllocSetContextCreate(CacheMemoryContext,
+ RelationGetRelationName(relation),
+ ALLOCSET_DEFAULT_MINSIZE,
+ ALLOCSET_DEFAULT_INITSIZE,
+ ALLOCSET_DEFAULT_MAXSIZE);
+
+ relation->rd_partdesc = MemoryContextAllocZero(relation->rd_pdcxt,
+ sizeof(PartitionDescData));
+ oldcxt = MemoryContextSwitchTo(relation->rd_pdcxt);
+ relation->rd_partdesc = GetPartitionDescCopy(key, result);
+ MemoryContextSwitchTo(oldcxt);
+
+ return result;
+}
+
+/*
+ * GetPartitionDescCopy
+ * Copy PartitionDesc of a partitioned relation into caller's context
+ *
+ * Like its caller, never returns NULL.
+ */
+static PartitionDesc
+GetPartitionDescCopy(PartitionKey key, PartitionDesc src)
+{
+ PartitionDesc result;
+ int i;
+
+ result = (PartitionDesc) palloc0(sizeof(PartitionDescData));
+ result->nparts = src->nparts;
+
+ /* If there are partitions, actually copy partitions */
+ if (src->nparts > 0)
+ {
+ result->oids = (Oid *) palloc0(result->nparts * sizeof(Oid));
+
+ switch (key->strategy)
+ {
+ case PARTITION_STRAT_LIST:
+ result->lists = (PartitionListBound **) palloc0(src->nparts *
+ sizeof(PartitionListBound *));
+ break;
+
+ case PARTITION_STRAT_RANGE:
+ result->rangelowers = (PartitionRangeBound **) palloc0(src->nparts *
+ sizeof(PartitionRangeBound *));
+ result->rangeuppers = (PartitionRangeBound **) palloc0(src->nparts *
+ sizeof(PartitionRangeBound *));
+ break;
+ }
+
+ for (i = 0; i < src->nparts; i++)
+ {
+ result->oids[i] = src->oids[i];
+
+ switch (key->strategy)
+ {
+ case PARTITION_STRAT_LIST:
+ result->lists[i] = copyPartitionListBound(src->lists[i], key);
+ break;
+
+ case PARTITION_STRAT_RANGE:
+ result->rangelowers[i] = copyPartitionRangeBound(src->rangelowers[i], key);
+ result->rangeuppers[i] = copyPartitionRangeBound(src->rangeuppers[i], key);
+ break;
+ }
+ }
+ }
+
+ return result;
+}
+
+/*
+ * GetRelPartitions
+ * Return information of partitions of rel
+ *
+ * Returns number of partitions in *nparts.
+ */
+static PartitionInfo **
+GetRelPartitions(Relation rel, int *nparts)
+{
+ int i;
+ Relation partrel;
+ List *partoids;
+ ListCell *cell;
+ PartitionInfo **parts;
+ PartitionKey key = rel->rd_partkey;
+
+ partoids = get_partitions(RelationGetRelid(rel), NoLock);
+
+ /* no partitions found */
+ if ((*nparts = list_length(partoids)) == 0)
+ return NULL;
+
+ partrel = heap_open(PartitionRelationId, AccessShareLock);
+
+ parts = (PartitionInfo **) palloc0(*nparts * sizeof(PartitionInfo *));
+
+ i = 0;
+ foreach(cell, partoids)
+ {
+ Oid partrelid = lfirst_oid(cell);
+ HeapTuple tuple;
+ Datum datum;
+ bool isnull;
+ Node *bound;
+
+ tuple = SearchSysCache1(PARTRELID, partrelid);
+ Assert(HeapTupleIsValid(tuple));
+
+ parts[i] = (PartitionInfo *) palloc0(sizeof(PartitionInfo));
+ parts[i]->oid = partrelid;
+
+ datum = fastgetattr(tuple,
+ Anum_pg_partition_partbound,
+ partrel->rd_att,
+ &isnull);
+
+ bound = stringToNode(TextDatumGetCString(datum));
+
+ switch (key->strategy)
+ {
+ case PARTITION_STRAT_LIST:
+ {
+ PartitionListSpec *list_spec;
+
+ Assert(IsA(bound, PartitionListSpec));
+ list_spec = (PartitionListSpec *) bound;
+ parts[i]->list = make_list_from_spec(key, list_spec->values);
+ }
+ break;
+
+ case PARTITION_STRAT_RANGE:
+ {
+ PartitionRangeSpec *range_spec;
+
+ Assert(IsA(bound, PartitionRangeSpec));
+ range_spec = (PartitionRangeSpec *) bound;
+
+ parts[i]->rangelower = make_range_from_spec(key,
+ range_spec->lower,
+ range_spec->lowerinc,
+ true);
+ parts[i]->rangeupper = make_range_from_spec(key,
+ range_spec->upper,
+ range_spec->upperinc,
+ false);
+ }
+ break;
+ }
+
+ ReleaseSysCache(tuple);
+ i++;
+ }
+
+ heap_close(partrel, AccessShareLock);
+
+ return parts;
+}
+
+/*
+ * Wrapper around find_inheritance_children for use by partitioning
+ * related code
+ */
+static List *
+get_partitions(Oid relid, int lockmode)
+{
+ return find_inheritance_children(relid, lockmode);
+}
+
+/*
+ * range_partition_cmp_upper
+ * Compare two range partition PartitionInfos using their rangeupper
+ * bounds
+ *
+ * Used as a callback to qsort_arg for sorting range partitions.
+ */
+static int32
+range_partition_cmp_upper(const void *a, const void *b, void *arg)
+{
+ PartitionRangeBound *b1 = (*(const PartitionInfo **) a)->rangeupper,
+ *b2 = (*(const PartitionInfo **) b)->rangeupper;
+
+ if (b1->infinite || b2->infinite)
+ return b1->infinite ? 1 : -1;
+
+ return compare_range_keys((PartitionKey) arg, b1->val, b2->val);
+}
+
+/*
+ * Helper routine to copy a list partition bound struct
+ */
+static PartitionListBound *
+copyPartitionListBound(PartitionListBound *src, PartitionKey key)
+{
+ int i;
+ PartitionListBound *result;
+
+ result = (PartitionListBound *) palloc0(sizeof(PartitionListBound));
+ result->nvalues = src->nvalues;
+ result->contains_null = src->contains_null;
+ result->values = (Datum *) palloc0(src->nvalues * sizeof(Datum));
+
+ for (i = 0; i < src->nvalues; i++)
+ result->values[i] = datumCopy(src->values[i],
+ key->tcinfo->typbyval[0],
+ key->tcinfo->typlen[0]);
+ return result;
+}
+
+/*
+ * Helper routine to copy a range partition bound struct
+ */
+static PartitionRangeBound *
+copyPartitionRangeBound(PartitionRangeBound *src, PartitionKey key)
+{
+ int i;
+ PartitionRangeBound *result;
+
+ result = (PartitionRangeBound *) palloc0(sizeof(PartitionRangeBound));
+ result->infinite = src->infinite;
+ result->inclusive = src->inclusive;
+ result->lower = src->lower;
+
+ if (src->val)
+ {
+ result->val = (Datum *) palloc0(key->partnatts * sizeof(Datum));
+ for (i = 0; i < key->partnatts; i++)
+ result->val[i] = datumCopy(src->val[i],
+ key->tcinfo->typbyval[i],
+ key->tcinfo->typlen[i]);
+ }
+
+ return result;
+}
+
+/*
+ * Helper routine to deep-free a PartitionInfo array
+ */
+static void
+free_partition_info_array(PartitionInfo **p, int num)
+{
+ int i;
+
+ for (i = 0; i < num; i++)
+ {
+ if (p[i]->list)
+ {
+ pfree(p[i]->list->values);
+ pfree(p[i]->list);
+ }
+
+ if (p[i]->rangelower)
+ {
+ if (p[i]->rangelower->val)
+ pfree(p[i]->rangelower->val);
+ pfree(p[i]->rangelower);
+ }
+
+ if (p[i]->rangeupper)
+ {
+ if (p[i]->rangeupper->val)
+ pfree(p[i]->rangeupper->val);
+ pfree(p[i]->rangeupper);
+ }
+
+ pfree(p[i]);
+ }
+
+ pfree(p);
+}
+
+/*
+ * get_check_expr_from_partbound - for direct use by tablecmds.c
+ */
+Expr *
+get_check_expr_from_partbound(Relation rel, Relation parent, Node *bound)
+{
+ PartitionKey key = parent->rd_partkey;
+ Expr *my_expr;
+ int i;
+
+ if (IsA(bound, PartitionListSpec))
+ {
+ PartitionListSpec *list_spec = (PartitionListSpec *) bound;
+
+ my_expr = get_check_expr_for_list(key, list_spec->values);
+ }
+ else if (IsA(bound, PartitionRangeSpec))
+ {
+ PartitionRangeSpec *range_spec = (PartitionRangeSpec *) bound;
+
+ my_expr = get_check_expr_for_range(key,
+ range_spec->lower,
+ range_spec->lowerinc,
+ range_spec->upper,
+ range_spec->upperinc);
+ }
+
+ /*
+ * Translate vars in the generated expression with the correct attnos.
+ * Note that the vars in my_expr bear attnos dictated by key which carries
+ * attnos of the parent.
+ */
+ for (i = 0; i < key->partnatts; i++)
+ {
+ AttrNumber attno = key->partattrs[i],
+ new_attno;
+ char *attname = get_attname(RelationGetRelid(parent), attno);
+
+ new_attno = get_attnum(RelationGetRelid(rel), attname);
+
+ my_expr = (Expr *) translate_var_attno((Node *) my_expr, attno,
+ new_attno);
+ }
+
+ return my_expr;
+}
+
+/*
+ * get_check_expr_for_list
+ */
+static Expr *
+get_check_expr_for_list(PartitionKey key, List *values)
+{
+ ArrayExpr *values_arr = makeNode(ArrayExpr);
+ ScalarArrayOpExpr *arrayop_expr;
+ Node *key_col;
+ Oid operoid;
+ StrategyNumber strategy = BTEqualStrategyNumber;
+
+ /* Btree equality operator of specified opfamily */
+ operoid = get_opfamily_member(key->partopfamily[0],
+ key->tcinfo->typid[0],
+ key->tcinfo->typid[0],
+ strategy);
+
+ /* lexpr */
+ if (key->partattrs[0] != 0)
+ key_col = (Node *) makeVar(1, key->partattrs[0],
+ key->tcinfo->typid[0],
+ key->tcinfo->typmod[0],
+ key->tcinfo->typcoll[0],
+ 0);
+ else
+ key_col = (Node *) linitial(key->partexprs);
+
+ /* rexpr */
+ values_arr->array_typeid = get_array_type(key->tcinfo->typid[0]);
+ values_arr->array_collid = key->tcinfo->typcoll[0];
+ values_arr->element_typeid = key->tcinfo->typid[0];
+ values_arr->elements = values;
+ values_arr->multidims = false;
+ values_arr->location = -1;
+
+ /* build the actual predicate expression node */
+ arrayop_expr = makeNode(ScalarArrayOpExpr);
+ arrayop_expr->opno = operoid;
+ arrayop_expr->opfuncid = get_opcode(operoid);
+ arrayop_expr->useOr = true;
+ arrayop_expr->inputcollid = key->tcinfo->typcoll[0];
+ arrayop_expr->args = list_make2(key_col, values_arr);
+ arrayop_expr->location = -1;
+
+ return (Expr *) arrayop_expr;
+}
+
+/*
+ * get_check_expr_for_range
+ */
+static Expr *
+get_check_expr_for_range(PartitionKey key,
+ List *lowervalues,
+ bool lowerinc,
+ List *uppervalues,
+ bool upperinc)
+{
+ List *and_args = NIL;
+ ListCell *cell1,
+ *cell2,
+ *partexprs_item;
+ int i;
+ uint16 strategy;
+
+ /*
+ * Handle the case with only one bound.
+ *
+ * In this case, consider only the first column of the key and generate
+ * appropriate opclause since only the comparison with the first column
+ * would have determined whether a rows went into such a partition.
+ * It's always true that -INF < someval and someval < +INF.
+ */
+ if (lowervalues == NIL || uppervalues == NIL)
+ {
+ List *values;
+ Const *key_val;
+ Node *key_col;
+ Oid operoid;
+ bool islower,
+ inclusive;
+
+ if (lowervalues != NIL)
+ {
+ values = lowervalues;
+ islower = true;
+ inclusive = lowerinc;
+ }
+ else
+ {
+ values = uppervalues;
+ islower = false;
+ inclusive = upperinc;
+ }
+
+ /* lexpr */
+ if (key->partattrs[0] != 0)
+ key_col = (Node *) makeVar(1, key->partattrs[0],
+ key->tcinfo->typid[0],
+ key->tcinfo->typmod[0],
+ key->tcinfo->typcoll[0],
+ 0);
+ else
+ key_col = (Node *) linitial(key->partexprs);
+
+ /* rexpr */
+ key_val = linitial(values);
+
+ if (islower)
+ strategy = inclusive ? BTGreaterEqualStrategyNumber : BTGreaterStrategyNumber;
+ else
+ strategy = inclusive ? BTLessEqualStrategyNumber : BTLessStrategyNumber;
+
+ operoid = get_opfamily_member(key->partopfamily[0],
+ key->tcinfo->typid[0],
+ key->tcinfo->typid[0],
+ strategy);
+
+ and_args = lappend(and_args,
+ make_opclause(operoid, BOOLOID,
+ false,
+ (Expr *) key_col,
+ (Expr *) key_val,
+ InvalidOid,
+ key->tcinfo->typcoll[0]));
+
+ return makeBoolExpr(AND_EXPR, and_args, -1);
+ }
+
+ /* Both bounds constrain values going into the partition. */
+ i = 0;
+ partexprs_item = list_head(key->partexprs);
+ forboth (cell1, lowervalues, cell2, uppervalues)
+ {
+ Node *key_col;
+ Const *lower_val = lfirst(cell1);
+ Const *upper_val = lfirst(cell2);
+ Expr *lower_clause,
+ *upper_clause;
+ Oid eqop,
+ lowerop,
+ upperop;
+ EState *estate;
+ MemoryContext oldcxt;
+ Expr *test_expr;
+ ExprState *test_exprstate;
+ Datum test_result;
+ bool isNull;
+
+ /* lexpr */
+ if (key->partattrs[i] != 0)
+ {
+ key_col = (Node *) makeVar(1, key->partattrs[i],
+ key->tcinfo->typid[i],
+ key->tcinfo->typmod[i],
+ key->tcinfo->typcoll[i],
+ 0);
+ }
+ else
+ {
+ key_col = (Node *) lfirst(partexprs_item);
+ partexprs_item = lnext(partexprs_item);
+ }
+
+ /*
+ * If lower_val = upper_val (= val, say), create a column = val
+ * constraint.
+ */
+ eqop = get_opfamily_member(key->partopfamily[i],
+ key->tcinfo->typid[i],
+ key->tcinfo->typid[i],
+ BTEqualStrategyNumber);
+
+ estate = CreateExecutorState();
+ oldcxt = MemoryContextSwitchTo(estate->es_query_cxt);
+ test_expr = make_opclause(eqop,
+ BOOLOID,
+ false,
+ (Expr *) lower_val,
+ (Expr *) upper_val,
+ InvalidOid,
+ key->tcinfo->typcoll[i]);
+ fix_opfuncids((Node *) test_expr);
+ test_exprstate = ExecInitExpr(test_expr, NULL);
+ test_result = ExecEvalExprSwitchContext(test_exprstate,
+ GetPerTupleExprContext(estate),
+ &isNull, NULL);
+ MemoryContextSwitchTo(oldcxt);
+ FreeExecutorState(estate);
+
+ if (DatumGetBool(test_result))
+ {
+ and_args = lappend(and_args,
+ make_opclause(eqop,
+ BOOLOID,
+ false,
+ (Expr *) key_col,
+ /* could've been upper_val */
+ (Expr *) lower_val,
+ InvalidOid,
+ key->tcinfo->typcoll[i]));
+
+ /* Go to the next column. */
+ }
+ else
+ {
+ /*
+ * For all columns but last, use inclusive strategy (<= or >=).
+ *
+ * For the last column, the bound's inclusivity determines.
+ */
+ strategy = i < key->partnatts - 1 || lowerinc
+ ? BTGreaterEqualStrategyNumber
+ : BTGreaterStrategyNumber;
+ lowerop = get_opfamily_member(key->partopfamily[i],
+ key->tcinfo->typid[i],
+ key->tcinfo->typid[i],
+ strategy);
+ strategy = i < key->partnatts - 1 || upperinc
+ ? BTLessEqualStrategyNumber
+ : BTLessStrategyNumber;
+ upperop = get_opfamily_member(key->partopfamily[i],
+ key->tcinfo->typid[i],
+ key->tcinfo->typid[i],
+ strategy);
+
+ lower_clause = make_opclause(lowerop,
+ BOOLOID,
+ false,
+ (Expr *) key_col,
+ (Expr *) lower_val,
+ InvalidOid,
+ key->tcinfo->typcoll[i]);
+ upper_clause = make_opclause(upperop,
+ BOOLOID,
+ false,
+ (Expr *) key_col,
+ (Expr *) upper_val,
+ InvalidOid,
+ key->tcinfo->typcoll[i]);
+
+ and_args = lappend(and_args,
+ makeBoolExpr(AND_EXPR,
+ list_make2(lower_clause, upper_clause), -1));
+
+ /* No need to constraint further columns. */
+ break;
+ }
+
+ i++;
+ }
+
+ return makeBoolExpr(AND_EXPR, and_args, -1);
+}
+
+/*
+ * translate_var_attno
+ * Changes Vars with a given attno in the provided expression tree to
+ * Vars with new_attno
+ */
+static Node *
+translate_var_attno(Node *expr, AttrNumber attno, AttrNumber new_attno)
+{
+ translate_var_attno_mutator_context cxt;
+
+ cxt.old_attno = attno;
+ cxt.new_attno = new_attno;
+
+ return expression_tree_mutator(expr, translate_var_attno_mutator, &cxt);
+}
+
+/*
+ * translate_var_attno_mutator
+ */
+static Node *
+translate_var_attno_mutator(Node *node,
+ translate_var_attno_mutator_context *cxt)
+{
+ if (node == NULL)
+ return NULL;
+
+ if (IsA(node, Var) && ((Var *) node)->varattno == cxt->old_attno)
+ {
+ Var *newvar = copyObject(node);
+
+ newvar->varattno = cxt->new_attno;
+
+ return (Node *) newvar;
+ }
+
+ return expression_tree_mutator(node, translate_var_attno_mutator,
+ (void *) cxt);
+}
+
+/*
+ * RelationGetPartitionConstraint
+ * Returns the equivelent CHECK constraint expression for a partition
+ */
+Node *
+RelationGetPartitionCheckExpr(Relation rel)
+{
+ /* Quick exit */
+ if (!relid_is_partition(RelationGetRelid(rel)))
+ return NULL;
+
+ /* Quick copy */
+ if (rel->rd_partcheck)
+ return copyObject(rel->rd_partcheck);
+
+ /* None of those, generate */
+ return generate_partition_check_expr(rel);
+}
+
+/*
+ * generate_partition_constraints
+ * Generate partition check constraint expression rel's pg_partition
+ * entry
+ *
+ * Result expression tree is stored CacheMemoryContext to ensure it survives
+ * as long as the relcache entry. But we should be running in a less long-lived
+ * working context. To avoid leaking cache memory if this routine fails partway
+ * routine fails partway through, we build in working memory and then copy
+ * the completed structure into cache memory.
+ */
+static Node *
+generate_partition_check_expr(Relation rel)
+{
+ HeapTuple tuple;
+ MemoryContext oldcxt;
+ Datum boundDatum;
+ bool isnull;
+ Node *bound;
+ Node *my_expr = NULL,
+ *result = NULL;
+ Oid relid = RelationGetRelid(rel);
+ Relation parent = heap_open(get_partition_parent(relid), NoLock);
+
+ /* quick copy */
+ if (rel->rd_partcheck)
+ {
+ heap_close(parent, NoLock);
+ return copyObject(rel->rd_partcheck);
+ }
+
+ tuple = SearchSysCache1(PARTRELID, relid);
+ Assert(HeapTupleIsValid(tuple));
+ boundDatum = SysCacheGetAttr(PARTRELID, tuple,
+ Anum_pg_partition_partbound,
+ &isnull);
+ Assert(!isnull);
+ bound = stringToNode(TextDatumGetCString(boundDatum));
+
+ my_expr = (Node *) get_check_expr_from_partbound(rel, parent, bound);
+
+ /*
+ * If parent is also a partition, create a AND_EXPR BoolExpr with just
+ * created expression and parent's check expression, as its args.
+ */
+ if (relid_is_partition(RelationGetRelid(parent)))
+ {
+ Node *parent_expr;
+
+ parent_expr = generate_partition_check_expr(parent);
+ result = (Node *) makeBoolExpr(AND_EXPR,
+ list_make2(parent_expr, my_expr), -1);
+ }
+ else
+ result = my_expr;
+
+ oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
+ rel->rd_partcheck = copyObject(result);
+ MemoryContextSwitchTo(oldcxt);
+
+ ReleaseSysCache(tuple);
+ heap_close(parent, NoLock);
+
+ return result;
+}
+
+/*
+ * RelationGetPartitionDescNode
+ * Recursively form partition node tree rooted at rel's node
+ */
+PartitionDescNode
+RelationGetPartitionDescNode(Relation rel)
+{
+ PartitionDescNode root;
+
+ /*
+ * Recurse to build the tree of partition descriptor nodes with
+ * rel's descriptor as the root of the tree.
+ */
+ root = GetPartitionDescNodeRecurse(rel, 0);
+ root->index = 0;
+ root->num_leaf_partitions = get_leaf_partition_count(root);
+
+ return root;
+}
+
+/*
+ * GetPartitionDescNodeRecurse
+ * Workhorse of RelationGetPartitionDescNode
+ *
+ * offset is the index (0-based) of the first leaf node in the tree, as
+ * as determined and passed by the parent or the left sibling.
+ */
+static PartitionDescNode
+GetPartitionDescNodeRecurse(Relation rel, int offset)
+{
+ PartitionDescNode root,
+ prev;
+ PartitionDesc pdesc = RelationGetPartitionDesc(rel);
+ int i;
+
+ /* First build our own node */
+ root = (PartitionDescNode) palloc0(sizeof(PartitionDescNodeData));
+ root->pdesc = pdesc;
+ root->relid = RelationGetRelid(rel);
+ root->offset = offset;
+ root->downlink = NULL;
+ root->next = NULL;
+
+ /*
+ * Recursively add nodes for partitions that are themselves partitioned,
+ * linking ourselves to the first such node via 'downlink'. Each node
+ * thus created (being on the same level) is linked to the right sibling
+ * via 'next'. For each node, determine the offset to pass and once
+ * created, update its index and num_leaf_partitions.
+ */
+ prev = NULL;
+ for (i = 0; i < pdesc->nparts; i++)
+ {
+ Oid relid = pdesc->oids[i];
+ int offset;
+ Relation rel;
+ PartitionDescNode pdnode;
+
+ if (!relid_is_partitioned(relid))
+ continue;
+
+ rel = heap_open(relid, AccessShareLock);
+
+ /* Offset for the new node */
+ if (prev)
+ /*
+ * Count leaves on the left - prev node has the account of those
+ * on its own left and then there may have been some siblings
+ * in-between that were leaves themselves.
+ */
+ offset = prev->offset + prev->num_leaf_partitions +
+ (i - prev->index - 1);
+ else
+ /*
+ * We're the first child - account of leaf nodes comes from the
+ * parent. Left siblings were all leaves themselves.
+ */
+ offset = root->offset + i;
+
+ pdnode = GetPartitionDescNodeRecurse(rel, offset);
+ pdnode->index = i;
+ pdnode->num_leaf_partitions = get_leaf_partition_count(pdnode);
+
+ heap_close(rel, AccessShareLock);
+
+ /* Found our first child; link to it. */
+ if (root->downlink == NULL)
+ root->downlink = pdnode;
+
+ /* Link new node to the left sibling, if any */
+ if (prev)
+ prev->next = pdnode;
+
+ prev = pdnode;
+ }
+
+ return root;
+}
+
+/*
+ * get_leaf_partition_count
+ * Recursively count the number of leaf partitions in the partition
+ * tree rooted at pdnode
+ */
+static int
+get_leaf_partition_count(PartitionDescNode pdnode)
+{
+ int i;
+ int result = 0;
+ PartitionDescNode node = pdnode->downlink;
+
+ for (i = 0; i < pdnode->pdesc->nparts; i++)
+ {
+ /* Indexes 0..(node->index - 1) are of leaf partitions */
+ if (node && i == node->index)
+ {
+ result += get_leaf_partition_count(node);
+ node = node->next;
+ }
+ else
+ result += 1;
+ }
+
+ return result;
+}
+
+/*
+ * get_leaf_partition_count
+ * Recursively compute the list of OIDs of leaf partitions in the
+ * partition tree rooted at pdnode
+ */
+List *
+get_leaf_partition_oids(PartitionDescNode pdnode)
+{
+ int i;
+ List *result = NIL;
+ Oid *myparts = pdnode->pdesc->oids;
+ int n_myparts = pdnode->pdesc->nparts;
+ PartitionDescNode node = pdnode->downlink;
+
+ for (i = 0; i < n_myparts; i++)
+ {
+ /* Indexes 0..(node->index - 1) are leaf partitions */
+ if (node && i == node->index)
+ {
+ result = list_concat(result, get_leaf_partition_oids(node));
+ node = node->next;
+ }
+ else
+ result = lappend_oid(result, myparts[i]);
+ }
+
+ return result;
+}
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 403738d..14e81de 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -2464,7 +2464,7 @@ CopyFrom(CopyState cstate)
if (!skip_tuple)
{
/* Check the constraints of the tuple */
- if (cstate->rel->rd_att->constr)
+ if (cstate->rel->rd_att->constr || resultRelInfo->ri_PartitionCheck)
ExecConstraints(resultRelInfo, slot, estate);
if (useHeapMultiInsert)
diff --git a/src/backend/commands/lockcmds.c b/src/backend/commands/lockcmds.c
index 62671a8..5120c93 100644
--- a/src/backend/commands/lockcmds.c
+++ b/src/backend/commands/lockcmds.c
@@ -16,6 +16,7 @@
#include "access/heapam.h"
#include "catalog/namespace.h"
+#include "catalog/partition.h"
#include "catalog/pg_inherits_fn.h"
#include "commands/lockcmds.h"
#include "miscadmin.h"
@@ -63,7 +64,8 @@ LockTableCommand(LockStmt *lockstmt)
RangeVarCallbackForLockTable,
(void *) &lockstmt->mode);
- if (recurse)
+ /* Force inheritance recursion, if partitioned table. */
+ if (recurse || relid_is_partitioned(reloid))
LockTableRecurse(reloid, lockstmt->mode, lockstmt->nowait);
}
}
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 6e66405..b763a1a 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -121,6 +121,19 @@ static List *on_commits = NIL;
/*
+ * For AT ATTACH PARTITION <table> FOR VALUES <partition_bound_spec>,
+ *
+ * Struct describing partition check expression corresponding to
+ * <partition_bound_spec> to validate for rows in <table> during
+ * Phase 3 scan.
+ */
+typedef struct PartitionCheck
+{
+ Expr *expr;
+ List *state;
+} PartitionCheck;
+
+/*
* State information for ALTER TABLE
*
* The pending-work queue for an ALTER TABLE is a List of AlteredTableInfo
@@ -163,6 +176,8 @@ typedef struct AlteredTableInfo
Oid newTableSpace; /* new tablespace; 0 means no change */
bool chgPersistence; /* T if SET LOGGED/UNLOGGED is used */
char newrelpersistence; /* if above is true */
+ PartitionCheck *partitionCheck; /* partition check expression for
+ * ATTACH PARTITION validation */
/* Objects to rebuild after completing ALTER TYPE operations */
List *changedConstraintOids; /* OIDs of constraints to rebuild */
List *changedConstraintDefs; /* string definitions of same */
@@ -712,6 +727,19 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
StoreCatalogInheritance(relationId, inheritOids);
/*
+ * Process and store partition bound. Do this before
+ * CommandCounterIncrement() below, so that we are not deemed an existing
+ * partition by StorePartitionBound() (which is basically found out by
+ * querying pg_inherits).
+ */
+ if (stmt->partbound)
+ {
+ Oid parentId = linitial_oid(inheritOids);
+
+ StorePartitionBound(relationId, parentId, stmt->partbound);
+ }
+
+ /*
* We must bump the command counter to make the newly-created relation
* tuple visible for opening.
*/
@@ -995,6 +1023,13 @@ RangeVarCallbackForDropRelation(const RangeVar *rel, Oid relOid, Oid oldRelOid,
if (!OidIsValid(relOid))
return;
+ if (relid_is_partition(relOid))
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("\"%s\" is a partition of \"%s\"", rel->relname,
+ get_rel_name(get_partition_parent(relOid))),
+ errhint("Use ALTER TABLE DETACH PARTITION to be able to drop it.")));
+
tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relOid));
if (!HeapTupleIsValid(tuple))
return; /* concurrently dropped, so nothing to do */
@@ -1086,7 +1121,8 @@ ExecuteTruncate(TruncateStmt *stmt)
rels = lappend(rels, rel);
relids = lappend_oid(relids, myrelid);
- if (recurse)
+ /* Force inheritance recursion, if partitioned table. */
+ if (recurse || relid_is_partitioned(myrelid))
{
ListCell *child;
List *children;
@@ -1810,8 +1846,12 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
Node *expr;
bool found_whole_row;
- /* ignore if the constraint is non-inheritable */
- if (check[i].ccnoinherit)
+ /*
+ * ignore if the constraint is non-inheritable and this is not
+ * the partition case (constraints of partitioned tables are
+ * always present in their partitions)
+ */
+ if (check[i].ccnoinherit && !is_partition)
continue;
/* Adjust Vars to match new table's column numbering */
@@ -2246,6 +2286,11 @@ renameatt_check(Oid myrelid, Form_pg_class classform, bool recursing)
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot rename column of typed table")));
+ if (relid_is_partition(myrelid) && !recursing)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot rename column of a partition")));
+
/*
* Renaming the columns of sequences or toast tables doesn't actually
* break anything from the system's point of view, since internal
@@ -2472,11 +2517,13 @@ renameatt(RenameStmt *stmt)
return InvalidObjectAddress;
}
+ /* Force inheritance recursion, if partitioned table. */
attnum =
renameatt_internal(relid,
stmt->subname, /* old att name */
stmt->newname, /* new att name */
- interpretInhOption(stmt->relation->inhOpt), /* recursive? */
+ interpretInhOption(stmt->relation->inhOpt) ||
+ relid_is_partitioned(relid), /* recursive? */
false, /* recursing? */
0, /* expected inhcount */
stmt->behavior);
@@ -2624,11 +2671,13 @@ RenameConstraint(RenameStmt *stmt)
}
}
+ /* Force inheritance recursion, if partitioned table. */
return
rename_constraint_internal(relid, typid,
stmt->subname,
stmt->newname,
- stmt->relation ? interpretInhOption(stmt->relation->inhOpt) : false, /* recursive? */
+ stmt->relation ? interpretInhOption(stmt->relation->inhOpt) ||
+ relid_is_partitioned(relid) : false, /* recursive? */
false, /* recursing? */
0 /* expected inhcount */ );
@@ -2870,8 +2919,11 @@ AlterTable(Oid relid, LOCKMODE lockmode, AlterTableStmt *stmt)
CheckTableNotInUse(rel, "ALTER TABLE");
+ /* Force inheritance recursion, if partitioned table */
ATController(stmt,
- rel, stmt->cmds, interpretInhOption(stmt->relation->inhOpt),
+ rel, stmt->cmds,
+ interpretInhOption(stmt->relation->inhOpt) ||
+ relid_is_partitioned(RelationGetRelid(rel)),
lockmode);
}
@@ -3990,7 +4042,7 @@ ATRewriteTables(AlterTableStmt *parsetree, List **wqueue, LOCKMODE lockmode)
* Test the current data within the table against new constraints
* generated by ALTER TABLE commands, but don't rebuild data.
*/
- if (tab->constraints != NIL || tab->new_notnull)
+ if (tab->constraints != NIL || tab->new_notnull || tab->partitionCheck)
ATRewriteTable(tab, InvalidOid, lockmode);
/*
@@ -4134,6 +4186,21 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
}
}
+ /*
+ * Build expression execution states for partition check expression
+ * as well.
+ */
+ if (tab->partitionCheck)
+ {
+ List *expr;
+
+ needscan = true;
+ Assert(tab->partitionCheck->expr);
+ expr = list_make1(tab->partitionCheck->expr);
+ tab->partitionCheck->state = (List *)
+ ExecPrepareExpr((Expr *) expr, estate);
+ }
+
foreach(l, tab->newvals)
{
NewColumnValue *ex = lfirst(l);
@@ -4323,6 +4390,12 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
}
}
+ if (tab->partitionCheck &&
+ !ExecQual(tab->partitionCheck->state, econtext, true))
+ ereport(ERROR,
+ (errcode(ERRCODE_CHECK_VIOLATION),
+ errmsg("table contains a row violating the partition bound specification")));
+
/* Write the tuple out to the new relation */
if (newrel)
heap_insert(newrel, tuple, mycid, hi_options, bistate);
@@ -4520,7 +4593,8 @@ ATSimpleRecursion(List **wqueue, Relation rel,
*/
if (recurse &&
(rel->rd_rel->relkind == RELKIND_RELATION ||
- rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE))
+ rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE ||
+ rel->rd_rel->relkind == RELKIND_PARTITIONED_REL))
{
Oid relid = RelationGetRelid(rel);
ListCell *child;
@@ -4842,6 +4916,11 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
if (recursing)
ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
+ if (relid_is_partition(RelationGetRelid(rel)) && !recursing)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot add column to a partition")));
+
attrdesc = heap_open(AttributeRelationId, RowExclusiveLock);
/*
@@ -5297,6 +5376,7 @@ ATExecDropNotNull(Relation rel, const char *colName, LOCKMODE lockmode)
List *indexoidlist;
ListCell *indexoidscan;
ObjectAddress address;
+ Oid relid;
/*
* lookup the attribute
@@ -5364,6 +5444,28 @@ ATExecDropNotNull(Relation rel, const char *colName, LOCKMODE lockmode)
list_free(indexoidlist);
/*
+ * If rel is partition, throw error if the attribute is not null in
+ * parent
+ */
+ relid = RelationGetRelid(rel);
+ if (relid_is_partition(relid))
+ {
+ Oid parentId = get_partition_parent(relid);
+ Relation parent = heap_open(parentId, AccessShareLock);
+ TupleDesc tupDesc = RelationGetDescr(parent);
+ AttrNumber parent_attnum;
+
+ parent_attnum = get_attnum(parentId, colName);
+ if (tupDesc->attrs[parent_attnum - 1]->attnotnull)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("column \"%s\" is not null in parent",
+ colName),
+ errhint("Please drop not null in the parent instead")));
+ heap_close(parent, AccessShareLock);
+ }
+
+ /*
* Okay, actually perform the catalog change ... if needed
*/
if (((Form_pg_attribute) GETSTRUCT(tuple))->attnotnull)
@@ -5893,6 +5995,11 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
if (recursing)
ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
+ if (relid_is_partition(RelationGetRelid(rel)) && !recursing)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot drop column from a partition")));
+
/*
* get the number of the attribute
*/
@@ -5938,11 +6045,13 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
if (!is_expr)
ereport(ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
- errmsg("cannot drop column named in partition key")));
+ errmsg("cannot drop column named in%s partition key",
+ recursing ? " downstream" : "")));
else
ereport(ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
- errmsg("cannot drop column referenced in partition key expression")));
+ errmsg("cannot drop column referenced in%s partition key expression",
+ recursing ? " downstream" : "")));
}
ReleaseSysCache(tuple);
@@ -6376,8 +6485,9 @@ ATAddCheckConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
/*
* If adding a NO INHERIT constraint, no need to find our children.
+ * However, that applies only to regular inheritance parents.
*/
- if (constr->is_no_inherit)
+ if (constr->is_no_inherit && !relid_is_partitioned(RelationGetRelid(rel)))
return address;
/*
@@ -7938,9 +8048,12 @@ ATExecDropConstraint(Relation rel, const char *constrName,
/*
* Propagate to children as appropriate. Unlike most other ALTER
* routines, we have to do this one level of recursion at a time; we can't
- * use find_all_inheritors to do it in one pass.
+ * use find_all_inheritors to do it in one pass. Note that if the parent
+ * is a partitioned table, we propagate to children (partitions) despite
+ * is_no_inherit_constraint.
*/
- if (!is_no_inherit_constraint)
+ if (!is_no_inherit_constraint ||
+ relid_is_partitioned(RelationGetRelid(rel)))
children = find_inheritance_children(RelationGetRelid(rel), lockmode);
else
children = NIL;
@@ -8070,6 +8183,11 @@ ATPrepAlterColumnType(List **wqueue,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot alter column type of typed table")));
+ if (relid_is_partition(RelationGetRelid(rel)) && !recursing)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot alter column type of a partition")));
+
/* lookup the attribute so we can check inheritance status */
tuple = SearchSysCacheAttName(RelationGetRelid(rel), colName);
if (!HeapTupleIsValid(tuple))
@@ -8100,11 +8218,13 @@ ATPrepAlterColumnType(List **wqueue,
if (!is_expr)
ereport(ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
- errmsg("cannot alter column named in partition key")));
+ errmsg("cannot alter column named in%s partition key",
+ recursing ? " downstream" : "")));
else
ereport(ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
- errmsg("cannot alter column referenced in partition key expression")));
+ errmsg("cannot alter column referenced in%s partition key expression",
+ recursing ? " downstream" : "")));
}
/* Look up the target type */
@@ -10237,6 +10357,11 @@ ATPrepAddInherit(Relation child_rel)
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot change inheritance of typed table")));
+ if (relid_is_partition(RelationGetRelid(child_rel)))
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot change inheritance of a partition")));
+
if (child_rel->rd_rel->relkind == RELKIND_PARTITIONED_REL)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
@@ -10293,6 +10418,11 @@ ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode)
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot inherit from partitioned table \"%s\"", parent->relname)));
+ if (relid_is_partition(RelationGetRelid(parent_rel)))
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot inherit from a partition")));
+
/*
* Prevent circularity by seeing if proposed parent inherits from child.
* (In particular, this disallows making a rel inherit from itself.)
@@ -10679,6 +10809,11 @@ ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
ObjectAddress address;
Relation parent_rel;
+ if (relid_is_partition(RelationGetRelid(rel)))
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot change inheritance of a partition")));
+
/*
* AccessShareLock on the parent is probably enough, seeing that DROP
* TABLE doesn't lock parent tables at all. We need some lock since we'll
@@ -12568,6 +12703,12 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
*/
ATSimplePermissions(attachRel, ATT_TABLE);
+ if (relid_is_partition(RelationGetRelid(attachRel)))
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("\"%s\" is already a partition",
+ RelationGetRelationName(attachRel))));
+
if (attachRel->rd_rel->reloftype)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
@@ -12601,6 +12742,12 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
errmsg("cannot attach a temporary relation of another session as partition ")));
/* If parent has OIDs, attachRel must have OIDs */
+ if (attachRel->rd_rel->reloftype)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot attach a typed table as partition")));
+
+ /* If parent has OIDs then child must have OIDs */
if (rel->rd_rel->relhasoids && !attachRel->rd_rel->relhasoids)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
@@ -12643,6 +12790,30 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
/* OK to create inheritance. Rest of the checks performed there */
CreateInheritance(attachRel, rel);
+ /*
+ * Set up to have the rows in table to be checked for violation of the
+ * partition bound spec in phase 3 scan.
+ */
+ if (!cmd->skip_validate)
+ {
+ AlteredTableInfo *tab;
+ PartitionCheck *check;
+ Expr *my_expr,
+ *parent_expr = (Expr *) RelationGetPartitionCheckExpr(rel);
+
+ tab = ATGetQueueEntry(wqueue, attachRel);
+ check = (PartitionCheck *) palloc0(sizeof(PartitionCheck));
+ my_expr = get_check_expr_from_partbound(attachRel, rel, cmd->bound);
+ check->expr = parent_expr ? makeBoolExpr(AND_EXPR,
+ list_make2(parent_expr, my_expr), -1)
+ : my_expr;
+ tab->partitionCheck = check;
+ }
+
+ /* Store bound into the catalog */
+ StorePartitionBound(RelationGetRelid(attachRel), RelationGetRelid(rel),
+ cmd->bound);
+
ObjectAddressSet(address, RelationRelationId, RelationGetRelid(attachRel));
/* keep our lock until commit */
@@ -12667,6 +12838,15 @@ ATExecDetachPartition(Relation rel, RangeVar *name)
/* All inheritance related checks are performed within the function */
RemoveInheritance(partRel, rel);
+ /* Remove the pg_partition entry */
+ RemovePartitionEntryByRelId(RelationGetRelid(partRel));
+
+ /*
+ * Invalidate the relcache so that the partition is no longer included
+ * in our partition descriptor.
+ */
+ CacheInvalidateRelcache(rel);
+
ObjectAddressSet(address, RelationRelationId, RelationGetRelid(partRel));
/* keep our lock until commit */
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 67492a5..23ed0b6 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -42,6 +42,7 @@
#include "access/transam.h"
#include "access/xact.h"
#include "catalog/namespace.h"
+#include "catalog/partition.h"
#include "commands/matview.h"
#include "commands/trigger.h"
#include "executor/execdebug.h"
@@ -1250,6 +1251,8 @@ InitResultRelInfo(ResultRelInfo *resultRelInfo,
resultRelInfo->ri_ConstraintExprs = NULL;
resultRelInfo->ri_junkFilter = NULL;
resultRelInfo->ri_projectReturning = NULL;
+ resultRelInfo->ri_PartitionCheck = (Expr *)
+ RelationGetPartitionCheckExpr(resultRelationDesc);
}
/*
@@ -1683,6 +1686,49 @@ ExecRelCheck(ResultRelInfo *resultRelInfo,
return NULL;
}
+/*
+ * ExecPartitionCheck --- check that tuple meets the partition boundary
+ * specification.
+ *
+ * Note: This is called, *iff* resultRelInfo is the main target table.
+ */
+static bool
+ExecPartitionCheck(ResultRelInfo *resultRelInfo, TupleTableSlot *slot,
+ EState *estate)
+{
+ ExprContext *econtext;
+
+ /*
+ * If first time through, build expression state tree for the partition
+ * check expression. Keep it in the per-query memory context so they'll
+ * survive throughout the query.
+ */
+ if (resultRelInfo->ri_PartitionCheckExpr == NULL)
+ {
+ List *qual = make_ands_implicit(resultRelInfo->ri_PartitionCheck);
+ resultRelInfo->ri_PartitionCheckExpr = (List *)
+ ExecPrepareExpr((Expr *) qual, estate);
+ }
+
+ /*
+ * We will use the EState's per-tuple context for evaluating constraint
+ * expressions (creating it if it's not already there).
+ */
+ econtext = GetPerTupleExprContext(estate);
+
+ /* Arrange for econtext's scan tuple to be the tuple under test */
+ econtext->ecxt_scantuple = slot;
+
+ /*
+ * NOTE: SQL specifies that a NULL result from a constraint expression
+ * is not to be treated as a failure. Therefore, tell ExecQual to
+ * return TRUE for NULL.
+ *
+ * XXX - although, it's unlikely that NULL would result.
+ */
+ return ExecQual(resultRelInfo->ri_PartitionCheckExpr, econtext, true);
+}
+
void
ExecConstraints(ResultRelInfo *resultRelInfo,
TupleTableSlot *slot, EState *estate)
@@ -1694,9 +1740,9 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
Bitmapset *insertedCols;
Bitmapset *updatedCols;
- Assert(constr);
+ Assert(constr || resultRelInfo->ri_PartitionCheck);
- if (constr->has_not_null)
+ if (constr && constr->has_not_null)
{
int natts = tupdesc->natts;
int attrChk;
@@ -1727,7 +1773,7 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
}
}
- if (constr->num_check > 0)
+ if (constr && constr->num_check > 0)
{
const char *failed;
@@ -1751,6 +1797,29 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
errtableconstraint(rel, failed)));
}
}
+
+ if (resultRelInfo->ri_PartitionCheck)
+ {
+ if (!ExecPartitionCheck(resultRelInfo, slot, estate))
+ {
+ char *val_desc;
+
+ insertedCols = GetInsertedColumns(resultRelInfo, estate);
+ updatedCols = GetUpdatedColumns(resultRelInfo, estate);
+ modifiedCols = bms_union(insertedCols, updatedCols);
+ val_desc = ExecBuildSlotValueDescription(RelationGetRelid(rel),
+ slot,
+ tupdesc,
+ modifiedCols,
+ 64);
+ ereport(ERROR,
+ (errcode(ERRCODE_CHECK_VIOLATION),
+ errmsg("new row violates the partition boundary"
+ " specification of \"%s\"",
+ RelationGetRelationName(rel)),
+ val_desc ? errdetail("Failing row contains %s.", val_desc) : 0));
+ }
+ }
}
/*
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 7ad1140..6ecb6be 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -354,7 +354,7 @@ ExecInsert(ModifyTableState *mtstate,
/*
* Check the constraints of the tuple
*/
- if (resultRelationDesc->rd_att->constr)
+ if (resultRelationDesc->rd_att->constr || resultRelInfo->ri_PartitionCheck)
ExecConstraints(resultRelInfo, slot, estate);
if (onconflict != ONCONFLICT_NONE && resultRelInfo->ri_NumIndices > 0)
@@ -907,7 +907,7 @@ lreplace:;
/*
* Check the constraints of the tuple
*/
- if (resultRelationDesc->rd_att->constr)
+ if (resultRelationDesc->rd_att->constr || resultRelInfo->ri_PartitionCheck)
ExecConstraints(resultRelInfo, slot, estate);
/*
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 8059594..45f0064 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -2247,6 +2247,35 @@ _readExtensibleNode(void)
}
/*
+ * _readPartitionListSpec
+ */
+static PartitionListSpec *
+_readPartitionListSpec(void)
+{
+ READ_LOCALS(PartitionListSpec);
+
+ READ_NODE_FIELD(values);
+
+ READ_DONE();
+}
+
+/*
+ * _readPartitionRangeSpec
+ */
+static PartitionRangeSpec *
+_readPartitionRangeSpec(void)
+{
+ READ_LOCALS(PartitionRangeSpec);
+
+ READ_BOOL_FIELD(lowerinc);
+ READ_NODE_FIELD(lower);
+ READ_BOOL_FIELD(upperinc);
+ READ_NODE_FIELD(upper);
+
+ READ_DONE();
+}
+
+/*
* parseNodeString
*
* Given a character string representing a node tree, parseNodeString creates
@@ -2476,6 +2505,10 @@ parseNodeString(void)
return_value = _readAlternativeSubPlan();
else if (MATCH("EXTENSIBLENODE", 14))
return_value = _readExtensibleNode();
+ else if (MATCH("PARTITIONLISTVALUES", 19))
+ return_value = _readPartitionListSpec();
+ else if (MATCH("PARTITIONRANGE", 14))
+ return_value = _readPartitionRangeSpec();
else
{
elog(ERROR, "badly formatted node string \"%.32s\"...", token);
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index 9c11b09..ed3193b 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -27,6 +27,7 @@
#include "catalog/catalog.h"
#include "catalog/dependency.h"
#include "catalog/heap.h"
+#include "catalog/partition.h"
#include "catalog/pg_am.h"
#include "catalog/pg_constraint.h"
#include "foreign/fdwapi.h"
@@ -1124,6 +1125,7 @@ get_relation_constraints(PlannerInfo *root,
Index varno = rel->relid;
Relation relation;
TupleConstr *constr;
+ Node *pexpr;
/*
* We assume the relation has already been safely locked.
@@ -1203,6 +1205,17 @@ get_relation_constraints(PlannerInfo *root,
}
}
+ /* Append partition check constraint, if any */
+ pexpr = RelationGetPartitionCheckExpr(relation);
+ if (pexpr)
+ {
+ /* Fix Vars to have the desired varno */
+ if (varno != 1)
+ ChangeVarNodes(pexpr, 1, varno, 0);
+
+ result = list_concat(result, make_ands_implicit((Expr *) pexpr));
+ }
+
heap_close(relation, NoLock);
return result;
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 7d2fedf..f55129a 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -25,6 +25,7 @@
#include "postgres.h"
#include "access/sysattr.h"
+#include "catalog/partition.h"
#include "catalog/pg_type.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
diff --git a/src/backend/parser/parse_clause.c b/src/backend/parser/parse_clause.c
index 307e383..5124dbf 100644
--- a/src/backend/parser/parse_clause.c
+++ b/src/backend/parser/parse_clause.c
@@ -202,8 +202,8 @@ setTargetTable(ParseState *pstate, RangeVar *relation,
pstate->p_target_rangetblentry = rte;
/*
- * Override specified inheritance option, if relation is a partitioned
- * table and it's a target of INSERT (ie, alsoSource == false).
+ * For UPDATE/DELETE, force inheritance recursion, if partitioned
+ * table.
*/
if (alsoSource)
rte->inh |= relid_is_partitioned(rte->relid);
diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c
index abcc1d6..b3870f6 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -1218,8 +1218,7 @@ addRangeTableEntry(ParseState *pstate,
* The initial default on access checks is always check-for-READ-access,
* which is the right thing for all except target tables.
*
- * Note: we override the specified inheritance option, if relation is a
- * partitioned table.
+ * Note: We force inheritance recursion, if partitioned table.
*/
rte->lateral = false;
rte->inh = inh || relid_is_partitioned(RelationGetRelid(rel));
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index c0a1bb0..d2273c6 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -32,6 +32,7 @@
#include "catalog/dependency.h"
#include "catalog/heap.h"
#include "catalog/index.h"
+#include "catalog/partition.h"
#include "catalog/namespace.h"
#include "catalog/partition.h"
#include "catalog/pg_am.h"
@@ -3105,6 +3106,11 @@ transformPartitionOf(CreateStmtContext *cxt, Node *bound)
if (parentRel->rd_rel->relhasoids)
cxt->hasoids = true;
+ /*
+ * Create ColumnDefs correponding to parent's attributes as "inherited"
+ * attributes. Also, set is_for_partition whose function is similar to
+ * is_from_type for MergeAttributes()'s purposes.
+ */
tupdesc = RelationGetDescr(parentRel);
for (i = 0; i < tupdesc->natts; i++)
{
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 49f6fad..bf59cc3 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -8159,6 +8159,84 @@ get_rule_expr(Node *node, deparse_context *context,
}
break;
+ case T_PartitionListSpec:
+ {
+ PartitionListSpec *list_spec = (PartitionListSpec *) node;
+ ListCell *cell;
+ char *sep;
+
+ appendStringInfoString(buf, "FOR VALUES");
+
+ appendStringInfoString(buf, " IN (");
+ sep = "";
+ foreach (cell, list_spec->values)
+ {
+ Const *val = lfirst(cell);
+
+ appendStringInfoString(buf, sep);
+ get_const_expr(val, context, -1);
+ sep = ", ";
+ }
+
+ appendStringInfoString(buf, ")");
+ }
+ break;
+
+ case T_PartitionRangeSpec:
+ {
+ PartitionRangeSpec *range_spec = (PartitionRangeSpec *) node;
+ ListCell *cell;
+ char *sep;
+
+ appendStringInfoString(buf, "FOR VALUES");
+
+ appendStringInfoString(buf, " START");
+ if (!range_spec->lower)
+ appendStringInfoString(buf, " UNBOUNDED");
+ else
+ {
+ appendStringInfoString(buf, " (");
+
+ sep = "";
+ foreach (cell, range_spec->lower)
+ {
+ Const *val = lfirst(cell);
+
+ appendStringInfoString(buf, sep);
+ get_const_expr(val, context, -1);
+ sep = ", ";
+ }
+ appendStringInfoString(buf, ")");
+
+ if (!range_spec->lowerinc)
+ appendStringInfoString(buf, " EXCLUSIVE");
+ }
+
+ appendStringInfoString(buf, " END");
+
+ if (!range_spec->upper)
+ appendStringInfoString(buf, " UNBOUNDED");
+ else
+ {
+ appendStringInfoString(buf, " (");
+
+ sep = "";
+ foreach (cell, range_spec->upper)
+ {
+ Const *val = lfirst(cell);
+
+ appendStringInfoString(buf, sep);
+ get_const_expr(val, context, -1);
+ sep = ", ";
+ }
+ appendStringInfoString(buf, ")");
+
+ if (range_spec->upperinc)
+ appendStringInfoString(buf, " INCLUSIVE");
+ }
+ }
+ break;
+
case T_List:
{
char *sep;
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 2841745..05c4f87 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -50,6 +50,7 @@
#include "catalog/pg_database.h"
#include "catalog/pg_namespace.h"
#include "catalog/pg_opclass.h"
+#include "catalog/partition.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_rewrite.h"
#include "catalog/pg_shseclabel.h"
@@ -2055,6 +2056,10 @@ RelationDestroyRelation(Relation relation, bool remember_tupdesc)
if (relation->rd_rsdesc)
MemoryContextDelete(relation->rd_rsdesc->rscxt);
FreePartitionKey(relation->rd_partkey);
+ if (relation->rd_pdcxt)
+ MemoryContextDelete(relation->rd_pdcxt);
+ if (relation->rd_partcheck)
+ pfree(relation->rd_partcheck);
if (relation->rd_fdwroutine)
pfree(relation->rd_fdwroutine);
pfree(relation);
@@ -4984,6 +4989,8 @@ load_relcache_init_file(bool shared)
rel->trigdesc = NULL;
rel->rd_rsdesc = NULL;
rel->rd_partkey = NULL;
+ rel->rd_partdesc = NULL;
+ rel->rd_partcheck = NULL;
rel->rd_indexprs = NIL;
rel->rd_indpred = NIL;
rel->rd_exclops = NULL;
diff --git a/src/backend/utils/cache/syscache.c b/src/backend/utils/cache/syscache.c
index a6563fe..4a5783a 100644
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -49,6 +49,7 @@
#include "catalog/pg_operator.h"
#include "catalog/pg_opfamily.h"
#include "catalog/pg_partitioned.h"
+#include "catalog/pg_partition.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_range.h"
#include "catalog/pg_rewrite.h"
@@ -569,6 +570,17 @@ static const struct cachedesc cacheinfo[] = {
},
8
},
+ {PartitionRelationId, /* PARTRELID */
+ PartitionRelidIndexId,
+ 1,
+ {
+ Anum_pg_partition_partrelid,
+ 0,
+ 0,
+ 0
+ },
+ 128
+ },
{PartitionedRelationId, /* PARTEDRELID */
PartitionedRelidIndexId,
1,
diff --git a/src/bin/pg_dump/common.c b/src/bin/pg_dump/common.c
index 1cbb987..c8e56bd 100644
--- a/src/bin/pg_dump/common.c
+++ b/src/bin/pg_dump/common.c
@@ -68,6 +68,8 @@ static int numextmembers;
static void flagInhTables(TableInfo *tbinfo, int numTables,
InhInfo *inhinfo, int numInherits);
+static void flagPartitions(TableInfo *tblinfo, int numTables,
+ PartInfo *partinfo, int numPartitions);
static void flagInhAttrs(DumpOptions *dopt, TableInfo *tblinfo, int numTables);
static DumpableObject **buildIndexArray(void *objArray, int numObjs,
Size objSize);
@@ -75,6 +77,8 @@ static int DOCatalogIdCompare(const void *p1, const void *p2);
static int ExtensionMemberIdCompare(const void *p1, const void *p2);
static void findParentsByOid(TableInfo *self,
InhInfo *inhinfo, int numInherits);
+static void findPartitionParentByOid(TableInfo *self, PartInfo *partinfo,
+ int numPartitions);
static int strInArray(const char *pattern, char **arr, int arr_size);
@@ -93,8 +97,10 @@ getSchemaData(Archive *fout, int *numTablesPtr)
NamespaceInfo *nspinfo;
ExtensionInfo *extinfo;
InhInfo *inhinfo;
+ PartInfo *partinfo;
int numAggregates;
int numInherits;
+ int numPartitions;
int numRules;
int numProcLangs;
int numCasts;
@@ -232,6 +238,10 @@ getSchemaData(Archive *fout, int *numTablesPtr)
inhinfo = getInherits(fout, &numInherits);
if (g_verbose)
+ write_msg(NULL, "reading partition information\n");
+ partinfo = getPartitions(fout, &numPartitions);
+
+ if (g_verbose)
write_msg(NULL, "reading event triggers\n");
getEventTriggers(fout, &numEventTriggers);
@@ -245,6 +255,11 @@ getSchemaData(Archive *fout, int *numTablesPtr)
write_msg(NULL, "finding inheritance relationships\n");
flagInhTables(tblinfo, numTables, inhinfo, numInherits);
+ /* Link tables to partition parents, mark parents as interesting */
+ if (g_verbose)
+ write_msg(NULL, "finding partition relationships\n");
+ flagPartitions(tblinfo, numTables, partinfo, numPartitions);
+
if (g_verbose)
write_msg(NULL, "reading column info for interesting tables\n");
getTableAttrs(fout, tblinfo, numTables);
@@ -319,6 +334,43 @@ flagInhTables(TableInfo *tblinfo, int numTables,
}
}
+/* flagPartitions -
+ * Fill in parent link fields of every target table that is partition,
+ * and mark parents of partitions as interesting
+ *
+ * modifies tblinfo
+ */
+static void
+flagPartitions(TableInfo *tblinfo, int numTables,
+ PartInfo *partinfo, int numPartitions)
+{
+ int i;
+
+ for (i = 0; i < numTables; i++)
+ {
+ /* Some kinds are never partitions */
+ if (tblinfo[i].relkind == RELKIND_SEQUENCE ||
+ tblinfo[i].relkind == RELKIND_VIEW ||
+ tblinfo[i].relkind == RELKIND_MATVIEW)
+ continue;
+
+ /* Don't bother computing anything for non-target tables, either */
+ if (!tblinfo[i].dobj.dump)
+ continue;
+
+ /* Find the parent TableInfo and save */
+ findPartitionParentByOid(&tblinfo[i], partinfo, numPartitions);
+
+ /* Mark the parent as interesting for getTableAttrs */
+ if (tblinfo[i].partitionOf)
+ {
+ tblinfo[i].partitionOf->interesting = true;
+ addObjectDependency(&tblinfo[i].dobj,
+ tblinfo[i].partitionOf->dobj.dumpId);
+ }
+ }
+}
+
/* flagInhAttrs -
* for each dumpable table in tblinfo, flag its inherited attributes
*
@@ -920,6 +972,40 @@ findParentsByOid(TableInfo *self,
}
/*
+ * findPartitionParentByOid
+ * find a partition's parent in tblinfo[]
+ */
+static void
+findPartitionParentByOid(TableInfo *self, PartInfo *partinfo,
+ int numPartitions)
+{
+ Oid oid = self->dobj.catId.oid;
+ int i;
+
+ for (i = 0; i < numPartitions; i++)
+ {
+ if (partinfo[i].partrelid == oid)
+ {
+ TableInfo *parent;
+
+ parent = findTableByOid(partinfo[i].partparent);
+ if (parent == NULL)
+ {
+ write_msg(NULL, "failed sanity check, parent OID %u of table \"%s\" (OID %u) not found\n",
+ partinfo[i].partparent,
+ self->dobj.name,
+ oid);
+ exit_nicely(1);
+ }
+ self->partitionOf = parent;
+
+ /* While we're at it, also save the partdef */
+ self->partitiondef = partinfo[i].partdef;
+ }
+ }
+}
+
+/*
* parseOidArray
* parse a string of numbers delimited by spaces into a character array
*
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 4c95aca..7337145 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -5986,6 +5986,63 @@ getInherits(Archive *fout, int *numInherits)
}
/*
+ * getPartitions
+ * read all the partition inheritance and partition bound information
+ * from the system catalogs return them in the PartInfo* structure
+ *
+ * numPartitions is set to the number of pairs read in
+ */
+PartInfo *
+getPartitions(Archive *fout, int *numPartitions)
+{
+ PGresult *res;
+ int ntups;
+ int i;
+ PQExpBuffer query = createPQExpBuffer();
+ PartInfo *partinfo;
+
+ int i_partrelid;
+ int i_partparent;
+ int i_partbound;
+
+ /* Make sure we are in proper schema */
+ selectSourceSchema(fout, "pg_catalog");
+
+ /* find all the inheritance information */
+
+ appendPQExpBufferStr(query,
+ "SELECT partrelid, inhparent AS partparent, "
+ " pg_get_expr(partbound, partrelid) AS partbound "
+ "FROM pg_partition, pg_inherits "
+ "WHERE partrelid = inhrelid");
+
+ res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
+
+ ntups = PQntuples(res);
+
+ *numPartitions = ntups;
+
+ partinfo = (PartInfo *) pg_malloc(ntups * sizeof(PartInfo));
+
+ i_partrelid = PQfnumber(res, "partrelid");
+ i_partparent = PQfnumber(res, "partparent");
+ i_partbound = PQfnumber(res, "partbound");
+
+ for (i = 0; i < ntups; i++)
+ {
+ partinfo[i].partrelid = atooid(PQgetvalue(res, i, i_partrelid));
+ partinfo[i].partparent = atooid(PQgetvalue(res, i, i_partparent));
+ partinfo[i].partdef = pg_strdup(PQgetvalue(res, i, i_partbound));
+ }
+
+ PQclear(res);
+
+ destroyPQExpBuffer(query);
+
+ return partinfo;
+}
+
+/*
* getIndexes
* get information about every index on a dumpable table
*
@@ -15133,6 +15190,17 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
if (tbinfo->reloftype && !dopt->binary_upgrade)
appendPQExpBuffer(q, " OF %s", tbinfo->reloftype);
+ if (tbinfo->partitionOf && !dopt->binary_upgrade)
+ {
+ TableInfo *parentRel = tbinfo->partitionOf;
+
+ appendPQExpBuffer(q, " PARTITION OF ");
+ if (parentRel->dobj.namespace != tbinfo->dobj.namespace)
+ appendPQExpBuffer(q, "%s.",
+ fmtId(parentRel->dobj.namespace->dobj.name));
+ appendPQExpBufferStr(q, fmtId(parentRel->dobj.name));
+ }
+
if (tbinfo->relkind != RELKIND_MATVIEW)
{
/* Dump the attributes */
@@ -15161,8 +15229,11 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
(!tbinfo->inhNotNull[j] ||
dopt->binary_upgrade));
- /* Skip column if fully defined by reloftype */
- if (tbinfo->reloftype &&
+ /*
+ * Skip column if fully defined by reloftype or the
+ * partition parent.
+ */
+ if ((tbinfo->reloftype || tbinfo->partitionOf) &&
!has_default && !has_notnull && !dopt->binary_upgrade)
continue;
@@ -15191,7 +15262,8 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
}
/* Attribute type */
- if (tbinfo->reloftype && !dopt->binary_upgrade)
+ if ((tbinfo->reloftype || tbinfo->partitionOf) &&
+ !dopt->binary_upgrade)
{
appendPQExpBufferStr(q, " WITH OPTIONS");
}
@@ -15256,15 +15328,22 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
if (actual_atts)
appendPQExpBufferStr(q, "\n)");
- else if (!(tbinfo->reloftype && !dopt->binary_upgrade))
+ else if (!((tbinfo->reloftype || tbinfo->partitionOf) &&
+ !dopt->binary_upgrade))
{
/*
* We must have a parenthesized attribute list, even though
- * empty, when not using the OF TYPE syntax.
+ * empty, when not using the OF TYPE or PARTITION OF syntax.
*/
appendPQExpBufferStr(q, " (\n)");
}
+ if (tbinfo->partitiondef && !dopt->binary_upgrade)
+ {
+ appendPQExpBufferStr(q, "\n");
+ appendPQExpBufferStr(q, tbinfo->partitiondef);
+ }
+
if (numParents > 0 && !dopt->binary_upgrade)
{
appendPQExpBufferStr(q, "\nINHERITS (");
@@ -15433,6 +15512,15 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
tbinfo->reloftype);
}
+ if (tbinfo->partitionOf)
+ {
+ appendPQExpBufferStr(q, "\n-- For binary upgrade, set up partitions this way.\n");
+ appendPQExpBuffer(q, "ALTER TABLE ONLY %s ATTACH PARTITION %s %s;\n",
+ fmtId(tbinfo->partitionOf->dobj.name),
+ tbinfo->dobj.name,
+ tbinfo->partitiondef);
+ }
+
appendPQExpBufferStr(q, "\n-- For binary upgrade, set heap's relfrozenxid and relminmxid\n");
appendPQExpBuffer(q, "UPDATE pg_catalog.pg_class\n"
"SET relfrozenxid = '%u', relminmxid = '%u'\n"
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 3094425..e3309b6 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -293,6 +293,8 @@ typedef struct _tableInfo
struct _tableDataInfo *dataObj; /* TableDataInfo, if dumping its data */
int numTriggers; /* number of triggers for table */
struct _triggerInfo *triggers; /* array of TriggerInfo structs */
+ struct _tableInfo *partitionOf; /* TableInfo for the partition parent */
+ char *partitiondef; /* partition key definition */
} TableInfo;
typedef struct _attrDefInfo
@@ -433,6 +435,15 @@ typedef struct _inhInfo
Oid inhparent; /* OID of its parent */
} InhInfo;
+/* PartInfo isn't a DumpableObject, just temporary state */
+typedef struct _partInfo
+{
+ Oid partrelid; /* OID of a partition */
+ Oid partparent; /* OID of its parent */
+ char *partdef; /* partition bound definition */
+} PartInfo;
+
+
typedef struct _prsInfo
{
DumpableObject dobj;
@@ -599,6 +610,7 @@ extern ConvInfo *getConversions(Archive *fout, int *numConversions);
extern TableInfo *getTables(Archive *fout, int *numTables);
extern void getOwnedSeqs(Archive *fout, TableInfo tblinfo[], int numTables);
extern InhInfo *getInherits(Archive *fout, int *numInherits);
+extern PartInfo *getPartitions(Archive *fout, int *numPartitions);
extern void getIndexes(Archive *fout, TableInfo tblinfo[], int numTables);
extern void getConstraints(Archive *fout, TableInfo tblinfo[], int numTables);
extern RuleInfo *getRules(Archive *fout, int *numRules);
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 78a1fdb..6bbfc9a 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -1694,6 +1694,34 @@ describeOneTableDetails(const char *schemaname,
}
/* Make footers */
+ if (pset.sversion >= 90600)
+ {
+ /* Get the partition information */
+ PGresult *result;
+ char *parent_name;
+ char *partdef;
+
+ printfPQExpBuffer(&buf,
+ "SELECT inhparent::pg_catalog.regclass, pg_get_expr(partbound, partrelid)"
+ " FROM pg_catalog.pg_partition p"
+ " JOIN pg_catalog.pg_inherits i"
+ " ON p.partrelid = i.inhrelid"
+ " WHERE p.partrelid = '%s';", oid);
+ result = PSQLexec(buf.data);
+ if (!result)
+ goto error_return;
+
+ if (PQntuples(result) > 0)
+ {
+ parent_name = PQgetvalue(result, 0, 0);
+ partdef = PQgetvalue(result, 0, 1);
+ printfPQExpBuffer(&tmpbuf, _("Partition Of: %s %s"), parent_name,
+ partdef);
+ printTableAddFooter(&cont, tmpbuf.data);
+ PQclear(result);
+ }
+ }
+
if (pset.sversion >= 90600 && tableinfo.relkind == 'P')
{
/* Get the partition key information */
@@ -2455,8 +2483,12 @@ describeOneTableDetails(const char *schemaname,
PQclear(result);
}
- /* print inherited tables */
- printfPQExpBuffer(&buf, "SELECT c.oid::pg_catalog.regclass FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i WHERE c.oid=i.inhparent AND i.inhrelid = '%s' ORDER BY inhseqno;", oid);
+ /* print inherited tables (exclude, if parent is a partitioned table) */
+ printfPQExpBuffer(&buf,
+ "SELECT c.oid::pg_catalog.regclass"
+ " FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i"
+ " WHERE c.oid=i.inhparent AND i.inhrelid = '%s'"
+ " AND c.relkind != 'P' ORDER BY inhseqno;", oid);
result = PSQLexec(buf.data);
if (!result)
@@ -2485,9 +2517,15 @@ describeOneTableDetails(const char *schemaname,
PQclear(result);
}
- /* print child tables */
+ /* print child tables (exclude, if parent is a partitioned table) */
if (pset.sversion >= 80300)
- printfPQExpBuffer(&buf, "SELECT c.oid::pg_catalog.regclass FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i WHERE c.oid=i.inhrelid AND i.inhparent = '%s' ORDER BY c.oid::pg_catalog.regclass::pg_catalog.text;", oid);
+ printfPQExpBuffer(&buf,
+ "SELECT c.oid::pg_catalog.regclass"
+ " FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i"
+ " WHERE c.oid=i.inhrelid AND"
+ " i.inhparent = '%s' AND"
+ " EXISTS (SELECT 1 FROM pg_class c WHERE c.oid = '%s' AND c.relkind != 'P')"
+ " ORDER BY c.oid::pg_catalog.regclass::pg_catalog.text;", oid, oid);
else
printfPQExpBuffer(&buf, "SELECT c.oid::pg_catalog.regclass FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i WHERE c.oid=i.inhrelid AND i.inhparent = '%s' ORDER BY c.relname;", oid);
diff --git a/src/include/catalog/indexing.h b/src/include/catalog/indexing.h
index cadb741..73786c0 100644
--- a/src/include/catalog/indexing.h
+++ b/src/include/catalog/indexing.h
@@ -322,6 +322,9 @@ DECLARE_UNIQUE_INDEX(pg_replication_origin_roname_index, 6002, on pg_replication
DECLARE_UNIQUE_INDEX(pg_partitioned_partedrelid_index, 3351, on pg_partitioned using btree(partedrelid oid_ops));
#define PartitionedRelidIndexId 3351
+DECLARE_UNIQUE_INDEX(pg_partition_partrelid_index, 3354, on pg_partition using btree(partrelid oid_ops));
+#define PartitionRelidIndexId 3354
+
/* last step of initialization script: build the indexes declared above */
BUILD_INDICES
diff --git a/src/include/catalog/partition.h b/src/include/catalog/partition.h
index 31dbe17..6204ad2 100644
--- a/src/include/catalog/partition.h
+++ b/src/include/catalog/partition.h
@@ -42,6 +42,53 @@ typedef struct PartitionKeyData
typedef struct PartitionKeyData *PartitionKey;
+/* Internal representation of a list partition bound */
+typedef struct PartitionListBound
+{
+ bool contains_null;
+ int nvalues; /* number of values in the following array */
+ Datum *values; /* values contained in the list */
+} PartitionListBound;
+
+/* Internal representation of a range partition bound */
+typedef struct
+{
+ Datum *val; /* composite bound value, if any */
+ bool infinite; /* bound is +/- infinity */
+ bool inclusive; /* bound is inclusive (vs exclusive) */
+ bool lower; /* this is the lower (vs upper) bound */
+} PartitionRangeBound;
+
+typedef struct PartitionDescData *PartitionDesc;
+
+/*
+ * Partition tree node
+ *
+ * pkinfo PartitionKey executor state
+ * pdesc info about immediate partitions (see
+ PartitionDescData)
+ * index index within 0..(parent_pdesc->nparts - 1)
+ * num_leaf_partitions number of leaf partitions in the partition
+ * tree rooted at this node
+ * offset index (0-based) of the first leaf partition
+ * in the partition tree rooted at this node
+ * downlink link to the leftmost child node
+ * next link to the right sibling node on a given level
+ */
+typedef struct PartitionDescNodeData
+{
+ PartitionDesc pdesc;
+ Oid relid;
+ int index;
+ int offset;
+ int num_leaf_partitions;
+
+ struct PartitionDescNodeData *downlink;
+ struct PartitionDescNodeData *next;
+} PartitionDescNodeData;
+
+typedef struct PartitionDescNodeData *PartitionDescNode;
+
extern void StorePartitionKey(Relation rel,
char strategy,
int16 partnatts,
@@ -53,4 +100,14 @@ extern void RelationBuildPartitionKey(Relation relation);
extern void FreePartitionKey(PartitionKey key);
extern bool relid_is_partitioned(Oid relid);
+extern void StorePartitionBound(Oid relid, Oid parentId, Node *bound);
+extern void RemovePartitionEntryByRelId(Oid relid);
+extern bool relid_is_partition(Oid relid);
+extern Oid get_partition_parent(Oid relid);
+extern void RelationDropPartitions(Oid relid);
+extern Node *RelationGetPartitionCheckExpr(Relation rel);
+extern Expr *get_check_expr_from_partbound(Relation rel, Relation parent,
+ Node *bound);
+extern List *get_leaf_partition_oids(PartitionDescNode pdnode);
+extern PartitionDescNode RelationGetPartitionDescNode(Relation rel);
#endif /* PARTITION_H */
diff --git a/src/include/catalog/pg_partition.h b/src/include/catalog/pg_partition.h
new file mode 100644
index 0000000..d31a6b7
--- /dev/null
+++ b/src/include/catalog/pg_partition.h
@@ -0,0 +1,54 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_partition.h
+ * definition of the system "partition" relation (pg_partition)
+ * along with the relation's initial contents.
+ *
+ *
+ * Portions Copyright (c) 1996-2015, PostgreSQL Global Development Group
+ *
+ * $PostgreSQL: pgsql/src/include/catalog/pg_partition.h $
+ *
+ * NOTES
+ * the genbki.sh script reads this file and generates .bki
+ * information from the DATA() statements.
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_PARTITION_H
+#define PG_PARTITION_H
+
+#include "catalog/genbki.h"
+
+/* ----------------
+ * pg_partitioned_rel definition. cpp turns this into
+ * typedef struct FormData_pg_partitioned_rel
+ * ----------------
+ */
+#define PartitionRelationId 3353
+
+CATALOG(pg_partition,3353) BKI_WITHOUT_OIDS
+{
+ Oid partrelid; /* partition relation OID */
+
+#ifdef CATALOG_VARLEN /* variable-length fields start here */
+ pg_node_tree partbound; /* node representation of partition bound */
+#endif
+} FormData_pg_partition;
+
+/* ----------------
+ * Form_pg_partition corresponds to a pointer to a tuple with
+ * the format of pg_partition relation.
+ * ----------------
+ */
+typedef FormData_pg_partition *Form_pg_partition;
+
+/* ----------------
+ * compiler constants for pg_partition
+ * ----------------
+ */
+#define Natts_pg_partition 2
+#define Anum_pg_partition_partrelid 1
+#define Anum_pg_partition_partbound 2
+
+#endif /* PG_PARTITION_H */
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index ee4e189..74288e2 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -319,6 +319,8 @@ typedef struct JunkFilter
* projectReturning for computing a RETURNING list
* onConflictSetProj for computing ON CONFLICT DO UPDATE SET
* onConflictSetWhere list of ON CONFLICT DO UPDATE exprs (qual)
+ * PartitionCheck partition check expression
+ * PartitionCheckExpr partition check expression state
* ----------------
*/
typedef struct ResultRelInfo
@@ -343,6 +345,8 @@ typedef struct ResultRelInfo
ProjectionInfo *ri_projectReturning;
ProjectionInfo *ri_onConflictSetProj;
List *ri_onConflictSetWhere;
+ Expr *ri_PartitionCheck;
+ List *ri_PartitionCheckExpr;
} ResultRelInfo;
/* ----------------
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index cf91ef8..c3177d7 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -91,6 +91,10 @@ typedef struct RelationData
struct RowSecurityDesc *rd_rsdesc; /* row security policies, or NULL */
struct PartitionKeyData *rd_partkey; /* partition key, or NULL */
+ MemoryContext rd_pdcxt; /* private context for partdesc */
+ struct PartitionDescData *rd_partdesc; /* partitions, or NULL */
+ Node *rd_partcheck; /* partition CHECK constraint expression
+ * tree */
/* data managed by RelationGetIndexList: */
List *rd_indexlist; /* list of OIDs of indexes on relation */
diff --git a/src/include/utils/syscache.h b/src/include/utils/syscache.h
index e727842..4e493e0 100644
--- a/src/include/utils/syscache.h
+++ b/src/include/utils/syscache.h
@@ -72,6 +72,7 @@ enum SysCacheIdentifier
OPEROID,
OPFAMILYAMNAMENSP,
OPFAMILYOID,
+ PARTRELID,
PARTEDRELID,
PROCNAMEARGSNSP,
PROCOID,
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index b1e473d..57bde9d 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -118,6 +118,7 @@ pg_namespace|t
pg_opclass|t
pg_operator|t
pg_opfamily|t
+pg_partition|t
pg_partitioned|t
pg_pltemplate|t
pg_policy|t
--
1.7.1
[text/x-diff] 0005-Introduce-tuple-routing-for-partitioned-tables-v2.patch (27.8K, ../../571759D8.7030205@lab.ntt.co.jp/6-0005-Introduce-tuple-routing-for-partitioned-tables-v2.patch)
download | inline diff:
From 8027df1b8ae354e91801be448bca3070e0a2cfc1 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Fri, 18 Mar 2016 19:48:49 +0900
Subject: [PATCH 5/5] Introduce tuple routing for partitioned tables.
A tuple inserted into a partitioned table using insert or copy is now
routed to the appropriate leaf partition, recursively.
State required for the same is built in advance by BeginCopy() and
ExecInitModifyTable(). It consists of N ResultRelInfos and pointers
to TupleConversionMap structs, where N is the number of leaf partitions
in the multi-level partition tree. One needs only to get a pointer to the
root PartitionDescNode node for the tree. A tuple conversion map is only
allocated if the tuple descriptor of a leaf partition differs from that
of the root table. Partition check expression is not initialized for
leaf partition result relations, as they are redundant in this case.
Whether or not to build one is now determined by new parameter
load_partition_check of InitResultRelInfo().
CopyFrom() or ExecInsert() passes the root node (set up initially, as
just mentioned) along with the tuple slot to get_partition_for_tuple()
which traverses the tree to determine the appropriate leaf partition for
the tuple. It returns the index (0..N) of the leaf partition. Caller
then switches the result relation (ResultRelInfo) appropriately and
performs the tuple conversion, if required, before following up with
the row triggers, constraints, actual heap and index insertion.
* Following requires further consideration:
Both CopyFrom() and ExecInsert() switch to the partition before invoking
BR triggers and back after AR trigger are processed. That ignores parent's
triggers completely. Note that those triggers are not required to be present
in each partition. OTOH, constraints are fine (both check and not null),
since parent's constraints are present in partitions.
---
src/backend/catalog/partition.c | 318 ++++++++++++++++++++++++++++++++
src/backend/commands/copy.c | 97 +++++++++-
src/backend/commands/tablecmds.c | 1 +
src/backend/executor/execMain.c | 44 +++++-
src/backend/executor/nodeModifyTable.c | 79 ++++++++
src/backend/parser/analyze.c | 8 +
src/include/catalog/partition.h | 24 +++
src/include/executor/executor.h | 6 +
src/include/nodes/execnodes.h | 10 +
9 files changed, 582 insertions(+), 5 deletions(-)
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index 175a077..39c30c9 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -129,6 +129,21 @@ static Node *generate_partition_check_expr(Relation rel);
static PartitionDescNode GetPartitionDescNodeRecurse(Relation rel, int offset);
static int get_leaf_partition_count(PartitionDescNode pdnode);
+static PartitionKeyExecInfo *BuildPartitionKeyExecInfo(Relation rel);
+static void FormPartitionKeyDatum(PartitionKeyExecInfo *pkinfo,
+ TupleTableSlot *slot,
+ EState *estate,
+ Datum *values,
+ bool *isnull);
+static int list_partition_for_tuple(PartitionKey key, PartitionDesc pdesc,
+ Datum value, bool isnull);
+static int range_partition_for_tuple(PartitionKey key, PartitionDesc pdesc,
+ Datum *values);
+static int range_partition_bsearch(PartitionKey key, PartitionDesc pdesc,
+ Datum *values);
+static bool rightof(PartitionKey key, Datum *values, PartitionRangeBound *bound);
+static bool leftof(PartitionKey key, Datum *values, PartitionRangeBound *bound);
+
/*
* StorePartitionKey
* Store the partition keys of rel into pg_partitioned catalog
@@ -1834,6 +1849,7 @@ GetPartitionDescNodeRecurse(Relation rel, int offset)
/* First build our own node */
root = (PartitionDescNode) palloc0(sizeof(PartitionDescNodeData));
+ root->pkinfo = BuildPartitionKeyExecInfo(rel);
root->pdesc = pdesc;
root->relid = RelationGetRelid(rel);
root->offset = offset;
@@ -1951,3 +1967,305 @@ get_leaf_partition_oids(PartitionDescNode pdnode)
return result;
}
+
+/* ----------------
+ * BuildPartitionKeyExecInfo
+ * Construct a list of PartitionKeyExecInfo records for an open
+ * relation
+ *
+ * PartitionKeyExecInfo stores the information about the partition key
+ * that's needed when inserting tuples into a partitioned table; especially,
+ * partition key expression state if there are any expression columns in
+ * the partition key. Normally we build a PartitionKeyExecInfo for a
+ * partitioned table just once per command, and then use it for (potentially)
+ * many tuples.
+ * ----------------
+ */
+static PartitionKeyExecInfo *
+BuildPartitionKeyExecInfo(Relation rel)
+{
+ PartitionKeyExecInfo *pkinfo;
+
+ pkinfo = (PartitionKeyExecInfo *) palloc0(sizeof(PartitionKeyExecInfo));
+ pkinfo->pi_Key = CopyPartitionKey(rel->rd_partkey);
+ pkinfo->pi_ExpressionState = NIL;
+
+ return pkinfo;
+}
+
+/*
+ * get_partition_for_tuple
+ * Recursively finds the "leaf" partition for tuple (slot)
+ *
+ * Returns -1 if no partition is found and sets *failed_at to the OID of
+ * the partitioned table whose partition was not found.
+ */
+int
+get_partition_for_tuple(PartitionDescNode pdnode,
+ TupleTableSlot *slot,
+ EState *estate,
+ Oid *failed_at)
+{
+ PartitionKeyExecInfo *pkinfo = pdnode->pkinfo;
+ PartitionDescNode node;
+ Datum values[PARTITION_MAX_KEYS];
+ bool isnull[PARTITION_MAX_KEYS];
+ int i;
+ int index;
+
+ if (pdnode->pdesc->nparts == 0)
+ return -1;
+
+ /* Extract partition key from tuple */
+ Assert(GetPerTupleExprContext(estate)->ecxt_scantuple == slot);
+ FormPartitionKeyDatum(pkinfo, slot, estate, values, isnull);
+
+ /* Disallow nulls, if range partition key */
+ for (i = 0; i < pkinfo->pi_Key->partnatts; i++)
+ if (isnull[i] && pkinfo->pi_Key->strategy == PARTITION_STRAT_RANGE)
+ ereport(ERROR,
+ (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+ errmsg("range partition key contains null")));
+
+ switch (pkinfo->pi_Key->strategy)
+ {
+ case PARTITION_STRAT_LIST:
+ index = list_partition_for_tuple(pkinfo->pi_Key, pdnode->pdesc,
+ values[0], isnull[0]);
+ break;
+
+ case PARTITION_STRAT_RANGE:
+ index = range_partition_for_tuple(pkinfo->pi_Key, pdnode->pdesc,
+ values);
+ break;
+ }
+
+ /* Don't recurse, if no partition exists for the key at this level... */
+ if (index < 0)
+ {
+ *failed_at = pdnode->relid;
+ return index;
+ }
+
+ /* ... or, if the index'th partition is a leaf partition. */
+ if (!relid_is_partitioned(pdnode->pdesc->oids[index]))
+ {
+ PartitionDescNode prev;
+
+ /* Patiently determine own leaf partition index */
+ prev = node = pdnode->downlink;
+ if (node && node->index < index)
+ {
+ while (node)
+ {
+ if (node->index > index)
+ {
+ node = prev;
+ break;
+ }
+
+ prev = node;
+ node = node->next;
+ }
+
+ if (!node)
+ node = prev;
+
+ /*
+ * Account for siblings on left that are partition trees and those
+ * that are leaf partitions themsleves.
+ */
+ return node->offset + node->num_leaf_partitions +
+ (index - node->index - 1);
+ }
+ else
+ /* Only leaf partitions in pdnode->offset..index */
+ return pdnode->offset + index;
+
+ }
+
+ /* Recurse, by locating the index'th partition's node */
+ node = pdnode->downlink;
+ while (node->next != NULL && node->index != index)
+ node = node->next;
+ Assert (node != NULL);
+
+ return get_partition_for_tuple(node, slot, estate, failed_at);
+}
+
+/*
+ * FormPartitionKeyDatum
+ * Construct values[] and isnull[] arrays for partition key columns
+ */
+static void
+FormPartitionKeyDatum(PartitionKeyExecInfo *pkinfo,
+ TupleTableSlot *slot,
+ EState *estate,
+ Datum *values,
+ bool *isnull)
+{
+ ListCell *partexpr_item;
+ int i;
+
+ if (pkinfo->pi_Key->partexprs != NIL && pkinfo->pi_ExpressionState == NIL)
+ {
+ /* First time through, set up expression evaluation state */
+ pkinfo->pi_ExpressionState = (List *)
+ ExecPrepareExpr((Expr *) pkinfo->pi_Key->partexprs,
+ estate);
+ /* Check caller has set up context correctly */
+ Assert(GetPerTupleExprContext(estate)->ecxt_scantuple == slot);
+ }
+
+ partexpr_item = list_head(pkinfo->pi_ExpressionState);
+ for (i = 0; i < pkinfo->pi_Key->partnatts; i++)
+ {
+ AttrNumber keycol = pkinfo->pi_Key->partattrs[i];
+ Datum pkDatum;
+ bool isNull;
+
+ if (keycol != 0)
+ {
+ /* Plain column; get the value directly from the heap tuple */
+ pkDatum = slot_getattr(slot, keycol, &isNull);
+ }
+ else
+ {
+ /* Expression; need to evaluate it */
+ if (partexpr_item == NULL)
+ elog(ERROR, "wrong number of partition key expressions");
+ pkDatum = ExecEvalExprSwitchContext((ExprState *) lfirst(partexpr_item),
+ GetPerTupleExprContext(estate),
+ &isNull,
+ NULL);
+ partexpr_item = lnext(partexpr_item);
+ }
+ values[i] = pkDatum;
+ isnull[i] = isNull;
+ }
+
+ if (partexpr_item != NULL)
+ elog(ERROR, "wrong number of partition key expressions");
+}
+
+/*
+ * list_partition_for_tuple
+ * Find the list partition for a tuple
+ *
+ * Returns -1 if none found.
+ */
+static int
+list_partition_for_tuple(PartitionKey key, PartitionDesc pdesc,
+ Datum value, bool isnull)
+{
+ int i;
+
+ Assert(pdesc->nparts > 0);
+
+ for (i = 0; i < pdesc->nparts; i++)
+ {
+ int j;
+
+ if (isnull)
+ {
+ if (pdesc->lists[i]->contains_null)
+ return i;
+
+ continue;
+ }
+
+ for (j = 0; j < pdesc->lists[i]->nvalues; j++)
+ {
+ int32 cmpval;
+
+ cmpval = DatumGetInt32(FunctionCall2Coll(&key->partsupfunc[0],
+ key->tcinfo->typcoll[0],
+ pdesc->lists[i]->values[j],
+ value));
+ if (!cmpval)
+ return i;
+ }
+ }
+
+ return -1;
+}
+
+/*
+ * range_partition_for_tuple
+ * Search the range partition for a range key ('values')
+ *
+ * Returns -1 if none found.
+ */
+static int
+range_partition_for_tuple(PartitionKey key, PartitionDesc pdesc, Datum *values)
+{
+ Assert(pdesc->nparts > 0);
+
+ return range_partition_bsearch(key, pdesc, values);
+}
+
+/*
+ * range_partition_bsearch
+ * Workhorse of range_partition_for_tuple
+ */
+static int
+range_partition_bsearch(PartitionKey key, PartitionDesc pdesc,
+ Datum *values)
+{
+ int low, high;
+
+ /* Good ol' bsearch */
+ low = 0;
+ high = pdesc->nparts - 1;
+ while (low <= high)
+ {
+ int idx = (low + high) / 2;
+
+ if (pdesc->rangeuppers[idx]->infinite)
+ {
+ if (rightof(key, values, pdesc->rangelowers[idx]))
+ return idx;
+
+ break;
+ }
+ else if (leftof(key, values, pdesc->rangeuppers[idx]))
+ {
+ if (pdesc->rangelowers[idx]->infinite)
+ return idx;
+
+ if (rightof(key, values, pdesc->rangelowers[idx]))
+ return idx;
+
+ high = idx - 1;
+ continue;
+ }
+
+ low = idx + 1;
+ }
+
+ return -1;
+}
+
+/* Does range key lie to the right of partition bound */
+static bool
+rightof(PartitionKey key, Datum *values, PartitionRangeBound *bound)
+{
+ int32 cmpval = compare_range_keys(key, values, bound->val);
+
+ if (!cmpval)
+ return bound->lower ? bound->inclusive : !bound->inclusive;
+
+ return cmpval > 0;
+}
+
+/* Does range key lie to the left of partition bound */
+static bool
+leftof(PartitionKey key, Datum *values, PartitionRangeBound *bound)
+{
+ int32 cmpval = compare_range_keys(key, values, bound->val);
+
+ if (!cmpval)
+ return !bound->lower ? bound->inclusive : !bound->inclusive;
+
+ return cmpval < 0;
+}
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 14e81de..9c9b401 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -161,6 +161,10 @@ typedef struct CopyStateData
ExprState **defexprs; /* array of default att expressions */
bool volatile_defexprs; /* is any of defexprs volatile? */
List *range_table;
+ PartitionDescNode pdnode; /* partition descriptor node tree */
+ ResultRelInfo *partitions;
+ TupleConversionMap **partition_tupconv_maps;
+ int num_partitions;
/*
* These variables are used to reduce overhead in textual COPY FROM.
@@ -1364,6 +1368,50 @@ BeginCopy(bool is_from,
(errcode(ERRCODE_UNDEFINED_COLUMN),
errmsg("table \"%s\" does not have OIDs",
RelationGetRelationName(cstate->rel))));
+
+ /* Initialize state for CopyFrom tuple routing */
+ if (is_from && rel->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+ {
+ List *leaf_part_oids;
+ ListCell *cell;
+ int i;
+ int num_leaf_parts;
+ ResultRelInfo *leaf_rel_rri;
+
+ cstate->pdnode = RelationGetPartitionDescNode(rel);
+ leaf_part_oids = get_leaf_partition_oids(cstate->pdnode);
+ num_leaf_parts = list_length(leaf_part_oids);
+
+ cstate->num_partitions = num_leaf_parts;
+ cstate->partitions = (ResultRelInfo *)
+ palloc0(num_leaf_parts * sizeof(ResultRelInfo));
+ cstate->partition_tupconv_maps = (TupleConversionMap **)
+ palloc0(num_leaf_parts * sizeof(TupleConversionMap *));
+
+ leaf_rel_rri = cstate->partitions;
+ i = 0;
+ foreach(cell, leaf_part_oids)
+ {
+ Relation leaf_rel;
+
+ leaf_rel = heap_open(lfirst_oid(cell), RowExclusiveLock);
+ InitResultRelInfo(leaf_rel_rri,
+ leaf_rel,
+ 1, /* dummy */
+ false, /* no need for partition check */
+ 0);
+
+ if (!equalTupleDescs(tupDesc, RelationGetDescr(leaf_rel)))
+ cstate->partition_tupconv_maps[i] =
+ convert_tuples_by_name(tupDesc,
+ RelationGetDescr(leaf_rel),
+ gettext_noop("could not convert row type"));
+
+ heap_close(leaf_rel, NoLock); /* keep the lock */
+ leaf_rel_rri++;
+ i++;
+ }
+ }
}
else
{
@@ -2216,6 +2264,7 @@ CopyFrom(CopyState cstate)
Datum *values;
bool *nulls;
ResultRelInfo *resultRelInfo;
+ ResultRelInfo *saved_resultRelInfo = NULL;
EState *estate = CreateExecutorState(); /* for ExecConstraints() */
ExprContext *econtext;
TupleTableSlot *myslot;
@@ -2236,7 +2285,8 @@ CopyFrom(CopyState cstate)
Assert(cstate->rel);
- if (cstate->rel->rd_rel->relkind != RELKIND_RELATION)
+ if (cstate->rel->rd_rel->relkind != RELKIND_RELATION &&
+ cstate->rel->rd_rel->relkind != RELKIND_PARTITIONED_REL)
{
if (cstate->rel->rd_rel->relkind == RELKIND_VIEW)
ereport(ERROR,
@@ -2344,6 +2394,7 @@ CopyFrom(CopyState cstate)
InitResultRelInfo(resultRelInfo,
cstate->rel,
1, /* dummy rangetable index */
+ true, /* do load partition check expression */
0);
ExecOpenIndices(resultRelInfo, false);
@@ -2371,6 +2422,7 @@ CopyFrom(CopyState cstate)
if ((resultRelInfo->ri_TrigDesc != NULL &&
(resultRelInfo->ri_TrigDesc->trig_insert_before_row ||
resultRelInfo->ri_TrigDesc->trig_insert_instead_row)) ||
+ cstate->pdnode != NULL ||
cstate->volatile_defexprs)
{
useHeapMultiInsert = false;
@@ -2395,7 +2447,11 @@ CopyFrom(CopyState cstate)
values = (Datum *) palloc(tupDesc->natts * sizeof(Datum));
nulls = (bool *) palloc(tupDesc->natts * sizeof(bool));
- bistate = GetBulkInsertState();
+ if (useHeapMultiInsert)
+ bistate = GetBulkInsertState();
+ else
+ bistate = NULL;
+
econtext = GetPerTupleExprContext(estate);
/* Set up callback to identify error line number */
@@ -2447,6 +2503,31 @@ CopyFrom(CopyState cstate)
slot = myslot;
ExecStoreTuple(tuple, slot, InvalidBuffer, false);
+ /* Determine the partition */
+ saved_resultRelInfo = resultRelInfo;
+ if (cstate->pdnode)
+ {
+ int i_leaf_partition;
+ TupleConversionMap *map;
+
+ econtext->ecxt_scantuple = slot;
+ i_leaf_partition = ExecFindPartition(resultRelInfo,
+ cstate->pdnode,
+ slot,
+ estate);
+ Assert(i_leaf_partition >= 0 &&
+ i_leaf_partition < cstate->num_partitions);
+
+ resultRelInfo = cstate->partitions + i_leaf_partition;
+ estate->es_result_relation_info = resultRelInfo;
+
+ map = cstate->partition_tupconv_maps[i_leaf_partition];
+ if (map)
+ tuple = do_convert_tuple(tuple, map);
+
+ tuple->t_tableOid = RelationGetRelid(resultRelInfo->ri_RelationDesc);
+ }
+
skip_tuple = false;
/* BEFORE ROW INSERT Triggers */
@@ -2497,7 +2578,8 @@ CopyFrom(CopyState cstate)
List *recheckIndexes = NIL;
/* OK, store the tuple and create index entries for it */
- heap_insert(cstate->rel, tuple, mycid, hi_options, bistate);
+ heap_insert(resultRelInfo->ri_RelationDesc,
+ tuple, mycid, hi_options, bistate);
if (resultRelInfo->ri_NumIndices > 0)
recheckIndexes = ExecInsertIndexTuples(slot, &(tuple->t_self),
@@ -2517,6 +2599,12 @@ CopyFrom(CopyState cstate)
* tuples inserted by an INSERT command.
*/
processed++;
+
+ if (saved_resultRelInfo)
+ {
+ resultRelInfo = saved_resultRelInfo;
+ estate->es_result_relation_info = resultRelInfo;
+ }
}
}
@@ -2530,7 +2618,8 @@ CopyFrom(CopyState cstate)
/* Done, clean up */
error_context_stack = errcallback.previous;
- FreeBulkInsertState(bistate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
MemoryContextSwitchTo(oldcontext);
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index b763a1a..a40bc61 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -1242,6 +1242,7 @@ ExecuteTruncate(TruncateStmt *stmt)
InitResultRelInfo(resultRelInfo,
rel,
0, /* dummy rangetable index */
+ false,
0);
resultRelInfo++;
}
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 23ed0b6..b151fb5 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -826,6 +826,7 @@ InitPlan(QueryDesc *queryDesc, int eflags)
InitResultRelInfo(resultRelInfo,
resultRelation,
resultRelationIndex,
+ true,
estate->es_instrument);
resultRelInfo++;
}
@@ -1214,6 +1215,7 @@ void
InitResultRelInfo(ResultRelInfo *resultRelInfo,
Relation resultRelationDesc,
Index resultRelationIndex,
+ bool load_partition_check,
int instrument_options)
{
MemSet(resultRelInfo, 0, sizeof(ResultRelInfo));
@@ -1251,7 +1253,8 @@ InitResultRelInfo(ResultRelInfo *resultRelInfo,
resultRelInfo->ri_ConstraintExprs = NULL;
resultRelInfo->ri_junkFilter = NULL;
resultRelInfo->ri_projectReturning = NULL;
- resultRelInfo->ri_PartitionCheck = (Expr *)
+ if (load_partition_check)
+ resultRelInfo->ri_PartitionCheck = (Expr *)
RelationGetPartitionCheckExpr(resultRelationDesc);
}
@@ -1315,6 +1318,7 @@ ExecGetTriggerResultRel(EState *estate, Oid relid)
InitResultRelInfo(rInfo,
rel,
0, /* dummy rangetable index */
+ true,
estate->es_instrument);
estate->es_trig_target_relations =
lappend(estate->es_trig_target_relations, rInfo);
@@ -2987,3 +2991,41 @@ EvalPlanQualEnd(EPQState *epqstate)
epqstate->planstate = NULL;
epqstate->origslot = NULL;
}
+
+int
+ExecFindPartition(ResultRelInfo *resultRelInfo, PartitionDescNode pdnode,
+ TupleTableSlot *slot, EState *estate)
+{
+ int i_leaf_partition;
+ Oid failed_at;
+
+ i_leaf_partition = get_partition_for_tuple(pdnode, slot, estate,
+ &failed_at);
+
+ if (i_leaf_partition < 0)
+ {
+ Relation rel = resultRelInfo->ri_RelationDesc;
+ char *val_desc;
+ Bitmapset *insertedCols,
+ *updatedCols,
+ *modifiedCols;
+ TupleDesc tupDesc = RelationGetDescr(rel);
+
+ insertedCols = GetInsertedColumns(resultRelInfo, estate);
+ updatedCols = GetUpdatedColumns(resultRelInfo, estate);
+ modifiedCols = bms_union(insertedCols, updatedCols);
+ val_desc = ExecBuildSlotValueDescription(RelationGetRelid(rel),
+ slot,
+ tupDesc,
+ modifiedCols,
+ 64);
+ Assert(OidIsValid(failed_at));
+ ereport(ERROR,
+ (errcode(ERRCODE_CHECK_VIOLATION),
+ errmsg("no partition of relation \"%s\" found for row",
+ get_rel_name(failed_at)),
+ val_desc ? errdetail("Failing row contains %s.", val_desc) : 0));
+ }
+
+ return i_leaf_partition;
+}
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 6ecb6be..18ce333 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -243,6 +243,7 @@ ExecInsert(ModifyTableState *mtstate,
{
HeapTuple tuple;
ResultRelInfo *resultRelInfo;
+ ResultRelInfo *saved_resultRelInfo = NULL;
Relation resultRelationDesc;
Oid newId;
List *recheckIndexes = NIL;
@@ -257,6 +258,31 @@ ExecInsert(ModifyTableState *mtstate,
* get information on the (current) result relation
*/
resultRelInfo = estate->es_result_relation_info;
+
+ saved_resultRelInfo = resultRelInfo;
+
+ if (mtstate->mt_partition_node)
+ {
+ int i_leaf_partition;
+ ExprContext *econtext = GetPerTupleExprContext(estate);
+ TupleConversionMap *map;
+
+ econtext->ecxt_scantuple = slot;
+ i_leaf_partition = ExecFindPartition(resultRelInfo,
+ mtstate->mt_partition_node,
+ slot,
+ estate);
+ Assert(i_leaf_partition >= 0 &&
+ i_leaf_partition < mtstate->mt_num_partitions);
+
+ resultRelInfo = mtstate->mt_partitions + i_leaf_partition;
+ estate->es_result_relation_info = resultRelInfo;
+
+ map = mtstate->mt_partition_tupconv_maps[i_leaf_partition];
+ if (map)
+ tuple = do_convert_tuple(tuple, map);
+ }
+
resultRelationDesc = resultRelInfo->ri_RelationDesc;
/*
@@ -496,6 +522,12 @@ ExecInsert(ModifyTableState *mtstate,
list_free(recheckIndexes);
+ if (saved_resultRelInfo)
+ {
+ resultRelInfo = saved_resultRelInfo;
+ estate->es_result_relation_info = resultRelInfo;
+ }
+
/*
* Check any WITH CHECK OPTION constraints from parent views. We are
* required to do this after testing all constraints and uniqueness
@@ -1550,6 +1582,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
Plan *subplan;
ListCell *l;
int i;
+ Relation rel;
/* check for unsupported flags */
Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));
@@ -1640,6 +1673,52 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
estate->es_result_relation_info = saved_resultRelInfo;
+ /* Build state for INSERT tuple routing */
+ rel = mtstate->resultRelInfo->ri_RelationDesc;
+ if (operation == CMD_INSERT && rel->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+ {
+ int i,
+ num_leaf_parts;
+ List *leaf_part_oids;
+ ListCell *cell;
+ ResultRelInfo *leaf_rel_rri;
+
+ mtstate->mt_partition_node = RelationGetPartitionDescNode(rel);
+ leaf_part_oids = get_leaf_partition_oids(mtstate->mt_partition_node);
+ num_leaf_parts = list_length(leaf_part_oids);
+
+ mtstate->mt_num_partitions = num_leaf_parts;
+ mtstate->mt_partitions = (ResultRelInfo *)
+ palloc0(num_leaf_parts * sizeof(ResultRelInfo));
+ mtstate->mt_partition_tupconv_maps = (TupleConversionMap **)
+ palloc0(num_leaf_parts * sizeof(TupleConversionMap *));
+
+ leaf_rel_rri = mtstate->mt_partitions;
+ i = 0;
+ foreach(cell, leaf_part_oids)
+ {
+ Relation leaf_rel;
+
+ leaf_rel = heap_open(lfirst_oid(cell), RowExclusiveLock);
+ InitResultRelInfo(leaf_rel_rri,
+ leaf_rel,
+ 1, /* dummy */
+ false, /* no need for partition checks */
+ eflags);
+
+ if (!equalTupleDescs(RelationGetDescr(rel),
+ RelationGetDescr(leaf_rel)))
+ mtstate->mt_partition_tupconv_maps[i] =
+ convert_tuples_by_name(RelationGetDescr(rel),
+ RelationGetDescr(leaf_rel),
+ gettext_noop("could not convert row type"));
+
+ heap_close(leaf_rel, NoLock); /* keep the lock */
+ leaf_rel_rri++;
+ i++;
+ }
+ }
+
/*
* Initialize any WITH CHECK OPTION constraints if needed.
*/
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index f55129a..802cd68 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -755,8 +755,16 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
/* Process ON CONFLICT, if any. */
if (stmt->onConflictClause)
+ {
+ /* Bail out if target relation is partitioned table */
+ if (relid_is_partitioned(pstate->p_target_rangetblentry->relid))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("ON CONFLICT clause is not supported with partitioned tables")));
+
qry->onConflict = transformOnConflictClause(pstate,
stmt->onConflictClause);
+ }
/*
* If we have a RETURNING clause, we need to add the target relation to
diff --git a/src/include/catalog/partition.h b/src/include/catalog/partition.h
index 6204ad2..f4840e4 100644
--- a/src/include/catalog/partition.h
+++ b/src/include/catalog/partition.h
@@ -14,6 +14,8 @@
#define PARTITION_H
#include "fmgr.h"
+#include "executor/tuptable.h"
+#include "nodes/execnodes.h"
#include "parser/parse_node.h"
#include "utils/relcache.h"
@@ -62,6 +64,22 @@ typedef struct
typedef struct PartitionDescData *PartitionDesc;
/*
+ * PartitionKeyExecInfo
+ *
+ * This struct holds the information needed to extract partition
+ * column values from a heap tuple.
+ *
+ * Key copy of the rd_partkey of rel
+ * ExpressionState exec state for expressions, or NIL if none
+ */
+typedef struct PartitionKeyExecInfo
+{
+ NodeTag type;
+ PartitionKey pi_Key;
+ List *pi_ExpressionState; /* list of ExprState */
+} PartitionKeyExecInfo;
+
+/*
* Partition tree node
*
* pkinfo PartitionKey executor state
@@ -77,6 +95,7 @@ typedef struct PartitionDescData *PartitionDesc;
*/
typedef struct PartitionDescNodeData
{
+ PartitionKeyExecInfo *pkinfo;
PartitionDesc pdesc;
Oid relid;
int index;
@@ -110,4 +129,9 @@ extern Expr *get_check_expr_from_partbound(Relation rel, Relation parent,
Node *bound);
extern List *get_leaf_partition_oids(PartitionDescNode pdnode);
extern PartitionDescNode RelationGetPartitionDescNode(Relation rel);
+
+extern int get_partition_for_tuple(PartitionDescNode pdnode,
+ TupleTableSlot *slot,
+ EState *estate,
+ Oid *failed_at);
#endif /* PARTITION_H */
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index 44fac27..d4e8b35 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -14,6 +14,7 @@
#ifndef EXECUTOR_H
#define EXECUTOR_H
+#include "catalog/partition.h"
#include "executor/execdesc.h"
#include "nodes/parsenodes.h"
@@ -188,6 +189,7 @@ extern void CheckValidResultRel(Relation resultRel, CmdType operation);
extern void InitResultRelInfo(ResultRelInfo *resultRelInfo,
Relation resultRelationDesc,
Index resultRelationIndex,
+ bool load_partition_check,
int instrument_options);
extern ResultRelInfo *ExecGetTriggerResultRel(EState *estate, Oid relid);
extern bool ExecContextForcesOids(PlanState *planstate, bool *hasoids);
@@ -211,6 +213,10 @@ extern void EvalPlanQualSetPlan(EPQState *epqstate,
extern void EvalPlanQualSetTuple(EPQState *epqstate, Index rti,
HeapTuple tuple);
extern HeapTuple EvalPlanQualGetTuple(EPQState *epqstate, Index rti);
+extern int ExecFindPartition(ResultRelInfo *resultRelInfo,
+ PartitionDescNode pdnode,
+ TupleTableSlot *slot,
+ EState *estate);
#define EvalPlanQualSetSlot(epqstate, slot) ((epqstate)->origslot = (slot))
extern void EvalPlanQualFetchRowMarks(EPQState *epqstate);
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 74288e2..f2f535e 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -16,6 +16,7 @@
#include "access/genam.h"
#include "access/heapam.h"
+#include "access/tupconvert.h"
#include "executor/instrument.h"
#include "lib/pairingheap.h"
#include "nodes/params.h"
@@ -1140,6 +1141,15 @@ typedef struct ModifyTableState
* tlist */
TupleTableSlot *mt_conflproj; /* CONFLICT ... SET ... projection
* target */
+ struct PartitionDescNodeData *mt_partition_node;
+ /* Partition descriptor node tree */
+ ResultRelInfo *mt_partitions; /* Per leaf partition target
+ * relations */
+ TupleConversionMap **mt_partition_tupconv_maps;
+ /* Per leaf partition
+ * tuple conversion map */
+ int mt_num_partitions; /* Number of leaf partition target
+ * relations in the above array */
} ModifyTableState;
/* ----------------
--
1.7.1
^ permalink raw reply [nested|flat] 161+ messages in thread
* Re: Declarative partitioning
@ 2016-04-20 14:46 Ildar Musin <i.musin@postgrespro.ru>
parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
0 siblings, 1 reply; 161+ messages in thread
From: Ildar Musin @ 2016-04-20 14:46 UTC (permalink / raw)
To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; Amit Langote <amitlangote09@gmail.com>; Alexander Korotkov <a.korotkov@postgrespro.ru>; +Cc: Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; Robert Haas <robertmhaas@gmail.com>; Corey Huinker <corey.huinker@gmail.com>; pgsql-hackers
Hi Amit,
On 20.04.2016 13:28, Amit Langote wrote:
> On 2016/04/19 23:52, Amit Langote wrote:
>> On Tue, Apr 19, 2016 at 11:26 PM, Alexander Korotkov
>>> Another question is that it might be NOT what users expect from that. From
>>> the syntax side it very looks like defining something boxes regions for two
>>> keys which could be replacement for subpartitioning. But it isn't so.
>> Need to check why query with qual b < 100 behaves the way it does.
>> Something's going wrong there with the constraints (partition
>> predicates) that are being generated internally (as mentioned before,
>> still driven by constraint exclusion using the constraints generated
>> on-the-fly).
>>
>> As for the composite range partition bounds in Ildar's example, it's
>> as if the second value in the key never determines the fate of a row
>> going into some partition, therefore no constraints should have been
>> generated for column b of the key. I'm afraid that's not the case as
>> per the latest patch. Will fix.
> The strange behavior that Ildar reported should have been fixed with the
> attached updated set of patches (v2):
>
> create table test(a int, b int) partition by range (a, b);
> create table test_1 partition of test for values start (0, 0) end (100, 100);
> create table test_2 partition of test for values start (100, 100) end
> (200, 200);
> create table test_3 partition of test for values start (200, 200) end
> (300, 300);
> CREATE TABLE
> CREATE TABLE
> CREATE TABLE
> CREATE TABLE
>
> insert into test(a, b) values (150, 50);
> INSERT 0 1
>
> select * from test where b < 100;
> a | b
> -----+----
> 150 | 50
> (1 row)
>
> explain (costs off) select * from test where b < 100;
> QUERY PLAN
> ---------------------------
> Append
> -> Seq Scan on test
> Filter: (b < 100)
> -> Seq Scan on test_1
> Filter: (b < 100)
> -> Seq Scan on test_2
> Filter: (b < 100)
> -> Seq Scan on test_3
> Filter: (b < 100)
> (9 rows)
>
>
> Multi-column range partitioning seems a bit tricky as far as generating
> constraints on individual columns using a partition's lower and upper
> bounds (both composite values) is concerned. I mentally pictured
> something like the following example scenario:
>
> create table test(a int, b int, c int)
> partition by range (a, b, c);
> create table test_1 partition of test
> for values start (0, 0, 0) end (0, 2, 0);
> create table test_2 partition of test
> for values start (0, 2, 0) end (0, 3, 0);
> create table test_3 partition of test
> for values start (0, 3, 0) end (0, 4, 0);
> create table test_4 partition of test
> for values start (0, 4, 0) end (1, 0, 0);
> create table test_5 partition of test
> for values start (1, 0, 0) end (1, 2, 0);
> create table test_6 partition of test
> for values start (1, 2, 0) end (1, 3, 0);
> create table test_7 partition of test
> for values start (1, 3, 0) end (1, 4, 0);
> create table test_8 partition of test
> for values start (1, 4, 0) end (2, 0, 0);
>
> Useful to think of the above as sequence of ranges [000, 020), [020, 030),
> [030, 040), [040, 100), [100, 120), [120, 130), [130, 140), [140, 200) for
> purposes of finding the partition for a row.
>
> Then constraints generated internally for each partition:
>
> test_1: a = 0 AND b >= 0 AND b <= 2
> test_2: a = 0 AND b >= 2 AND b <= 3
> test_3: a = 0 AND b >= 3 AND b <= 4
> test_4: a >= 0 AND a <= 1
> test_5: a = 1 AND b >= 0 AND b <= 2
> test_6: a = 1 AND b >= 2 AND b <= 3
> test_7: a = 1 AND b >= 3 AND b <= 4
> test_8: a >= 1 AND a <= 2
>
> I will try further to poke holes in my thinking about this. Please feel
> free to point out if you find any.
>
> Thanks,
> Amit
Thanks for clarification! I tried the updated patch, now it works correctly.
I encountered another problem that concerns expressions as partitioning
key. Probably there is still some work in progress. But if it will help
here is my case:
create table inh(a int, b int) partition by range ((a+b));
create table inh_1 partition of inh for values start (0) end (10);
create table inh_2 partition of inh for values start (10) end (20);
Then if we run any SELECT query it crashes postgres:
select * from inh;
Crash occurs in get_check_expr_from_partbound(). It seems that function
is not yet expecting an expression key and designed to handle only
simple attributes keys. Backtrace:
#0 strlen () at ../sysdeps/x86_64/strlen.S:106
#1 0x00000000004add8a in hashname (fcinfo=0x7ffdbdb9c760) at hashfunc.c:145
#2 0x000000000099cc08 in DirectFunctionCall1Coll (func=0x4add66
<hashname>, collation=0, arg1=0) at fmgr.c:1027
#3 0x00000000009724dd in CatalogCacheComputeHashValue (cache=0x26590b0,
nkeys=2, cur_skey=0x7ffdbdb9cbf0) at catcache.c:207
#4 0x0000000000974979 in SearchCatCache (cache=0x26590b0, v1=32807,
v2=0, v3=0, v4=0) at catcache.c:1151
#5 0x0000000000988e35 in SearchSysCache (cacheId=6, key1=32807, key2=0,
key3=0, key4=0) at syscache.c:1006
#6 0x0000000000988fe3 in SearchSysCacheAttName (relid=32807,
attname=0x0) at syscache.c:1106
#7 0x000000000098a744 in get_attnum (relid=32807, attname=0x0) at
lsyscache.c:825
#8 0x000000000056afd2 in get_check_expr_from_partbound
(rel=0x7f868601ca20, parent=0x7f868601b770, bound=0x26e6ac8) at
partition.c:1427
#9 0x000000000056bc9e in generate_partition_check_expr
(rel=0x7f868601ca20) at partition.c:1788
#10 0x000000000056bb5f in RelationGetPartitionCheckExpr
(rel=0x7f868601ca20) at partition.c:1746
#11 0x0000000000782b5f in get_relation_constraints (root=0x268f1b8,
relationObjectId=32807, rel=0x26e5cd8, include_notnull=1 '\001') at
plancat.c:1209
#12 0x0000000000782d74 in relation_excluded_by_constraints
(root=0x268f1b8, rel=0x26e5cd8, rte=0x268ebf0) at plancat.c:1302
#13 0x000000000072a18d in set_append_rel_size (root=0x268f1b8,
rel=0x26e5690, rti=1, rte=0x268ea80) at allpaths.c:947
...
--
Ildar Musin
i.musin@postgrespro.ru
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 161+ messages in thread
* Re: Declarative partitioning
@ 2016-04-20 16:06 Amit Langote <amitlangote09@gmail.com>
parent: Ildar Musin <i.musin@postgrespro.ru>
0 siblings, 1 reply; 161+ messages in thread
From: Amit Langote @ 2016-04-20 16:06 UTC (permalink / raw)
To: Ildar Musin <i.musin@postgrespro.ru>; +Cc: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; Alexander Korotkov <a.korotkov@postgrespro.ru>; Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; Robert Haas <robertmhaas@gmail.com>; Corey Huinker <corey.huinker@gmail.com>; pgsql-hackers
Hi Ildar,
On Wed, Apr 20, 2016 at 11:46 PM, Ildar Musin <i.musin@postgrespro.ru> wrote:
> Thanks for clarification! I tried the updated patch, now it works correctly.
Great, thanks!
> I encountered another problem that concerns expressions as partitioning key.
> Probably there is still some work in progress. But if it will help here is
> my case:
>
> create table inh(a int, b int) partition by range ((a+b));
> create table inh_1 partition of inh for values start (0) end (10);
> create table inh_2 partition of inh for values start (10) end (20);
>
> Then if we run any SELECT query it crashes postgres:
>
> select * from inh;
>
> Crash occurs in get_check_expr_from_partbound(). It seems that function is
> not yet expecting an expression key and designed to handle only simple
> attributes keys. Backtrace:
You're right, silly mistake. :-(
Will fix
Thanks,
Amit
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 161+ messages in thread
* Re: Declarative partitioning
@ 2016-04-21 02:05 Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
parent: Amit Langote <amitlangote09@gmail.com>
0 siblings, 2 replies; 161+ messages in thread
From: Amit Langote @ 2016-04-21 02:05 UTC (permalink / raw)
To: Amit Langote <amitlangote09@gmail.com>; Ildar Musin <i.musin@postgrespro.ru>; +Cc: Alexander Korotkov <a.korotkov@postgrespro.ru>; Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; Robert Haas <robertmhaas@gmail.com>; Corey Huinker <corey.huinker@gmail.com>; pgsql-hackers
Hi Ildar,
On 2016/04/21 1:06, Amit Langote wrote:
> On Wed, Apr 20, 2016 at 11:46 PM, Ildar Musin <i.musin@postgrespro.ru> wrote:
>> Crash occurs in get_check_expr_from_partbound(). It seems that function is
>> not yet expecting an expression key and designed to handle only simple
>> attributes keys. Backtrace:
>
> You're right, silly mistake. :-(
>
> Will fix
Attached updated version fixes this. I'll take time to send the next
version but I'd very much appreciate it if you keep reporting anything
that doesn't look/work right like you did so far.
Thanks,
Amit
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Attachments:
[text/x-diff] 0001-Add-syntax-to-specify-partition-key-v3.patch (41.5K, ../../57183572.8020900@lab.ntt.co.jp/2-0001-Add-syntax-to-specify-partition-key-v3.patch)
download | inline diff:
From c076abd84a5ea9a0c0fb9669f1ccb5cdc38efcf5 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Mon, 22 Feb 2016 14:06:33 +0900
Subject: [PATCH 1/5] Add syntax to specify partition key.
This adds the following clause to CREATE TABLE:
PARTITION BY method (col [USING opclass] [, ...])
where 'method' is either RANGE or LIST. 'col' could be either a simple
column reference or a parenthesized expression. 'opclass' is name
of btree operator class to use with the column. If method is LIST,
only one column is allowed, whereas, for RANGE method up to 32 columns
can be specified. If this clause is specified, the following constraints
viz. PARTITION KEY, UNIQUE, FOREIGN KEY, EXCLUSION are not allowed
because they cannot be supported on partitioned tables.
This adds 2 new parser nodes viz. PartitionElem and PartitionBy. The former
is similar to IndexElem and the latter consists of details of partition
key such as the method and list of PartitionElems. The new member partby
of CreateStmt is a pointer to the PartitionBy struct created when PARTITION
BY clause is present.
---
doc/src/sgml/ref/create_table.sgml | 35 +++++
src/backend/commands/indexcmds.c | 4 +-
src/backend/commands/tablecmds.c | 215 ++++++++++++++++++++++++++++
src/backend/nodes/copyfuncs.c | 33 +++++
src/backend/nodes/equalfuncs.c | 28 ++++
src/backend/nodes/outfuncs.c | 27 ++++
src/backend/parser/gram.y | 110 ++++++++++++---
src/backend/parser/parse_agg.c | 11 ++
src/backend/parser/parse_expr.c | 5 +
src/backend/parser/parse_utilcmd.c | 63 ++++++++
src/include/commands/defrem.h | 2 +
src/include/nodes/nodes.h | 2 +
src/include/nodes/parsenodes.h | 35 +++++
src/include/parser/kwlist.h | 1 +
src/include/parser/parse_node.h | 3 +-
src/include/pg_config_manual.h | 5 +
src/test/regress/expected/create_table.out | 79 ++++++++++
src/test/regress/sql/create_table.sql | 68 +++++++++
18 files changed, 704 insertions(+), 22 deletions(-)
diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml
index d1807ed..70a7579 100644
--- a/doc/src/sgml/ref/create_table.sgml
+++ b/doc/src/sgml/ref/create_table.sgml
@@ -28,6 +28,7 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
[, ... ]
] )
[ INHERITS ( <replaceable>parent_table</replaceable> [, ... ] ) ]
+[ PARTITION BY {RANGE | LIST} ( { <replaceable class="parameter">column_name</replaceable> | ( <replaceable class="parameter">expression</replaceable> ) } [ <replaceable class="parameter">opclass</replaceable> ] [, ...] )
[ WITH ( <replaceable class="PARAMETER">storage_parameter</replaceable> [= <replaceable class="PARAMETER">value</replaceable>] [, ... ] ) | WITH OIDS | WITHOUT OIDS ]
[ ON COMMIT { PRESERVE ROWS | DELETE ROWS | DROP } ]
[ TABLESPACE <replaceable class="PARAMETER">tablespace_name</replaceable> ]
@@ -38,6 +39,7 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
| <replaceable>table_constraint</replaceable> }
[, ... ]
) ]
+[ PARTITION BY {RANGE | LIST} ( { <replaceable class="parameter">column_name</replaceable> | ( <replaceable class="parameter">expression</replaceable> ) } [ <replaceable class="parameter">opclass</replaceable> ] [, ...] )
[ WITH ( <replaceable class="PARAMETER">storage_parameter</replaceable> [= <replaceable class="PARAMETER">value</replaceable>] [, ... ] ) | WITH OIDS | WITHOUT OIDS ]
[ ON COMMIT { PRESERVE ROWS | DELETE ROWS | DROP } ]
[ TABLESPACE <replaceable class="PARAMETER">tablespace_name</replaceable> ]
@@ -314,6 +316,39 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
</varlistentry>
<varlistentry>
+ <term><literal>PARTITION BY {RANGE | LIST} ( { <replaceable class="parameter">column_name</replaceable> | ( <replaceable class="parameter">expression</replaceable> ) } [ <replaceable class="parameter">opclass</replaceable> ] [, ...] ) </literal></term>
+ <listitem>
+ <para>
+ The optional <literal>PARTITION BY</> clause specifies a method of
+ partitioning the table and the corresponding partition key. Table
+ thus created is called <firstterm>partitioned</firstterm> table. Key
+ consists of an ordered list of column names and/or expressions when
+ using the <literal>RANGE</> method, whereas only a single column or
+ expression can be specified when using the <literal>LIST</> method.
+ The type of a key column or an expresion must have an associated
+ btree operator class or one must be specified along with the column
+ or the expression.
+ </para>
+
+ <para>
+ A partitioned table is divided into sub-tables (called partitions), which
+ in turn, are created using separate <literal>CREATE TABLE</> commands.
+ The table itself is empty. A data row inserted into the table is mapped
+ to and stored in one of the partitions (if one exists) based on the
+ values of columns and/or expressions in the partition key and partition
+ rules associated with the partitions.
+ </para>
+
+ <para>
+ Currently, there are following limitations on definition of partitioned
+ tables: one cannot specify any UNIQUE, PRIMARY KEY, EXCLUDE and/or
+ FOREIGN KEY constraints.
+ </para>
+
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><literal>LIKE <replaceable>source_table</replaceable> [ <replaceable>like_option</replaceable> ... ]</literal></term>
<listitem>
<para>
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index d14d540..4ef6b80 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -69,8 +69,6 @@ static void ComputeIndexAttrs(IndexInfo *indexInfo,
char *accessMethodName, Oid accessMethodId,
bool amcanorder,
bool isconstraint);
-static Oid GetIndexOpClass(List *opclass, Oid attrType,
- char *accessMethodName, Oid accessMethodId);
static char *ChooseIndexName(const char *tabname, Oid namespaceId,
List *colnames, List *exclusionOpNames,
bool primary, bool isconstraint);
@@ -1256,7 +1254,7 @@ ComputeIndexAttrs(IndexInfo *indexInfo,
/*
* Resolve possibly-defaulted operator class specification
*/
-static Oid
+Oid
GetIndexOpClass(List *opclass, Oid attrType,
char *accessMethodName, Oid accessMethodId)
{
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 45a5144..c881ff0 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -433,6 +433,11 @@ static void RangeVarCallbackForDropRelation(const RangeVar *rel, Oid relOid,
Oid oldRelOid, void *arg);
static void RangeVarCallbackForAlterRelation(const RangeVar *rv, Oid relid,
Oid oldrelid, void *arg);
+static PartitionBy *transformPartitionBy(Relation rel, PartitionBy *partitionby);
+static void ComputePartitionAttrs(Oid relid, List *partParams,
+ AttrNumber *partattrs,
+ List **partexprs,
+ Oid *partoplass);
/* ----------------------------------------------------------------
@@ -710,6 +715,21 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
AddRelationNewConstraints(rel, rawDefaults, stmt->constraints,
true, true, false);
+ /* Process partition key, if any */
+ if (stmt->partby)
+ {
+ PartitionBy *partby;
+ int16 partnatts;
+ AttrNumber partattrs[PARTITION_MAX_KEYS];
+ Oid partopclass[PARTITION_MAX_KEYS];
+ List *partexprs = NIL;
+
+ partnatts = list_length(stmt->partby->partParams);
+ partby = transformPartitionBy(rel, stmt->partby);
+ ComputePartitionAttrs(relationId, stmt->partby->partParams,
+ partattrs, &partexprs, partopclass);
+ }
+
ObjectAddressSet(address, RelationRelationId, relationId);
/*
@@ -12061,3 +12081,198 @@ RangeVarCallbackForAlterRelation(const RangeVar *rv, Oid relid, Oid oldrelid,
ReleaseSysCache(tuple);
}
+
+/*
+ * transformPartitionBy
+ * Transform any expressions present in the partition key
+ */
+static PartitionBy *
+transformPartitionBy(Relation rel, PartitionBy *partitionby)
+{
+ PartitionBy *partby;
+ ParseState *pstate;
+ RangeTblEntry *rte;
+ ListCell *l;
+
+ partby = (PartitionBy *) makeNode(PartitionBy);
+
+ partby->strategy = partitionby->strategy;
+ partby->location = partitionby->location;
+ partby->partParams = NIL;
+
+ /*
+ * Create a dummy ParseState and insert the target relation as its sole
+ * rangetable entry. We need a ParseState for transformExpr.
+ */
+ pstate = make_parsestate(NULL);
+ rte = addRangeTableEntryForRelation(pstate, rel, NULL, false, true);
+ addRTEtoQuery(pstate, rte, true, true, true);
+
+ /* take care of any partition expressions */
+ foreach(l, partitionby->partParams)
+ {
+ ListCell *column;
+ PartitionElem *pelem = (PartitionElem *) lfirst(l);
+
+ /* Check for PARTITION BY ... ON (foo, foo) */
+ foreach(column, partby->partParams)
+ {
+ PartitionElem *pparam = (PartitionElem *) lfirst(column);
+
+ if (pelem->name && pparam->name &&
+ !strcmp(pelem->name, pparam->name))
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_COLUMN),
+ errmsg("column \"%s\" appears twice in partition key", pelem->name),
+ parser_errposition(pstate, pelem->location)));
+ }
+
+ if (pelem->expr)
+ {
+ /* Now do parse transformation of the expression */
+ pelem->expr = transformExpr(pstate, pelem->expr,
+ EXPR_KIND_PARTITION_KEY);
+
+ /* we have to fix its collations too */
+ assign_expr_collations(pstate, pelem->expr);
+
+ /*
+ * transformExpr() should have already rejected subqueries,
+ * aggregates, and window functions, based on the EXPR_KIND_ for
+ * a partition key expression.
+ *
+ * Also reject expressions returning sets; this is for consistency
+ * DefineRelation() will make more checks.
+ */
+ if (expression_returns_set(pelem->expr))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("partition key expression cannot return a set"),
+ parser_errposition(pstate, pelem->location)));
+ }
+
+ partby->partParams = lappend(partby->partParams, pelem);
+ }
+
+ return partby;
+}
+
+/*
+ * ComputePartitionAttrs
+ * Compute per-partition-column information from partParams
+ */
+static void
+ComputePartitionAttrs(Oid relid, List *partParams, AttrNumber *partattrs,
+ List **partexprs, Oid *partopclass)
+{
+ int attn;
+ ListCell *lc;
+
+ attn = 0;
+ foreach(lc, partParams)
+ {
+ PartitionElem *pelem = (PartitionElem *) lfirst(lc);
+ Oid atttype;
+ Oid opclassOid;
+
+ if (pelem->name != NULL)
+ {
+ HeapTuple atttuple;
+ Form_pg_attribute attform;
+
+ atttuple = SearchSysCacheAttName(relid, pelem->name);
+ if (!HeapTupleIsValid(atttuple))
+ {
+ /* difference in error message spellings is historical */
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_COLUMN),
+ errmsg("column \"%s\" named in partition key does not exist",
+ pelem->name)));
+ }
+ attform = (Form_pg_attribute) GETSTRUCT(atttuple);
+
+ if (attform->attnum <= 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_COLUMN),
+ errmsg("cannot use system column \"%s\" in partition key",
+ pelem->name)));
+
+ partattrs[attn] = attform->attnum;
+ atttype = attform->atttypid;
+ ReleaseSysCache(atttuple);
+ }
+ else
+ {
+ /* Partition key expression */
+ Node *expr = pelem->expr;
+
+ Assert(expr != NULL);
+ atttype = exprType(expr);
+
+ if (IsA(expr, CollateExpr))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("cannot use COLLATE in partition key expression")));
+
+ if (IsA(expr, Var) &&
+ ((Var *) expr)->varattno != InvalidAttrNumber)
+ {
+ /*
+ * User wrote "(column)" or "(column COLLATE something)".
+ * Treat it like simple attribute anyway.
+ */
+ partattrs[attn] = ((Var *) expr)->varattno;
+ }
+ else
+ {
+ partattrs[attn] = 0; /* marks expression */
+ *partexprs = lappend(*partexprs, expr);
+
+ /*
+ * transformExpr() should have already rejected subqueries,
+ * aggregates, and window functions, based on the EXPR_KIND_
+ * for a partition key expression.
+ */
+
+ /*
+ * An expression using mutable functions is probably wrong even
+ * even to use in a partition key
+ */
+ expr = (Node *) expression_planner((Expr *) expr);
+
+ if (IsA(expr, Const))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("cannot use a constant expression as partition key")));
+
+ if (contain_mutable_functions(expr))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("functions in partition key expression must be marked IMMUTABLE")));
+ }
+ }
+
+ /*
+ * Identify the opclass to use. At the moment, we use "btree" operators
+ * that seems enough for list and range partitioning.
+ */
+ if (!pelem->opclass)
+ {
+ opclassOid = GetDefaultOpClass(atttype, BTREE_AM_OID);
+
+ if (!OidIsValid(opclassOid))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("data type %s has no default btree operator class",
+ format_type_be(atttype)),
+ errhint("You must specify an existing btree operator class or define one for the type.")));
+ }
+ else
+ opclassOid = GetIndexOpClass(pelem->opclass,
+ atttype,
+ "btree",
+ BTREE_AM_OID);
+
+ partopclass[attn++] = opclassOid;
+ }
+}
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index a21928b..5f4f66f 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2999,6 +2999,7 @@ CopyCreateStmtFields(const CreateStmt *from, CreateStmt *newnode)
COPY_NODE_FIELD(relation);
COPY_NODE_FIELD(tableElts);
COPY_NODE_FIELD(inhRelations);
+ COPY_NODE_FIELD(partby);
COPY_NODE_FIELD(ofTypename);
COPY_NODE_FIELD(constraints);
COPY_NODE_FIELD(options);
@@ -4154,6 +4155,32 @@ _copyAlterPolicyStmt(const AlterPolicyStmt *from)
return newnode;
}
+static PartitionBy *
+_copyPartitionBy(const PartitionBy *from)
+{
+
+ PartitionBy *newnode = makeNode(PartitionBy);
+
+ COPY_SCALAR_FIELD(strategy);
+ COPY_NODE_FIELD(partParams);
+ COPY_LOCATION_FIELD(location);
+
+ return newnode;
+}
+
+static PartitionElem *
+_copyPartitionElem(const PartitionElem *from)
+{
+ PartitionElem *newnode = makeNode(PartitionElem);
+
+ COPY_STRING_FIELD(name);
+ COPY_NODE_FIELD(expr);
+ COPY_NODE_FIELD(opclass);
+ COPY_LOCATION_FIELD(location);
+
+ return newnode;
+}
+
/* ****************************************************************
* pg_list.h copy functions
* ****************************************************************
@@ -5047,6 +5074,12 @@ copyObject(const void *from)
case T_RoleSpec:
retval = _copyRoleSpec(from);
break;
+ case T_PartitionBy:
+ retval = _copyPartitionBy(from);
+ break;
+ case T_PartitionElem:
+ retval = _copyPartitionElem(from);
+ break;
default:
elog(ERROR, "unrecognized node type: %d", (int) nodeTag(from));
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 3c6c567..001a468 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1154,6 +1154,7 @@ _equalCreateStmt(const CreateStmt *a, const CreateStmt *b)
COMPARE_NODE_FIELD(relation);
COMPARE_NODE_FIELD(tableElts);
COMPARE_NODE_FIELD(inhRelations);
+ COMPARE_NODE_FIELD(partby);
COMPARE_NODE_FIELD(ofTypename);
COMPARE_NODE_FIELD(constraints);
COMPARE_NODE_FIELD(options);
@@ -2617,6 +2618,27 @@ _equalRoleSpec(const RoleSpec *a, const RoleSpec *b)
return true;
}
+static bool
+_equalPartitionBy(const PartitionBy *a, const PartitionBy *b)
+{
+ COMPARE_SCALAR_FIELD(strategy);
+ COMPARE_NODE_FIELD(partParams);
+ COMPARE_LOCATION_FIELD(location);
+
+ return true;
+}
+
+static bool
+_equalPartitionElem(const PartitionElem *a, const PartitionElem *b)
+{
+ COMPARE_STRING_FIELD(name);
+ COMPARE_NODE_FIELD(expr);
+ COMPARE_NODE_FIELD(opclass);
+ COMPARE_LOCATION_FIELD(location);
+
+ return true;
+}
+
/*
* Stuff from pg_list.h
*/
@@ -3367,6 +3389,12 @@ equal(const void *a, const void *b)
case T_RoleSpec:
retval = _equalRoleSpec(a, b);
break;
+ case T_PartitionBy:
+ retval = _equalPartitionBy(a, b);
+ break;
+ case T_PartitionElem:
+ retval = _equalPartitionElem(a, b);
+ break;
default:
elog(ERROR, "unrecognized node type: %d",
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 5ac7446..4e70a69 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2367,6 +2367,7 @@ _outCreateStmtInfo(StringInfo str, const CreateStmt *node)
WRITE_NODE_FIELD(relation);
WRITE_NODE_FIELD(tableElts);
WRITE_NODE_FIELD(inhRelations);
+ WRITE_NODE_FIELD(partby);
WRITE_NODE_FIELD(ofTypename);
WRITE_NODE_FIELD(constraints);
WRITE_NODE_FIELD(options);
@@ -3215,6 +3216,26 @@ _outConstraint(StringInfo str, const Constraint *node)
}
}
+static void
+_outPartitionBy(StringInfo str, const PartitionBy *node)
+{
+ WRITE_NODE_TYPE("PARTITIONBY");
+
+ WRITE_CHAR_FIELD(strategy);
+ WRITE_NODE_FIELD(partParams);
+ WRITE_LOCATION_FIELD(location);
+}
+
+static void
+_outPartitionElem(StringInfo str, const PartitionElem *node)
+{
+ WRITE_NODE_TYPE("PARTITIONELEM");
+
+ WRITE_STRING_FIELD(name);
+ WRITE_NODE_FIELD(expr);
+ WRITE_NODE_FIELD(opclass);
+ WRITE_LOCATION_FIELD(location);
+}
/*
* outNode -
@@ -3794,6 +3815,12 @@ outNode(StringInfo str, const void *obj)
case T_XmlSerialize:
_outXmlSerialize(str, obj);
break;
+ case T_PartitionBy:
+ _outPartitionBy(str, obj);
+ break;
+ case T_PartitionElem:
+ _outPartitionElem(str, obj);
+ break;
default:
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 18ec5f0..0607439 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -227,6 +227,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
struct ImportQual *importqual;
InsertStmt *istmt;
VariableSetStmt *vsetstmt;
+ PartitionElem *partelem;
+ PartitionBy *partby;
}
%type <node> stmt schema_stmt
@@ -539,6 +541,9 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
opt_frame_clause frame_extent frame_bound
%type <str> opt_existing_window_name
%type <boolean> opt_if_not_exists
+%type <partby> PartitionBy OptPartitionBy
+%type <partelem> part_elem
+%type <list> part_params
/*
* Non-keyword token types. These are hard-wired into the "flex" lexer.
@@ -603,7 +608,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
KEY
LABEL LANGUAGE LARGE_P LAST_P LATERAL_P
- LEADING LEAKPROOF LEAST LEFT LEVEL LIKE LIMIT LISTEN LOAD LOCAL
+ LEADING LEAKPROOF LEAST LEFT LEVEL LIKE LIMIT LIST LISTEN LOAD LOCAL
LOCALTIME LOCALTIMESTAMP LOCATION LOCK_P LOCKED LOGGED
MAPPING MATCH MATERIALIZED MAXVALUE METHOD MINUTE_P MINVALUE MODE MONTH_P MOVE
@@ -2808,69 +2813,75 @@ copy_generic_opt_arg_list_item:
*****************************************************************************/
CreateStmt: CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
- OptInherit OptWith OnCommitOption OptTableSpace
+ OptInherit OptPartitionBy OptWith OnCommitOption OptTableSpace
{
CreateStmt *n = makeNode(CreateStmt);
$4->relpersistence = $2;
n->relation = $4;
n->tableElts = $6;
n->inhRelations = $8;
+ n->partby = $9;
n->ofTypename = NULL;
n->constraints = NIL;
- n->options = $9;
- n->oncommit = $10;
- n->tablespacename = $11;
+ n->options = $10;
+ n->oncommit = $11;
+ n->tablespacename = $12;
n->if_not_exists = false;
$$ = (Node *)n;
}
| CREATE OptTemp TABLE IF_P NOT EXISTS qualified_name '('
- OptTableElementList ')' OptInherit OptWith OnCommitOption
- OptTableSpace
+ OptTableElementList ')' OptInherit OptPartitionBy OptWith
+ OnCommitOption OptTableSpace
{
CreateStmt *n = makeNode(CreateStmt);
$7->relpersistence = $2;
n->relation = $7;
n->tableElts = $9;
n->inhRelations = $11;
+ n->partby = $12;
n->ofTypename = NULL;
n->constraints = NIL;
- n->options = $12;
- n->oncommit = $13;
- n->tablespacename = $14;
+ n->options = $13;
+ n->oncommit = $14;
+ n->tablespacename = $15;
n->if_not_exists = true;
$$ = (Node *)n;
}
| CREATE OptTemp TABLE qualified_name OF any_name
- OptTypedTableElementList OptWith OnCommitOption OptTableSpace
+ OptTypedTableElementList OptPartitionBy OptWith OnCommitOption
+ OptTableSpace
{
CreateStmt *n = makeNode(CreateStmt);
$4->relpersistence = $2;
n->relation = $4;
n->tableElts = $7;
n->inhRelations = NIL;
+ n->partby = $8;
n->ofTypename = makeTypeNameFromNameList($6);
n->ofTypename->location = @6;
n->constraints = NIL;
- n->options = $8;
- n->oncommit = $9;
- n->tablespacename = $10;
+ n->options = $9;
+ n->oncommit = $10;
+ n->tablespacename = $11;
n->if_not_exists = false;
$$ = (Node *)n;
}
| CREATE OptTemp TABLE IF_P NOT EXISTS qualified_name OF any_name
- OptTypedTableElementList OptWith OnCommitOption OptTableSpace
+ OptTypedTableElementList OptPartitionBy OptWith OnCommitOption
+ OptTableSpace
{
CreateStmt *n = makeNode(CreateStmt);
$7->relpersistence = $2;
n->relation = $7;
n->tableElts = $10;
n->inhRelations = NIL;
+ n->partby = $11;
n->ofTypename = makeTypeNameFromNameList($9);
n->ofTypename->location = @9;
n->constraints = NIL;
- n->options = $11;
- n->oncommit = $12;
- n->tablespacename = $13;
+ n->options = $12;
+ n->oncommit = $13;
+ n->tablespacename = $14;
n->if_not_exists = true;
$$ = (Node *)n;
}
@@ -3415,6 +3426,68 @@ OptInherit: INHERITS '(' qualified_name_list ')' { $$ = $3; }
| /*EMPTY*/ { $$ = NIL; }
;
+/* Optional partition key definition */
+OptPartitionBy: PartitionBy { $$ = $1; }
+ | /*EMPTY*/ { $$ = NULL; }
+ ;
+
+PartitionBy: PARTITION BY RANGE '(' part_params ')'
+ {
+ PartitionBy *n = makeNode(PartitionBy);
+
+ n->strategy = PARTITION_STRAT_RANGE;
+ n->partParams = $5;
+ n->location = @1;
+
+ $$ = n;
+ }
+ | PARTITION BY LIST '(' part_params ')'
+ {
+ PartitionBy *n = makeNode(PartitionBy);
+
+ n->strategy = PARTITION_STRAT_LIST;
+ n->partParams = $5;
+ n->location = @1;
+
+ $$ = n;
+ }
+ ;
+
+part_params: part_elem { $$ = list_make1($1); }
+ | part_params ',' part_elem { $$ = lappend($1, $3); }
+ ;
+
+part_elem: ColId opt_class
+ {
+ PartitionElem *n = makeNode(PartitionElem);
+
+ n->name = $1;
+ n->expr = NULL;
+ n->opclass = $2;
+ n->location = @1;
+ $$ = n;
+ }
+ | func_expr_windowless opt_class
+ {
+ PartitionElem *n = makeNode(PartitionElem);
+
+ n->name = NULL;
+ n->expr = $1;
+ n->opclass = $2;
+ n->location = @1;
+ $$ = n;
+ }
+ | '(' a_expr ')' opt_class
+ {
+ PartitionElem *n = makeNode(PartitionElem);
+
+ n->name = NULL;
+ n->expr = $2;
+ n->opclass = $4;
+ n->location = @1;
+ $$ = n;
+ }
+ ;
/* WITH (options) is preferred, WITH OIDS and WITHOUT OIDS are legacy forms */
OptWith:
WITH reloptions { $$ = $2; }
@@ -13839,6 +13912,7 @@ unreserved_keyword:
| LAST_P
| LEAKPROOF
| LEVEL
+ | LIST
| LISTEN
| LOAD
| LOCAL
diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c
index 91bfe66..b56fd4f 100644
--- a/src/backend/parser/parse_agg.c
+++ b/src/backend/parser/parse_agg.c
@@ -486,6 +486,14 @@ check_agglevels_and_constraints(ParseState *pstate, Node *expr)
err = _("grouping operations are not allowed in trigger WHEN conditions");
break;
+ case EXPR_KIND_PARTITION_KEY:
+ if (isAgg)
+ err = _("aggregate functions are not allowed in partition key expressions");
+ else
+ err = _("grouping operations are not allowed in partition key expressions");
+
+ break;
+
/*
* There is intentionally no default: case here, so that the
@@ -843,6 +851,9 @@ transformWindowFuncCall(ParseState *pstate, WindowFunc *wfunc,
case EXPR_KIND_TRIGGER_WHEN:
err = _("window functions are not allowed in trigger WHEN conditions");
break;
+ case EXPR_KIND_PARTITION_KEY:
+ err = _("window functions are not allowed in partition key expressions");
+ break;
/*
* There is intentionally no default: case here, so that the
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index ebda55d..70a663c 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -1715,6 +1715,9 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
case EXPR_KIND_TRIGGER_WHEN:
err = _("cannot use subquery in trigger WHEN condition");
break;
+ case EXPR_KIND_PARTITION_KEY:
+ err = _("cannot use subquery in partition key expressions");
+ break;
/*
* There is intentionally no default: case here, so that the
@@ -3225,6 +3228,8 @@ ParseExprKindName(ParseExprKind exprKind)
return "EXECUTE";
case EXPR_KIND_TRIGGER_WHEN:
return "WHEN";
+ case EXPR_KIND_PARTITION_KEY:
+ return "partition key expression";
/*
* There is intentionally no default: case here, so that the
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 6528494..e267f74 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -87,6 +87,7 @@ typedef struct
List *alist; /* "after list" of things to do after creating
* the table */
IndexStmt *pkey; /* PRIMARY KEY index, if any */
+ bool ispartitioned; /* true if table is partitioned */
} CreateStmtContext;
/* State shared by transformCreateSchemaStmt and its subroutines */
@@ -229,6 +230,7 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
cxt.blist = NIL;
cxt.alist = NIL;
cxt.pkey = NULL;
+ cxt.ispartitioned = stmt->partby != NULL;
/*
* Notice that we allow OIDs here only for plain tables, even though
@@ -247,6 +249,24 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
if (stmt->ofTypename)
transformOfType(&cxt, stmt->ofTypename);
+ if (stmt->partby)
+ {
+ int partnatts = list_length(stmt->partby->partParams);
+
+ if (partnatts > PARTITION_MAX_KEYS)
+ ereport(ERROR,
+ (errcode(ERRCODE_TOO_MANY_COLUMNS),
+ errmsg("cannot use more than %d columns in partition key",
+ PARTITION_MAX_KEYS)));
+
+ if (stmt->partby->strategy == PARTITION_STRAT_LIST &&
+ partnatts > 1)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("cannot use more than one column in partition key"),
+ errdetail("Only one column allowed with list partitioning.")));
+ }
+
/*
* Run through each primary element in the table creation clause. Separate
* column defs from constraints, and do preliminary analysis. We have to
@@ -582,6 +602,12 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
errmsg("primary key constraints are not supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
+ if (cxt->ispartitioned)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("primary key constraints are not supported on partitioned tables"),
+ parser_errposition(cxt->pstate,
+ constraint->location)));
/* FALL THRU */
case CONSTR_UNIQUE:
@@ -591,6 +617,12 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
errmsg("unique constraints are not supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
+ if (cxt->ispartitioned)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("unique constraints are not supported on partitioned tables"),
+ parser_errposition(cxt->pstate,
+ constraint->location)));
if (constraint->keys == NIL)
constraint->keys = list_make1(makeString(column->colname));
cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
@@ -608,6 +640,12 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
errmsg("foreign key constraints are not supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
+ if (cxt->ispartitioned)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("foreign key constraints are not supported on partitioned tables"),
+ parser_errposition(cxt->pstate,
+ constraint->location)));
/*
* Fill in the current attribute's name and throw it into the
@@ -673,6 +711,12 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
errmsg("primary key constraints are not supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
+ if (cxt->ispartitioned)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("primary key constraints are not supported on partitioned tables"),
+ parser_errposition(cxt->pstate,
+ constraint->location)));
cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
break;
@@ -683,6 +727,12 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
errmsg("unique constraints are not supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
+ if (cxt->ispartitioned)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("unique constraints are not supported on partitioned tables"),
+ parser_errposition(cxt->pstate,
+ constraint->location)));
cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
break;
@@ -693,6 +743,12 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
errmsg("exclusion constraints are not supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
+ if (cxt->ispartitioned)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("exclusion constraints are not supported on partitioned tables"),
+ parser_errposition(cxt->pstate,
+ constraint->location)));
cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
break;
@@ -707,6 +763,12 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
errmsg("foreign key constraints are not supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
+ if (cxt->ispartitioned)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("foreign key constraints are not supported on partitioned tables"),
+ parser_errposition(cxt->pstate,
+ constraint->location)));
cxt->fkconstraints = lappend(cxt->fkconstraints, constraint);
break;
@@ -2515,6 +2577,7 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
cxt.blist = NIL;
cxt.alist = NIL;
cxt.pkey = NULL;
+ cxt.ispartitioned = false;
/*
* The only subtypes that currently require parse transformation handling
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index b064eb4..28024f8 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -42,6 +42,8 @@ extern bool CheckIndexCompatible(Oid oldId,
List *attributeList,
List *exclusionOpNames);
extern Oid GetDefaultOpClass(Oid type_id, Oid am_id);
+extern Oid GetIndexOpClass(List *opclass, Oid attrType,
+ char *accessMethodName, Oid accessMethodId);
/* commands/functioncmds.c */
extern ObjectAddress CreateFunction(CreateFunctionStmt *stmt, const char *queryString);
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index ea65f2e..9e582a5 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -452,6 +452,8 @@ typedef enum NodeTag
T_OnConflictClause,
T_CommonTableExpr,
T_RoleSpec,
+ T_PartitionElem,
+ T_PartitionBy,
/*
* TAGS FOR REPLICATION GRAMMAR PARSE NODES (replnodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 714cf15..4f697b8 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -696,6 +696,40 @@ typedef struct XmlSerialize
int location; /* token location, or -1 if unknown */
} XmlSerialize;
+/* Partitioning related definitions */
+
+/*
+ * PartitionElem - a partition key column
+ *
+ * 'name' Name of the table column included in the key
+ * 'expr' Expression node tree of expressional key column
+ * 'opclass' Operator class name associated with the column
+ */
+typedef struct PartitionElem
+{
+ NodeTag type;
+ char *name; /* name of column to partition on, or NULL */
+ Node *expr; /* expression to partition on, or NULL */
+ List *opclass; /* name of desired opclass; NIL = default */
+ int location; /* token location, or -1 if unknown */
+} PartitionElem;
+
+/*
+ * PartitionBy - partition key definition including the strategy
+ *
+ * 'strategy' partition strategy to use (one of the below defined)
+ * 'partParams' List of PartitionElems, one for each key column
+ */
+#define PARTITION_STRAT_LIST 'l'
+#define PARTITION_STRAT_RANGE 'r'
+
+typedef struct PartitionBy
+{
+ NodeTag type;
+ char strategy;
+ List *partParams;
+ int location; /* token location, or -1 if unknown */
+} PartitionBy;
/****************************************************************************
* Nodes for a Query tree
@@ -1750,6 +1784,7 @@ typedef struct CreateStmt
List *tableElts; /* column definitions (list of ColumnDef) */
List *inhRelations; /* relations to inherit from (list of
* inhRelation) */
+ PartitionBy *partby; /* PARTITION BY clause */
TypeName *ofTypename; /* OF typename */
List *constraints; /* constraints (list of Constraint nodes) */
List *options; /* options from WITH clause */
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 17ffef5..40da67a 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -227,6 +227,7 @@ PG_KEYWORD("left", LEFT, TYPE_FUNC_NAME_KEYWORD)
PG_KEYWORD("level", LEVEL, UNRESERVED_KEYWORD)
PG_KEYWORD("like", LIKE, TYPE_FUNC_NAME_KEYWORD)
PG_KEYWORD("limit", LIMIT, RESERVED_KEYWORD)
+PG_KEYWORD("list", LIST, UNRESERVED_KEYWORD)
PG_KEYWORD("listen", LISTEN, UNRESERVED_KEYWORD)
PG_KEYWORD("load", LOAD, UNRESERVED_KEYWORD)
PG_KEYWORD("local", LOCAL, UNRESERVED_KEYWORD)
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index e3e359c..a13c6fb 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -64,7 +64,8 @@ typedef enum ParseExprKind
EXPR_KIND_ALTER_COL_TRANSFORM, /* transform expr in ALTER COLUMN TYPE */
EXPR_KIND_EXECUTE_PARAMETER, /* parameter value in EXECUTE */
EXPR_KIND_TRIGGER_WHEN, /* WHEN condition in CREATE TRIGGER */
- EXPR_KIND_POLICY /* USING or WITH CHECK expr in policy */
+ EXPR_KIND_POLICY, /* USING or WITH CHECK expr in policy */
+ EXPR_KIND_PARTITION_KEY /* partition key expression */
} ParseExprKind;
diff --git a/src/include/pg_config_manual.h b/src/include/pg_config_manual.h
index 8d5a378..850251b 100644
--- a/src/include/pg_config_manual.h
+++ b/src/include/pg_config_manual.h
@@ -46,6 +46,11 @@
#define INDEX_MAX_KEYS 32
/*
+ * Maximum number of columns in a partition key
+ */
+#define PARTITION_MAX_KEYS 32
+
+/*
* Set the upper and lower bounds of sequence values.
*/
#define SEQ_MAXVALUE PG_INT64_MAX
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 41ceb87..aaae9f9 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -253,3 +253,82 @@ DROP TABLE as_select1;
-- check that the oid column is added before the primary key is checked
CREATE TABLE oid_pk (f1 INT, PRIMARY KEY(oid)) WITH OIDS;
DROP TABLE oid_pk;
+--
+-- partitioned tables
+--
+-- cannot use system columns in partition key
+CREATE TABLE fail_oid_pk (
+ a int
+) PARTITION BY RANGE (oid) WITH OIDS; -- fail
+ERROR: cannot use system column "oid" in partition key
+-- cannot use more than 1 column as partition key for list partitioned table
+CREATE TABLE two_col_tab (
+ a1 int,
+ a2 int
+) PARTITION BY LIST (a1, a2); -- fail
+ERROR: cannot use more than one column in partition key
+DETAIL: Only one column allowed with list partitioning.
+-- cannot use more than 32 columns as partition key for range partitioned table
+CREATE TABLE wide_tab (
+ a01 int, a02 int, a03 int, a04 int, a05 int, a06 int, a07 int, a08 int,
+ a09 int, a10 int, a11 int, a12 int, a13 int, a14 int, a15 int, a16 int,
+ a17 int, a18 int, a19 int, a20 int, a21 int, a22 int, a23 int, a24 int,
+ a25 int, a26 int, a27 int, a28 int, a29 int, a30 int, a31 int, a32 int,
+ a33 int
+) PARTITION BY RANGE (a01, a02, a03, a04, a05, a06, a07, a08,
+ a09, a10, a11, a12, a13, a14, a15, a16,
+ a17, a18, a19, a20, a21, a22, a23, a24,
+ a25, a26, a27, a28, a29, a30, a31, a32, a33); -- fail
+ERROR: cannot use more than 32 columns in partition key
+-- Partitioning requires to use columns with data type having a suitable
+-- btree operator class
+CREATE TABLE point_tab (
+ a point
+) PARTITION BY LIST (a); -- fail
+ERROR: data type point has no default btree operator class
+HINT: You must specify an existing btree operator class or define one for the type.
+CREATE TABLE point_tab (
+ a point
+) PARTITION BY LIST (a USING point_ops); -- fail
+ERROR: operator class "point_ops" does not exist for access method "btree"
+CREATE TABLE point_tab (
+ a point
+) PARTITION BY RANGE (a); -- fail
+ERROR: data type point has no default btree operator class
+HINT: You must specify an existing btree operator class or define one for the type.
+CREATE TABLE point_tab (
+ a point
+) PARTITION BY RANGE (a USING point_ops); -- fail
+ERROR: operator class "point_ops" does not exist for access method "btree"
+-- fail to create a partitioned table with PRIMARY KEY constraint
+CREATE TABLE pk_fail (
+ a int PRIMARY KEY
+) PARTITION BY RANGE (a);
+ERROR: primary key constraints are not supported on partitioned tables
+LINE 2: a int PRIMARY KEY
+ ^
+-- fail to create a partitioned table with foreign key constraint
+CREATE TABLE pk_rel(
+ a int PRIMARY KEY
+);
+CREATE TABLE fk_fail (
+ a int REFERENCES pk_rel(a)
+) PARTITION BY RANGE (a);
+ERROR: foreign key constraints are not supported on partitioned tables
+LINE 2: a int REFERENCES pk_rel(a)
+ ^
+DROP TABLE pk_rel;
+-- fail to create a partitioned table with prohibited expressions in key
+CREATE TABLE fail_const_key (
+ a int
+) PARTITION BY RANGE (('a'));
+ERROR: cannot use a constant expression as partition key
+CREATE TABLE fail_agg_in_key (
+ a int
+) PARTITION BY RANGE ((avg(a)));
+ERROR: aggregate functions are not allowed in partition key expressions
+CREATE TABLE fail_window_fun_in_key (
+ a int,
+ b int
+) PARTITION BY RANGE ((avg(a) OVER (PARTITION BY b)));
+ERROR: window functions are not allowed in partition key expressions
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 78bdc8b..df47656 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -269,3 +269,71 @@ DROP TABLE as_select1;
-- check that the oid column is added before the primary key is checked
CREATE TABLE oid_pk (f1 INT, PRIMARY KEY(oid)) WITH OIDS;
DROP TABLE oid_pk;
+
+--
+-- partitioned tables
+--
+
+-- cannot use system columns in partition key
+CREATE TABLE fail_oid_pk (
+ a int
+) PARTITION BY RANGE (oid) WITH OIDS; -- fail
+
+-- cannot use more than 1 column as partition key for list partitioned table
+CREATE TABLE two_col_tab (
+ a1 int,
+ a2 int
+) PARTITION BY LIST (a1, a2); -- fail
+
+-- cannot use more than 32 columns as partition key for range partitioned table
+CREATE TABLE wide_tab (
+ a01 int, a02 int, a03 int, a04 int, a05 int, a06 int, a07 int, a08 int,
+ a09 int, a10 int, a11 int, a12 int, a13 int, a14 int, a15 int, a16 int,
+ a17 int, a18 int, a19 int, a20 int, a21 int, a22 int, a23 int, a24 int,
+ a25 int, a26 int, a27 int, a28 int, a29 int, a30 int, a31 int, a32 int,
+ a33 int
+) PARTITION BY RANGE (a01, a02, a03, a04, a05, a06, a07, a08,
+ a09, a10, a11, a12, a13, a14, a15, a16,
+ a17, a18, a19, a20, a21, a22, a23, a24,
+ a25, a26, a27, a28, a29, a30, a31, a32, a33); -- fail
+
+-- Partitioning requires to use columns with data type having a suitable
+-- btree operator class
+CREATE TABLE point_tab (
+ a point
+) PARTITION BY LIST (a); -- fail
+CREATE TABLE point_tab (
+ a point
+) PARTITION BY LIST (a USING point_ops); -- fail
+CREATE TABLE point_tab (
+ a point
+) PARTITION BY RANGE (a); -- fail
+CREATE TABLE point_tab (
+ a point
+) PARTITION BY RANGE (a USING point_ops); -- fail
+
+-- fail to create a partitioned table with PRIMARY KEY constraint
+CREATE TABLE pk_fail (
+ a int PRIMARY KEY
+) PARTITION BY RANGE (a);
+
+-- fail to create a partitioned table with foreign key constraint
+CREATE TABLE pk_rel(
+ a int PRIMARY KEY
+);
+CREATE TABLE fk_fail (
+ a int REFERENCES pk_rel(a)
+) PARTITION BY RANGE (a);
+DROP TABLE pk_rel;
+
+-- fail to create a partitioned table with prohibited expressions in key
+CREATE TABLE fail_const_key (
+ a int
+) PARTITION BY RANGE (('a'));
+CREATE TABLE fail_agg_in_key (
+ a int
+) PARTITION BY RANGE ((avg(a)));
+CREATE TABLE fail_window_fun_in_key (
+ a int,
+ b int
+) PARTITION BY RANGE ((avg(a) OVER (PARTITION BY b)));
--
1.7.1
[text/x-diff] 0002-Infrastructure-for-creation-of-partitioned-tables-v3.patch (85.9K, ../../57183572.8020900@lab.ntt.co.jp/3-0002-Infrastructure-for-creation-of-partitioned-tables-v3.patch)
download | inline diff:
From 6b07810b54405a3a40125fadfd9c322d8fcb7eed Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Mon, 22 Feb 2016 17:38:55 +0900
Subject: [PATCH 2/5] Infrastructure for creation of partitioned tables.
First, this adds a new system catalog pg_partitioned with:
partedrelid oid
partstrat char
partnatts int16
partattrs int2vector
partclass oidvector
partexprs pg_node_tree
There is a unique index on partedrelid and associated syscache PARTEDRELID.
Second, a new relkind RELKIND_PARTITIONED_REL is added to pg_class. Tables
created with CREATE TABLE PARTITION BY are of this relkind. Some special
properties:
* Has a pg_partitioned entry
* Access to them unconditionally overrides specified inheritance option
* Dropping or altering type of columns that are part of the partition key
is disallowed
* Changing inheritance (ALTER TABLE table INHERIT parent) and linking to
a type (ALTER TABLE table OF type) is disallowed
Partition key is represented using struct PartitionKey and built during the
first relcache build of a partitioned relation. It is stored in rd_partkey
using the cache context.
---
doc/src/sgml/catalogs.sgml | 99 ++++++
src/backend/access/common/reloptions.c | 2 +
src/backend/catalog/Makefile | 4 +-
src/backend/catalog/aclchk.c | 2 +
src/backend/catalog/heap.c | 19 +-
src/backend/catalog/objectaddress.c | 10 +-
src/backend/catalog/partition.c | 451 ++++++++++++++++++++++++++++
src/backend/commands/copy.c | 6 +
src/backend/commands/indexcmds.c | 3 +-
src/backend/commands/lockcmds.c | 2 +-
src/backend/commands/policy.c | 2 +-
src/backend/commands/seclabel.c | 1 +
src/backend/commands/sequence.c | 1 +
src/backend/commands/tablecmds.c | 168 ++++++++++-
src/backend/commands/trigger.c | 7 +-
src/backend/commands/vacuum.c | 11 +-
src/backend/executor/execMain.c | 1 +
src/backend/executor/nodeModifyTable.c | 1 +
src/backend/parser/parse_agg.c | 6 +-
src/backend/parser/parse_clause.c | 8 +
src/backend/parser/parse_expr.c | 2 +-
src/backend/parser/parse_relation.c | 6 +-
src/backend/parser/parse_utilcmd.c | 4 +-
src/backend/rewrite/rewriteDefine.c | 1 +
src/backend/rewrite/rewriteHandler.c | 1 +
src/backend/tcop/utility.c | 5 +-
src/backend/utils/adt/ruleutils.c | 147 +++++++++
src/backend/utils/cache/relcache.c | 14 +-
src/backend/utils/cache/syscache.c | 12 +
src/bin/pg_dump/pg_dump.c | 36 ++-
src/bin/pg_dump/pg_dump.h | 1 +
src/bin/psql/describe.c | 61 +++-
src/bin/psql/tab-complete.c | 6 +-
src/include/catalog/indexing.h | 3 +
src/include/catalog/partition.h | 55 ++++
src/include/catalog/pg_class.h | 1 +
src/include/catalog/pg_partitioned.h | 67 ++++
src/include/catalog/pg_proc.h | 2 +
src/include/commands/defrem.h | 4 +-
src/include/utils/builtins.h | 1 +
src/include/utils/rel.h | 2 +
src/include/utils/syscache.h | 1 +
src/test/regress/expected/create_table.out | 30 ++-
src/test/regress/expected/sanity_check.out | 1 +
src/test/regress/sql/create_table.sql | 21 ++
45 files changed, 1227 insertions(+), 61 deletions(-)
create mode 100644 src/backend/catalog/partition.c
create mode 100644 src/include/catalog/partition.h
create mode 100644 src/include/catalog/pg_partitioned.h
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index d6b60db..ba658b1 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -224,6 +224,11 @@
</row>
<row>
+ <entry><link linkend="catalog-pg-partitioned"><structname>pg_partitioned</structname></link></entry>
+ <entry>information about partitioned tables, including the partition key</entry>
+ </row>
+
+ <row>
<entry><link linkend="catalog-pg-policy"><structname>pg_policy</structname></link></entry>
<entry>row-security policies</entry>
</row>
@@ -4682,6 +4687,100 @@
</sect1>
+ <sect1 id="catalog-pg-partitioned">
+ <title><structname>pg_partitioned</structname></title>
+
+ <indexterm zone="catalog-pg-partitioned">
+ <primary>pg_partitioned</primary>
+ </indexterm>
+
+ <para>
+ The catalog <structname>pg_partitioned</structname> stores information
+ about the partition key of partitioned tables.
+ </para>
+
+ <table>
+ <title><structname>pg_partitioned</> Columns</title>
+
+ <tgroup cols="4">
+ <thead>
+ <row>
+ <entry>Name</entry>
+ <entry>Type</entry>
+ <entry>References</entry>
+ <entry>Description</entry>
+ </row>
+ </thead>
+
+ <tbody>
+
+ <row>
+ <entry><structfield>partedrelid</structfield></entry>
+ <entry><type>oid</type></entry>
+ <entry><literal><link linkend="catalog-pg-class"><structname>pg_class</structname></link>.oid</literal></entry>
+ <entry>The OID of the <structname>pg_class</> entry for this partitioned table</entry>
+ </row>
+
+ <row>
+ <entry><structfield>partstrat</structfield></entry>
+ <entry><type>char</type></entry>
+ <entry></entry>
+ <entry>
+ Partitioning strategy (or method); <literal>l</> = list partitioned table,
+ <literal>r</> = range partitioned table
+ </entry>
+ </row>
+
+ <row>
+ <entry><structfield>partnatts</structfield></entry>
+ <entry><type>int2</type></entry>
+ <entry></entry>
+ <entry>The number of columns in partition key</entry>
+ </row>
+
+ <row>
+ <entry><structfield>partattrs</structfield></entry>
+ <entry><type>int2vector</type></entry>
+ <entry><literal><link linkend="catalog-pg-attribute"><structname>pg_attribute</structname></link>.attnum</literal></entry>
+ <entry>
+ This is an array of <structfield>partnatts</structfield> values that
+ indicate which table columns are used as partition key. For example,
+ a value of <literal>1 3</literal> would mean that the first and the
+ third table columns make up the partition key. A zero in this array
+ indicates that the corresponding partition key column is an expression
+ over the table columns, rather than a simple column reference.
+ </entry>
+ </row>
+
+ <row>
+ <entry><structfield>partclass</structfield></entry>
+ <entry><type>oidvector</type></entry>
+ <entry><literal><link linkend="catalog-pg-opclass"><structname>pg_opclass</structname></link>.oid</literal></entry>
+ <entry>
+ For each column in the partition key, this contains the OID of
+ the operator class to use. See
+ <link linkend="catalog-pg-opclass"><structname>pg_opclass</structname></link> for details.
+ </entry>
+ </row>
+
+ <row>
+ <entry><structfield>partexprs</structfield></entry>
+ <entry><type>pg_node_tree</type></entry>
+ <entry></entry>
+ <entry>
+ Expression trees (in <function>nodeToString()</function>
+ representation) for partition key columns that are not simple column
+ references. This is a list with one element for each zero
+ entry in <structfield>partkey</>. Null if all partition key columns
+ are simple references.
+ </entry>
+ </row>
+
+ </tbody>
+ </tgroup>
+ </table>
+ </sect1>
+
<sect1 id="catalog-pg-policy">
<title><structname>pg_policy</structname></title>
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 797be63..bfccf9b 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -925,6 +925,7 @@ extractRelOptions(HeapTuple tuple, TupleDesc tupdesc,
case RELKIND_RELATION:
case RELKIND_TOASTVALUE:
case RELKIND_MATVIEW:
+ case RELKIND_PARTITIONED_REL:
options = heap_reloptions(classForm->relkind, datum, false);
break;
case RELKIND_VIEW:
@@ -1376,6 +1377,7 @@ heap_reloptions(char relkind, Datum reloptions, bool validate)
return (bytea *) rdopts;
case RELKIND_RELATION:
case RELKIND_MATVIEW:
+ case RELKIND_PARTITIONED_REL:
return default_reloptions(reloptions, validate, RELOPT_KIND_HEAP);
default:
/* other relkinds are not supported */
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index 1ce7610..4dc6300 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -11,7 +11,7 @@ top_builddir = ../../..
include $(top_builddir)/src/Makefile.global
OBJS = catalog.o dependency.o heap.o index.o indexing.o namespace.o aclchk.o \
- objectaccess.o objectaddress.o pg_aggregate.o pg_collation.o \
+ objectaccess.o objectaddress.o partition.o pg_aggregate.o pg_collation.o \
pg_constraint.o pg_conversion.o \
pg_depend.o pg_enum.o pg_inherits.o pg_largeobject.o pg_namespace.o \
pg_operator.o pg_proc.o pg_range.o pg_db_role_setting.o pg_shdepend.o \
@@ -41,7 +41,7 @@ POSTGRES_BKI_SRCS = $(addprefix $(top_srcdir)/src/include/catalog/,\
pg_foreign_data_wrapper.h pg_foreign_server.h pg_user_mapping.h \
pg_foreign_table.h pg_policy.h pg_replication_origin.h \
pg_default_acl.h pg_init_privs.h pg_seclabel.h pg_shseclabel.h \
- pg_collation.h pg_range.h pg_transform.h \
+ pg_collation.h pg_range.h pg_transform.h pg_partitioned.h\
toasting.h indexing.h \
)
diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
index 7d656d5..2070c7f 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -765,6 +765,8 @@ objectsInSchemaToOids(GrantObjectType objtype, List *nspnames)
case ACL_OBJECT_RELATION:
objs = getRelationsInNamespace(namespaceId, RELKIND_RELATION);
objects = list_concat(objects, objs);
+ objs = getRelationsInNamespace(namespaceId, RELKIND_PARTITIONED_REL);
+ objects = list_concat(objects, objs);
objs = getRelationsInNamespace(namespaceId, RELKIND_VIEW);
objects = list_concat(objects, objs);
objs = getRelationsInNamespace(namespaceId, RELKIND_MATVIEW);
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index e997b57..932a48b 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -41,6 +41,7 @@
#include "catalog/heap.h"
#include "catalog/index.h"
#include "catalog/objectaccess.h"
+#include "catalog/partition.h"
#include "catalog/pg_attrdef.h"
#include "catalog/pg_collation.h"
#include "catalog/pg_constraint.h"
@@ -1101,9 +1102,10 @@ heap_create_with_catalog(const char *relname,
{
/* Use binary-upgrade override for pg_class.oid/relfilenode? */
if (IsBinaryUpgrade &&
- (relkind == RELKIND_RELATION || relkind == RELKIND_SEQUENCE ||
- relkind == RELKIND_VIEW || relkind == RELKIND_MATVIEW ||
- relkind == RELKIND_COMPOSITE_TYPE || relkind == RELKIND_FOREIGN_TABLE))
+ (relkind == RELKIND_RELATION || relkind == RELKIND_PARTITIONED_REL ||
+ relkind == RELKIND_SEQUENCE || relkind == RELKIND_VIEW ||
+ relkind == RELKIND_MATVIEW || relkind == RELKIND_COMPOSITE_TYPE ||
+ relkind == RELKIND_FOREIGN_TABLE))
{
if (!OidIsValid(binary_upgrade_next_heap_pg_class_oid))
ereport(ERROR,
@@ -1134,6 +1136,7 @@ heap_create_with_catalog(const char *relname,
switch (relkind)
{
case RELKIND_RELATION:
+ case RELKIND_PARTITIONED_REL:
case RELKIND_VIEW:
case RELKIND_MATVIEW:
case RELKIND_FOREIGN_TABLE:
@@ -1178,6 +1181,7 @@ heap_create_with_catalog(const char *relname,
* such is an implementation detail: toast tables, sequences and indexes.
*/
if (IsUnderPostmaster && (relkind == RELKIND_RELATION ||
+ relkind == RELKIND_PARTITIONED_REL ||
relkind == RELKIND_VIEW ||
relkind == RELKIND_MATVIEW ||
relkind == RELKIND_FOREIGN_TABLE ||
@@ -1353,7 +1357,8 @@ heap_create_with_catalog(const char *relname,
if (relpersistence == RELPERSISTENCE_UNLOGGED)
{
Assert(relkind == RELKIND_RELATION || relkind == RELKIND_MATVIEW ||
- relkind == RELKIND_TOASTVALUE);
+ relkind == RELKIND_TOASTVALUE || relkind == RELKIND_PARTITIONED_REL);
+
heap_create_init_fork(new_rel_desc);
}
@@ -1800,6 +1805,12 @@ heap_drop_with_catalog(Oid relid)
}
/*
+ * If a partitioned table, delete the pg_partitioned tuples.
+ */
+ if (rel->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+ RemovePartitionKeyByRelId(relid);
+
+ /*
* Schedule unlinking of the relation's physical files at commit.
*/
if (rel->rd_rel->relkind != RELKIND_VIEW &&
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index 1324461..215375e 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -1204,7 +1204,8 @@ get_relation_by_qualified_name(ObjectType objtype, List *objname,
RelationGetRelationName(relation))));
break;
case OBJECT_TABLE:
- if (relation->rd_rel->relkind != RELKIND_RELATION)
+ if (relation->rd_rel->relkind != RELKIND_RELATION &&
+ relation->rd_rel->relkind != RELKIND_PARTITIONED_REL)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not a table",
@@ -3252,6 +3253,10 @@ getRelationDescription(StringInfo buffer, Oid relid)
appendStringInfo(buffer, _("table %s"),
relname);
break;
+ case RELKIND_PARTITIONED_REL:
+ appendStringInfo(buffer, _("partitioned table %s"),
+ relname);
+ break;
case RELKIND_INDEX:
appendStringInfo(buffer, _("index %s"),
relname);
@@ -3708,6 +3713,9 @@ getRelationTypeDescription(StringInfo buffer, Oid relid, int32 objectSubId)
case RELKIND_RELATION:
appendStringInfoString(buffer, "table");
break;
+ case RELKIND_PARTITIONED_REL:
+ appendStringInfoString(buffer, "partitioned table");
+ break;
case RELKIND_INDEX:
appendStringInfoString(buffer, "index");
break;
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
new file mode 100644
index 0000000..5e238dc
--- /dev/null
+++ b/src/backend/catalog/partition.c
@@ -0,0 +1,451 @@
+/*-------------------------------------------------------------------------
+ *
+ * partition.c
+ * Partitioning related utility functions.
+ *
+ * Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ * src/backend/utils/misc/partition.c
+ *
+ *-------------------------------------------------------------------------
+*/
+
+#include "postgres.h"
+
+#include "access/heapam.h"
+#include "access/htup_details.h"
+#include "access/nbtree.h"
+#include "catalog/dependency.h"
+#include "catalog/indexing.h"
+#include "catalog/objectaddress.h"
+#include "catalog/partition.h"
+#include "catalog/pg_collation.h"
+#include "catalog/pg_opclass.h"
+#include "catalog/pg_partitioned.h"
+#include "catalog/pg_type.h"
+#include "executor/executor.h"
+#include "miscadmin.h"
+#include "nodes/nodeFuncs.h"
+#include "optimizer/clauses.h"
+#include "optimizer/planmain.h"
+#include "storage/lmgr.h"
+#include "utils/array.h"
+#include "utils/builtins.h"
+#include "utils/datum.h"
+#include "utils/memutils.h"
+#include "utils/fmgroids.h"
+#include "utils/inval.h"
+#include "utils/lsyscache.h"
+#include "utils/syscache.h"
+
+static PartitionKey CopyPartitionKey(PartitionKey fromkey);
+static KeyTypeCollInfo *copy_key_type_coll_info(int nkeycols,
+ KeyTypeCollInfo *tcinfo);
+static void free_key_type_coll_info(KeyTypeCollInfo *tcinfo);
+
+/*
+ * StorePartitionKey
+ * Store the partition keys of rel into pg_partitioned catalog
+ */
+void
+StorePartitionKey(Relation rel,
+ char strategy,
+ int16 partnatts,
+ AttrNumber *partattrs,
+ List *partexprs,
+ Oid *partopclass)
+{
+ int i;
+ int2vector *partattrs_vec;
+ oidvector *partopclass_vec;
+ Datum partexprsDatum;
+ Relation pg_partitioned;
+ HeapTuple tuple;
+ Datum values[Natts_pg_partitioned];
+ bool nulls[Natts_pg_partitioned];
+ ObjectAddress myself;
+ ObjectAddress referenced;
+
+ Assert(rel->rd_rel->relkind == RELKIND_PARTITIONED_REL);
+
+ tuple = SearchSysCache1(PARTEDRELID,
+ ObjectIdGetDatum(RelationGetRelid(rel)));
+ /* Cannot already exist */
+ Assert(!HeapTupleIsValid(tuple));
+
+ /*
+ * Copy the partition key, opclass info into arrays (should we
+ * make the caller pass them like this to start with?)
+ */
+ partattrs_vec = buildint2vector(partattrs, partnatts);
+ partopclass_vec = buildoidvector(partopclass, partnatts);
+
+ /* Convert the partition key expressions (if any) to a text datum */
+ if (partexprs)
+ {
+ char *exprsString;
+
+ exprsString = nodeToString(partexprs);
+ partexprsDatum = CStringGetTextDatum(exprsString);
+ pfree(exprsString);
+ }
+ else
+ partexprsDatum = (Datum) 0;
+
+ pg_partitioned = heap_open(PartitionedRelationId, RowExclusiveLock);
+
+ MemSet(nulls, false, sizeof(nulls));
+
+ /* Only this can ever be NULL */
+ if (!partexprsDatum)
+ nulls[Anum_pg_partitioned_partexprs - 1] = true;
+
+ values[Anum_pg_partitioned_partedrelid - 1] = ObjectIdGetDatum(RelationGetRelid(rel));
+ values[Anum_pg_partitioned_partstrat - 1] = CharGetDatum(strategy);
+ values[Anum_pg_partitioned_partnatts - 1] = Int16GetDatum(partnatts);
+ values[Anum_pg_partitioned_partattrs - 1] = PointerGetDatum(partattrs_vec);
+ values[Anum_pg_partitioned_partclass - 1] = PointerGetDatum(partopclass_vec);
+ values[Anum_pg_partitioned_partexprs - 1] = partexprsDatum;
+
+ tuple = heap_form_tuple(RelationGetDescr(pg_partitioned), values, nulls);
+
+ simple_heap_insert(pg_partitioned, tuple);
+
+ /* Update the indexes on pg_partitioned */
+ CatalogUpdateIndexes(pg_partitioned, tuple);
+
+ /* Make this relation dependent on a few things: */
+ myself.classId = RelationRelationId;
+ myself.objectId = RelationGetRelid(rel);;
+ myself.objectSubId = 0;
+
+ /* Operator class per key column */
+ for (i = 0; i < partnatts; i++)
+ {
+ referenced.classId = OperatorClassRelationId;
+ referenced.objectId = partopclass[i];
+ referenced.objectSubId = 0;
+
+ recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+ }
+
+ /*
+ * Things inside key expressions
+ *
+ * An ugliness: normal dependencies also created on attribute references
+ * as part of the following.
+ */
+ if (partexprs)
+ recordDependencyOnSingleRelExpr(&myself,
+ (Node *) partexprs,
+ RelationGetRelid(rel),
+ DEPENDENCY_NORMAL,
+ DEPENDENCY_NORMAL);
+ /* Tell world about the key */
+ CacheInvalidateRelcache(rel);
+
+ heap_close(pg_partitioned, RowExclusiveLock);
+ heap_freetuple(tuple);
+}
+
+/*
+ * RemovePartitionKeyByRelId
+ * Remove pg_partitioned entry for a relation
+ */
+void
+RemovePartitionKeyByRelId(Oid relid)
+{
+ Relation rel;
+ HeapTuple tuple;
+
+ rel = heap_open(PartitionedRelationId, RowExclusiveLock);
+
+ tuple = SearchSysCache1(PARTEDRELID, ObjectIdGetDatum(relid));
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "cache lookup failed for partition key of relation %u", relid);
+
+ simple_heap_delete(rel, &tuple->t_self);
+
+ /* Update the indexes on pg_partitioned */
+ CatalogUpdateIndexes(rel, tuple);
+
+ ReleaseSysCache(tuple);
+ heap_close(rel, RowExclusiveLock);
+}
+
+/*
+ * RelationBuildPartitionKey
+ * Build and attach to relcache partition key data of relation
+ *
+ * Note that the partition key data attached to a relcache entry must be
+ * stored CacheMemoryContext to ensure it survives as long as the relcache
+ * entry. But we should be running in a less long-lived working context.
+ * To avoid leaking cache memory if this routine fails partway through,
+ * we build in working memory and then copy the completed structure into
+ * cache memory.
+ */
+void
+RelationBuildPartitionKey(Relation relation)
+{
+ Form_pg_partitioned form;
+ Relation catalog;
+ HeapTuple tuple;
+ bool isnull;
+ int i;
+ PartitionKey key;
+ int2vector *partattrs;
+ oidvector *opclass;
+ KeyTypeCollInfo *tcinfo;
+ ListCell *partexprs_item;
+ Datum datum;
+ MemoryContext oldcxt;
+
+ tuple = SearchSysCache1(PARTEDRELID,
+ ObjectIdGetDatum(RelationGetRelid(relation)));
+ /*
+ * Happend when relcache is built before the partition key is stored in
+ * system catalog
+ */
+ if (!HeapTupleIsValid(tuple))
+ return;
+
+ form = (Form_pg_partitioned) GETSTRUCT(tuple);
+
+ /* Allocate in the hopefully short-lived working context */
+ key = (PartitionKey) palloc0(sizeof(PartitionKeyData));
+ key->strategy = form->partstrat;
+ key->partnatts = form->partnatts;
+
+ /* Open the catalog for its tuple descriptor */
+ catalog = heap_open(PartitionedRelationId, AccessShareLock);
+ datum = fastgetattr(tuple, Anum_pg_partitioned_partattrs,
+ RelationGetDescr(catalog),
+ &isnull);
+ Assert(!isnull);
+ partattrs = (int2vector *) DatumGetPointer(datum);
+
+ datum = fastgetattr(tuple, Anum_pg_partitioned_partclass,
+ RelationGetDescr(catalog),
+ &isnull);
+ Assert(!isnull);
+ opclass = (oidvector *) DatumGetPointer(datum);
+
+ datum = heap_getattr(tuple,
+ Anum_pg_partitioned_partexprs,
+ RelationGetDescr(catalog),
+ &isnull);
+
+ if (!isnull)
+ {
+ char *exprsString;
+ Node *exprs;
+
+ exprsString = TextDatumGetCString(datum);
+ exprs = stringToNode(exprsString);
+ pfree(exprsString);
+
+ /*
+ * Run the expressions through eval_const_expressions. This is
+ * not just an optimization, but is necessary, because eventually
+ * the planner will be comparing them to similarly-processed qual
+ * clauses, and may fail to detect valid matches without this.
+ * We don't bother with canonicalize_qual, however.
+ */
+ exprs = eval_const_expressions(NULL, (Node *) exprs);
+
+ /* May as well fix opfuncids too */
+ fix_opfuncids((Node *) exprs);
+ key->partexprs = (List *) exprs;
+ }
+
+ key->partattrs = (AttrNumber *) palloc0(key->partnatts * sizeof(AttrNumber));
+ key->partopfamily = (Oid *) palloc0(key->partnatts * sizeof(Oid));
+ key->partsupfunc = (FmgrInfo *) palloc0(key->partnatts * sizeof(FmgrInfo));
+
+ /* Gather type and collation info as well */
+ key->tcinfo = tcinfo = (KeyTypeCollInfo *) palloc0(sizeof(KeyTypeCollInfo));
+ tcinfo->typid = (Oid *) palloc0(key->partnatts * sizeof(Oid));
+ tcinfo->typmod = (int32 *) palloc0(key->partnatts * sizeof(int32));
+ tcinfo->typlen = (int16 *) palloc0(key->partnatts * sizeof(int16));
+ tcinfo->typbyval = (bool *) palloc0(key->partnatts * sizeof(bool));
+ tcinfo->typalign = (char *) palloc0(key->partnatts * sizeof(char));
+ tcinfo->typcoll = (Oid *) palloc0(key->partnatts * sizeof(Oid));
+
+ /*
+ * Copy partattrs. Further, determine and store the opfamily and
+ * btree support function per attribute.
+ */
+ partexprs_item = list_head(key->partexprs);
+ for (i = 0; i < key->partnatts; i++)
+ {
+ HeapTuple tuple;
+ AttrNumber attno;
+ Form_pg_opclass form;
+ Oid funcid;
+
+ key->partattrs[i] = attno = partattrs->values[i];
+
+ /* Collect type information */
+ if (attno != InvalidAttrNumber)
+ {
+ tcinfo->typid[i] = relation->rd_att->attrs[attno - 1]->atttypid;
+ tcinfo->typmod[i] = relation->rd_att->attrs[attno - 1]->atttypmod;
+ tcinfo->typcoll[i] = relation->rd_att->attrs[attno - 1]->attcollation;
+ }
+ else
+ {
+ tcinfo->typid[i] = exprType(lfirst(partexprs_item));
+ tcinfo->typmod[i] = exprTypmod(lfirst(partexprs_item));
+ tcinfo->typcoll[i] = exprCollation(lfirst(partexprs_item));
+ partexprs_item = lnext(partexprs_item);
+ }
+ get_typlenbyvalalign(tcinfo->typid[i],
+ &tcinfo->typlen[i],
+ &tcinfo->typbyval[i],
+ &tcinfo->typalign[i]);
+
+ tuple = SearchSysCache1(CLAOID, ObjectIdGetDatum(opclass->values[i]));
+
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "cache lookup failed for opclass %u", opclass->values[i]);
+
+ form = (Form_pg_opclass) GETSTRUCT(tuple);
+ key->partopfamily[i] = form->opcfamily;
+
+ /*
+ * Btree support function is good to cover the list and range
+ * partitionig related needs.
+ */
+ funcid = get_opfamily_proc(form->opcfamily,
+ form->opcintype, form->opcintype,
+ BTORDER_PROC);
+
+ fmgr_info(funcid, &key->partsupfunc[i]);
+ ReleaseSysCache(tuple);
+ }
+
+ ReleaseSysCache(tuple);
+ heap_close(catalog, AccessShareLock);
+
+ /* Success --- now copy to the cache memory */
+ oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
+ relation->rd_partkey = CopyPartitionKey(key);
+ MemoryContextSwitchTo(oldcxt);
+
+ FreePartitionKey(key);
+}
+
+/*
+ * CopyPartitionKey
+ *
+ * The copy is allocated in the current memory context.
+ */
+static PartitionKey
+CopyPartitionKey(PartitionKey fromkey)
+{
+ PartitionKey newkey;
+
+ newkey = (PartitionKey) palloc0(sizeof(PartitionKeyData));
+
+ newkey->strategy = fromkey->strategy;
+ newkey->partnatts = fromkey->partnatts;
+
+ newkey->partattrs = (AttrNumber *)
+ palloc(newkey->partnatts * sizeof(AttrNumber));
+ memcpy(newkey->partattrs, fromkey->partattrs,
+ newkey->partnatts * sizeof(AttrNumber));
+
+ newkey->partopfamily = (Oid *) palloc(newkey->partnatts * sizeof(Oid));
+ memcpy(newkey->partopfamily, fromkey->partopfamily,
+ newkey->partnatts * sizeof(Oid));
+
+ newkey->partsupfunc = (FmgrInfo *)
+ palloc(newkey->partnatts * sizeof(FmgrInfo));
+ memcpy(newkey->partsupfunc, fromkey->partsupfunc,
+ newkey->partnatts * sizeof(FmgrInfo));
+
+ newkey->partexprs = copyObject(fromkey->partexprs);
+ newkey->tcinfo = copy_key_type_coll_info(newkey->partnatts,
+ fromkey->tcinfo);
+
+ return newkey;
+}
+
+/*
+ * copy_key_type_coll_info
+ *
+ * The copy is allocated in the current memory context.
+ */
+static KeyTypeCollInfo *
+copy_key_type_coll_info(int nkeycols, KeyTypeCollInfo *tcinfo)
+{
+ KeyTypeCollInfo *result = (KeyTypeCollInfo *)
+ palloc0(sizeof(KeyTypeCollInfo));
+
+ result->typid = (Oid *) palloc0(nkeycols * sizeof(Oid));
+ memcpy(result->typid, tcinfo->typid, nkeycols * sizeof(Oid));
+
+ result->typmod = (int32 *) palloc0(nkeycols * sizeof(int32));
+ memcpy(result->typmod, tcinfo->typmod, nkeycols * sizeof(int32));
+
+ result->typlen = (int16 *) palloc0(nkeycols * sizeof(int16));
+ memcpy(result->typlen, tcinfo->typlen, nkeycols * sizeof(int16));
+
+ result->typbyval = (bool *) palloc0(nkeycols * sizeof(bool));
+ memcpy(result->typbyval, tcinfo->typbyval, nkeycols * sizeof(bool));
+
+ result->typalign = (char *) palloc0(nkeycols * sizeof(bool));
+ memcpy(result->typalign, tcinfo->typalign, nkeycols * sizeof(char));
+
+ result->typcoll = (Oid *) palloc0(nkeycols * sizeof(Oid));
+ memcpy(result->typcoll, tcinfo->typcoll, nkeycols * sizeof(Oid));
+
+ return result;
+}
+
+/*
+ * free_key_type_info
+ */
+static void
+free_key_type_coll_info(KeyTypeCollInfo *tcinfo)
+{
+ Assert(tcinfo != NULL);
+
+ pfree(tcinfo->typid);
+ pfree(tcinfo->typmod);
+ pfree(tcinfo->typlen);
+ pfree(tcinfo->typbyval);
+ pfree(tcinfo->typalign);
+ pfree(tcinfo->typcoll);
+ pfree(tcinfo);
+}
+
+/*
+ * Does relid have a pg_partitioned entry?
+ */
+bool
+relid_is_partitioned(Oid relid)
+{
+ return SearchSysCacheExists1(PARTEDRELID, ObjectIdGetDatum(relid));
+}
+
+/*
+ * FreePartitionKeys
+ */
+void
+FreePartitionKey(PartitionKey key)
+{
+ if (key == NULL)
+ return;
+
+ pfree(key->partattrs);
+ pfree(key->partopfamily);
+ pfree(key->partsupfunc);
+ if (key->partexprs)
+ pfree(key->partexprs);
+ free_key_type_coll_info(key->tcinfo);
+ pfree(key);
+}
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 3201476..403738d 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -1718,6 +1718,12 @@ BeginCopyTo(Relation rel,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot copy from sequence \"%s\"",
RelationGetRelationName(rel))));
+ else if (rel->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot copy from partitioned table \"%s\"",
+ RelationGetRelationName(rel)),
+ errhint("Try the COPY (SELECT ...) TO variant.")));
else
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 4ef6b80..e8d25e8 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -369,7 +369,8 @@ DefineIndex(Oid relationId,
namespaceId = RelationGetNamespace(rel);
if (rel->rd_rel->relkind != RELKIND_RELATION &&
- rel->rd_rel->relkind != RELKIND_MATVIEW)
+ rel->rd_rel->relkind != RELKIND_MATVIEW &&
+ rel->rd_rel->relkind != RELKIND_PARTITIONED_REL)
{
if (rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
diff --git a/src/backend/commands/lockcmds.c b/src/backend/commands/lockcmds.c
index 175d1f3..62671a8 100644
--- a/src/backend/commands/lockcmds.c
+++ b/src/backend/commands/lockcmds.c
@@ -88,7 +88,7 @@ RangeVarCallbackForLockTable(const RangeVar *rv, Oid relid, Oid oldrelid,
* check */
/* Currently, we only allow plain tables to be locked */
- if (relkind != RELKIND_RELATION)
+ if (relkind != RELKIND_RELATION && relkind != RELKIND_PARTITIONED_REL)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not a table",
diff --git a/src/backend/commands/policy.c b/src/backend/commands/policy.c
index 146b36c..7449273 100644
--- a/src/backend/commands/policy.c
+++ b/src/backend/commands/policy.c
@@ -88,7 +88,7 @@ RangeVarCallbackForPolicy(const RangeVar *rv, Oid relid, Oid oldrelid,
rv->relname)));
/* Relation type MUST be a table. */
- if (relkind != RELKIND_RELATION)
+ if (relkind != RELKIND_RELATION && relkind != RELKIND_PARTITIONED_REL)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not a table", rv->relname)));
diff --git a/src/backend/commands/seclabel.c b/src/backend/commands/seclabel.c
index 5bd7e12..8bc8638 100644
--- a/src/backend/commands/seclabel.c
+++ b/src/backend/commands/seclabel.c
@@ -107,6 +107,7 @@ ExecSecLabelStmt(SecLabelStmt *stmt)
* are the only relkinds for which pg_dump will dump labels).
*/
if (relation->rd_rel->relkind != RELKIND_RELATION &&
+ relation->rd_rel->relkind != RELKIND_PARTITIONED_REL &&
relation->rd_rel->relkind != RELKIND_VIEW &&
relation->rd_rel->relkind != RELKIND_MATVIEW &&
relation->rd_rel->relkind != RELKIND_COMPOSITE_TYPE &&
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index c98f981..5dcd4cf 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -1467,6 +1467,7 @@ process_owned_by(Relation seqrel, List *owned_by)
/* Must be a regular or foreign table */
if (!(tablerel->rd_rel->relkind == RELKIND_RELATION ||
+ tablerel->rd_rel->relkind == RELKIND_PARTITIONED_REL ||
tablerel->rd_rel->relkind == RELKIND_FOREIGN_TABLE))
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index c881ff0..fcae323 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -29,6 +29,7 @@
#include "catalog/indexing.h"
#include "catalog/namespace.h"
#include "catalog/objectaccess.h"
+#include "catalog/partition.h"
#include "catalog/pg_am.h"
#include "catalog/pg_collation.h"
#include "catalog/pg_constraint.h"
@@ -262,6 +263,12 @@ struct DropRelationCallbackState
bool concurrent;
};
+/* for find_attr_reference_walker */
+typedef struct
+{
+ AttrNumber attnum;
+} find_attr_reference_context;
+
/* Alter table target-type flags for ATSimplePermissions */
#define ATT_TABLE 0x0001
#define ATT_VIEW 0x0002
@@ -438,6 +445,9 @@ static void ComputePartitionAttrs(Oid relid, List *partParams,
AttrNumber *partattrs,
List **partexprs,
Oid *partoplass);
+static bool find_attr_reference_walker(Node *node,
+ find_attr_reference_context *context);
+static bool is_partition_attr(Relation rel, AttrNumber attnum, bool *is_expr);
/* ----------------------------------------------------------------
@@ -601,7 +611,8 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
* affect other relkinds, but it would complicate interpretOidsOption().
*/
localHasOids = interpretOidsOption(stmt->options,
- (relkind == RELKIND_RELATION));
+ (relkind == RELKIND_RELATION ||
+ relkind == RELKIND_PARTITIONED_REL));
descriptor->tdhasoid = (localHasOids || parentOidCount > 0);
/*
@@ -715,7 +726,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
AddRelationNewConstraints(rel, rawDefaults, stmt->constraints,
true, true, false);
- /* Process partition key, if any */
+ /* Process and store partition key, if any */
if (stmt->partby)
{
PartitionBy *partby;
@@ -728,6 +739,8 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
partby = transformPartitionBy(rel, stmt->partby);
ComputePartitionAttrs(relationId, stmt->partby->partParams,
partattrs, &partexprs, partopclass);
+ StorePartitionKey(rel, partby->strategy, partnatts, partattrs,
+ partexprs, partopclass);
}
ObjectAddressSet(address, RelationRelationId, relationId);
@@ -975,7 +988,15 @@ RangeVarCallbackForDropRelation(const RangeVar *rel, Oid relOid, Oid oldRelOid,
return; /* concurrently dropped, so nothing to do */
classform = (Form_pg_class) GETSTRUCT(tuple);
- if (classform->relkind != relkind)
+ /*
+ * Both normal and partitioned tables are dropped using DROP TABLE.
+ * RemoveRelations however never passes RELKIND_PARTITIONED_REL as
+ * relkind for OBJECT_TABLE relations. A mismatch below may have
+ * to do with that. So, check.
+ */
+ if (classform->relkind != relkind &&
+ (relkind == RELKIND_RELATION &&
+ classform->relkind != RELKIND_PARTITIONED_REL))
DropErrorMsgWrongType(rel->relname, classform->relkind, relkind);
/* Allow DROP to either table owner or schema owner */
@@ -1313,7 +1334,8 @@ truncate_check_rel(Relation rel)
AclResult aclresult;
/* Only allow truncate on regular tables */
- if (rel->rd_rel->relkind != RELKIND_RELATION)
+ if (rel->rd_rel->relkind != RELKIND_RELATION &&
+ rel->rd_rel->relkind != RELKIND_PARTITIONED_REL)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not a table",
@@ -1541,8 +1563,16 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
*/
relation = heap_openrv(parent, ShareUpdateExclusiveLock);
+ /* Cannot inherit from partitioned tables */
+ if (relation->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot inherit from partitioned table \"%s\"",
+ parent->relname)));
+
if (relation->rd_rel->relkind != RELKIND_RELATION &&
- relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
+ relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE &&
+ relation->rd_rel->relkind != RELKIND_PARTITIONED_REL)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("inherited relation \"%s\" is not a table or foreign table",
@@ -2182,6 +2212,7 @@ renameatt_check(Oid myrelid, Form_pg_class classform, bool recursing)
* restriction.
*/
if (relkind != RELKIND_RELATION &&
+ relkind != RELKIND_PARTITIONED_REL &&
relkind != RELKIND_VIEW &&
relkind != RELKIND_MATVIEW &&
relkind != RELKIND_COMPOSITE_TYPE &&
@@ -4313,6 +4344,7 @@ ATSimplePermissions(Relation rel, int allowed_targets)
switch (rel->rd_rel->relkind)
{
case RELKIND_RELATION:
+ case RELKIND_PARTITIONED_REL:
actual_target = ATT_TABLE;
break;
case RELKIND_VIEW:
@@ -4549,6 +4581,7 @@ find_composite_type_dependencies(Oid typeOid, Relation origRelation,
att = rel->rd_att->attrs[pg_depend->objsubid - 1];
if (rel->rd_rel->relkind == RELKIND_RELATION ||
+ rel->rd_rel->relkind == RELKIND_PARTITIONED_REL ||
rel->rd_rel->relkind == RELKIND_MATVIEW)
{
if (origTypeName)
@@ -5439,6 +5472,7 @@ ATPrepSetStatistics(Relation rel, const char *colName, Node *newValue, LOCKMODE
* allowSystemTableMods to be turned on.
*/
if (rel->rd_rel->relkind != RELKIND_RELATION &&
+ rel->rd_rel->relkind != RELKIND_PARTITIONED_REL &&
rel->rd_rel->relkind != RELKIND_MATVIEW &&
rel->rd_rel->relkind != RELKIND_INDEX &&
rel->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
@@ -5713,6 +5747,73 @@ ATPrepDropColumn(List **wqueue, Relation rel, bool recurse, bool recursing,
cmd->subtype = AT_DropColumnRecurse;
}
+/* Checks if a Var node is for a given attnum */
+static bool
+find_attr_reference_walker(Node *node, find_attr_reference_context *context)
+{
+ if (node == NULL)
+ return false;
+
+ if (IsA(node, Var))
+ {
+ Var *variable = (Var *) node;
+ AttrNumber attnum = variable->varattno;
+
+ if (attnum == context->attnum)
+ return true;
+ }
+
+ return expression_tree_walker(node, find_attr_reference_walker, context);
+}
+
+/*
+ * Checks if attnum is a partition attribute for rel
+ *
+ * Sets *is_expr if attnum is found to be referenced in some partition key
+ * expression.
+ */
+static bool
+is_partition_attr(Relation rel, AttrNumber attnum, bool *is_expr)
+{
+ PartitionKey key;
+ ListCell *partexpr_item;
+ int i;
+
+ if (rel->rd_rel->relkind != RELKIND_PARTITIONED_REL)
+ {
+ *is_expr = false;
+ return false;
+ }
+
+ key = rel->rd_partkey;
+
+ partexpr_item = list_head(key->partexprs);
+ for (i = 0; i < key->partnatts; i++)
+ {
+ AttrNumber partatt = key->partattrs[i];
+
+ if(partatt != 0)
+ {
+ *is_expr = false;
+ if (attnum == partatt)
+ return true;
+ }
+ else
+ {
+ find_attr_reference_context context;
+
+ *is_expr = true;
+ context.attnum = attnum;
+ if (find_attr_reference_walker(lfirst(partexpr_item), &context))
+ return true;
+
+ partexpr_item = lnext(partexpr_item);
+ }
+ }
+
+ return false;
+}
+
/*
* Return value is the address of the dropped column.
*/
@@ -5727,6 +5828,7 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
AttrNumber attnum;
List *children;
ObjectAddress object;
+ bool is_expr;
/* At top level, permission check was done in ATPrepCmd, else do it */
if (recursing)
@@ -5771,6 +5873,19 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
errmsg("cannot drop inherited column \"%s\"",
colName)));
+ /* Don't drop columns used in partition key */
+ if (is_partition_attr(rel, attnum, &is_expr))
+ {
+ if (!is_expr)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("cannot drop column named in partition key")));
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("cannot drop column referenced in partition key expression")));
+ }
+
ReleaseSysCache(tuple);
/*
@@ -6289,6 +6404,12 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
* Validity checks (permission checks wait till we have the column
* numbers)
*/
+ if (pkrel->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot reference relation \"%s\"", RelationGetRelationName(pkrel)),
+ errdetail("Referencing partitioned tables in foreign key constraints is not supported.")));
+
if (pkrel->rd_rel->relkind != RELKIND_RELATION)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
@@ -7883,6 +8004,7 @@ ATPrepAlterColumnType(List **wqueue,
NewColumnValue *newval;
ParseState *pstate = make_parsestate(NULL);
AclResult aclresult;
+ bool is_expr;
if (rel->rd_rel->reloftype && !recursing)
ereport(ERROR,
@@ -7913,6 +8035,19 @@ ATPrepAlterColumnType(List **wqueue,
errmsg("cannot alter inherited column \"%s\"",
colName)));
+ /* Don't alter columns used in partition key */
+ if (is_partition_attr(rel, attnum, &is_expr))
+ {
+ if (!is_expr)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("cannot alter column named in partition key")));
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("cannot alter column referenced in partition key expression")));
+ }
+
/* Look up the target type */
typenameTypeIdAndMod(NULL, typeName, &targettype, &targettypmod);
@@ -7928,7 +8063,8 @@ ATPrepAlterColumnType(List **wqueue,
list_make1_oid(rel->rd_rel->reltype),
false);
- if (tab->relkind == RELKIND_RELATION)
+ if (tab->relkind == RELKIND_RELATION ||
+ tab->relkind == RELKIND_PARTITIONED_REL)
{
/*
* Set up an expression to transform the old data value to the new
@@ -8955,6 +9091,7 @@ ATExecChangeOwner(Oid relationOid, Oid newOwnerId, bool recursing, LOCKMODE lock
switch (tuple_class->relkind)
{
case RELKIND_RELATION:
+ case RELKIND_PARTITIONED_REL:
case RELKIND_VIEW:
case RELKIND_MATVIEW:
case RELKIND_FOREIGN_TABLE:
@@ -9417,6 +9554,7 @@ ATExecSetRelOptions(Relation rel, List *defList, AlterTableType operation,
switch (rel->rd_rel->relkind)
{
case RELKIND_RELATION:
+ case RELKIND_PARTITIONED_REL:
case RELKIND_TOASTVALUE:
case RELKIND_MATVIEW:
(void) heap_reloptions(rel->rd_rel->relkind, newOptions, true);
@@ -9839,7 +9977,8 @@ AlterTableMoveAll(AlterTableMoveAllStmt *stmt)
/* Only move the object type requested */
if ((stmt->objtype == OBJECT_TABLE &&
- relForm->relkind != RELKIND_RELATION) ||
+ relForm->relkind != RELKIND_RELATION &&
+ relForm->relkind != RELKIND_PARTITIONED_REL) ||
(stmt->objtype == OBJECT_INDEX &&
relForm->relkind != RELKIND_INDEX) ||
(stmt->objtype == OBJECT_MATVIEW &&
@@ -10038,6 +10177,11 @@ ATPrepAddInherit(Relation child_rel)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot change inheritance of typed table")));
+
+ if (child_rel->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot change inheritance of partitioned table")));
}
/*
@@ -10089,6 +10233,12 @@ ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode)
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot inherit to temporary relation of another session")));
+ /* Prevent partitioned tables from becoming inheritance parents */
+ if (parent_rel->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot inherit from partitioned table \"%s\"", parent->relname)));
+
/*
* Check for duplicates in the list of parents, and determine the highest
* inhseqno already present; we'll use the next one for the new parent.
@@ -11467,6 +11617,7 @@ AlterTableNamespaceInternal(Relation rel, Oid oldNspOid, Oid nspOid,
/* Fix other dependent stuff */
if (rel->rd_rel->relkind == RELKIND_RELATION ||
+ rel->rd_rel->relkind == RELKIND_PARTITIONED_REL ||
rel->rd_rel->relkind == RELKIND_MATVIEW)
{
AlterIndexNamespaces(classRel, rel, oldNspOid, nspOid, objsMoved);
@@ -11916,7 +12067,7 @@ RangeVarCallbackOwnsTable(const RangeVar *relation,
if (!relkind)
return;
if (relkind != RELKIND_RELATION && relkind != RELKIND_TOASTVALUE &&
- relkind != RELKIND_MATVIEW)
+ relkind != RELKIND_MATVIEW && relkind != RELKIND_PARTITIONED_REL)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not a table or materialized view", relation->relname)));
@@ -12070,6 +12221,7 @@ RangeVarCallbackForAlterRelation(const RangeVar *rv, Oid relid, Oid oldrelid,
*/
if (IsA(stmt, AlterObjectSchemaStmt) &&
relkind != RELKIND_RELATION &&
+ relkind != RELKIND_PARTITIONED_REL &&
relkind != RELKIND_VIEW &&
relkind != RELKIND_MATVIEW &&
relkind != RELKIND_SEQUENCE &&
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index 6f728ff..c1e1c4d 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -174,7 +174,8 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
* Triggers must be on tables or views, and there are additional
* relation-type-specific restrictions.
*/
- if (rel->rd_rel->relkind == RELKIND_RELATION)
+ if (rel->rd_rel->relkind == RELKIND_RELATION ||
+ rel->rd_rel->relkind == RELKIND_PARTITIONED_REL)
{
/* Tables can't have INSTEAD OF triggers */
if (stmt->timing != TRIGGER_TYPE_BEFORE &&
@@ -1112,6 +1113,7 @@ RemoveTriggerById(Oid trigOid)
rel = heap_open(relid, AccessExclusiveLock);
if (rel->rd_rel->relkind != RELKIND_RELATION &&
+ rel->rd_rel->relkind != RELKIND_PARTITIONED_REL &&
rel->rd_rel->relkind != RELKIND_VIEW &&
rel->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
ereport(ERROR,
@@ -1218,7 +1220,8 @@ RangeVarCallbackForRenameTrigger(const RangeVar *rv, Oid relid, Oid oldrelid,
/* only tables and views can have triggers */
if (form->relkind != RELKIND_RELATION && form->relkind != RELKIND_VIEW &&
- form->relkind != RELKIND_FOREIGN_TABLE)
+ form->relkind != RELKIND_FOREIGN_TABLE &&
+ form->relkind != RELKIND_PARTITIONED_REL)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not a table, view, or foreign table",
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 93361a0..003a30f 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -1301,9 +1301,14 @@ vacuum_rel(Oid relid, RangeVar *relation, int options, VacuumParams *params)
onerel->rd_rel->relkind != RELKIND_MATVIEW &&
onerel->rd_rel->relkind != RELKIND_TOASTVALUE)
{
- ereport(WARNING,
- (errmsg("skipping \"%s\" --- cannot vacuum non-tables or special system tables",
- RelationGetRelationName(onerel))));
+ if (onerel->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+ ereport(WARNING,
+ (errmsg("skipping \"%s\" --- cannot vacuum partitioned tables",
+ RelationGetRelationName(onerel))));
+ else
+ ereport(WARNING,
+ (errmsg("skipping \"%s\" --- cannot vacuum non-tables or special system tables",
+ RelationGetRelationName(onerel))));
relation_close(onerel, lmode);
PopActiveSnapshot();
CommitTransactionCommand();
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index ac02304..67492a5 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -1019,6 +1019,7 @@ CheckValidResultRel(Relation resultRel, CmdType operation)
switch (resultRel->rd_rel->relkind)
{
case RELKIND_RELATION:
+ case RELKIND_PARTITIONED_REL:
/* OK */
break;
case RELKIND_SEQUENCE:
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index e62c8aa..7ad1140 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1871,6 +1871,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
relkind = resultRelInfo->ri_RelationDesc->rd_rel->relkind;
if (relkind == RELKIND_RELATION ||
+ relkind == RELKIND_PARTITIONED_REL ||
relkind == RELKIND_MATVIEW)
{
j->jf_junkAttNo = ExecFindJunkAttribute(j, "ctid");
diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c
index b56fd4f..77ffd84 100644
--- a/src/backend/parser/parse_agg.c
+++ b/src/backend/parser/parse_agg.c
@@ -488,9 +488,9 @@ check_agglevels_and_constraints(ParseState *pstate, Node *expr)
break;
case EXPR_KIND_PARTITION_KEY:
if (isAgg)
- err = _("aggregate functions are not allowed in partition key expressions");
+ err = _("aggregate functions are not allowed in partition key expression");
else
- err = _("grouping operations are not allowed in partition key expressions");
+ err = _("grouping operations are not allowed in partition key expression");
break;
@@ -852,7 +852,7 @@ transformWindowFuncCall(ParseState *pstate, WindowFunc *wfunc,
err = _("window functions are not allowed in trigger WHEN conditions");
break;
case EXPR_KIND_PARTITION_KEY:
- err = _("window functions are not allowed in partition key expressions");
+ err = _("window functions are not allowed in partition key expression");
break;
/*
diff --git a/src/backend/parser/parse_clause.c b/src/backend/parser/parse_clause.c
index 751de4b..307e383 100644
--- a/src/backend/parser/parse_clause.c
+++ b/src/backend/parser/parse_clause.c
@@ -21,6 +21,7 @@
#include "access/tsmapi.h"
#include "catalog/catalog.h"
#include "catalog/heap.h"
+#include "catalog/partition.h"
#include "catalog/pg_am.h"
#include "catalog/pg_constraint_fn.h"
#include "catalog/pg_type.h"
@@ -200,6 +201,13 @@ setTargetTable(ParseState *pstate, RangeVar *relation,
relation->alias, inh, false);
pstate->p_target_rangetblentry = rte;
+ /*
+ * Override specified inheritance option, if relation is a partitioned
+ * table and it's a target of INSERT (ie, alsoSource == false).
+ */
+ if (alsoSource)
+ rte->inh |= relid_is_partitioned(rte->relid);
+
/* assume new rte is at end */
rtindex = list_length(pstate->p_rtable);
Assert(rte == rt_fetch(rtindex, pstate->p_rtable));
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 70a663c..6a74c28 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -1716,7 +1716,7 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
err = _("cannot use subquery in trigger WHEN condition");
break;
case EXPR_KIND_PARTITION_KEY:
- err = _("cannot use subquery in partition key expressions");
+ err = _("cannot use subquery in partition key expression");
break;
/*
diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c
index 81332b5..abcc1d6 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -20,6 +20,7 @@
#include "access/sysattr.h"
#include "catalog/heap.h"
#include "catalog/namespace.h"
+#include "catalog/partition.h"
#include "catalog/pg_type.h"
#include "funcapi.h"
#include "nodes/makefuncs.h"
@@ -1216,9 +1217,12 @@ addRangeTableEntry(ParseState *pstate,
*
* The initial default on access checks is always check-for-READ-access,
* which is the right thing for all except target tables.
+ *
+ * Note: we override the specified inheritance option, if relation is a
+ * partitioned table.
*/
rte->lateral = false;
- rte->inh = inh;
+ rte->inh = inh || relid_is_partitioned(RelationGetRelid(rel));
rte->inFromCl = inFromCl;
rte->requiredPerms = ACL_SELECT;
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e267f74..465597d 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -33,6 +33,7 @@
#include "catalog/heap.h"
#include "catalog/index.h"
#include "catalog/namespace.h"
+#include "catalog/partition.h"
#include "catalog/pg_am.h"
#include "catalog/pg_collation.h"
#include "catalog/pg_constraint.h"
@@ -821,6 +822,7 @@ transformTableLikeClause(CreateStmtContext *cxt, TableLikeClause *table_like_cla
relation = relation_openrv(table_like_clause->relation, AccessShareLock);
if (relation->rd_rel->relkind != RELKIND_RELATION &&
+ relation->rd_rel->relkind != RELKIND_PARTITIONED_REL &&
relation->rd_rel->relkind != RELKIND_VIEW &&
relation->rd_rel->relkind != RELKIND_MATVIEW &&
relation->rd_rel->relkind != RELKIND_COMPOSITE_TYPE &&
@@ -2577,7 +2579,7 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
cxt.blist = NIL;
cxt.alist = NIL;
cxt.pkey = NULL;
- cxt.ispartitioned = false;
+ cxt.ispartitioned = relid_is_partitioned(relid);
/*
* The only subtypes that currently require parse transformation handling
diff --git a/src/backend/rewrite/rewriteDefine.c b/src/backend/rewrite/rewriteDefine.c
index 96f3f37..db80c90 100644
--- a/src/backend/rewrite/rewriteDefine.c
+++ b/src/backend/rewrite/rewriteDefine.c
@@ -260,6 +260,7 @@ DefineQueryRewrite(char *rulename,
* blocks them for users. Don't mention them in the error message.
*/
if (event_relation->rd_rel->relkind != RELKIND_RELATION &&
+ event_relation->rd_rel->relkind != RELKIND_PARTITIONED_REL &&
event_relation->rd_rel->relkind != RELKIND_MATVIEW &&
event_relation->rd_rel->relkind != RELKIND_VIEW)
ereport(ERROR,
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index a22a11e..f429dd6 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -1222,6 +1222,7 @@ rewriteTargetListUD(Query *parsetree, RangeTblEntry *target_rte,
TargetEntry *tle;
if (target_relation->rd_rel->relkind == RELKIND_RELATION ||
+ target_relation->rd_rel->relkind == RELKIND_PARTITIONED_REL ||
target_relation->rd_rel->relkind == RELKIND_MATVIEW)
{
/*
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index ac50c2a..220cbfc 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -975,10 +975,13 @@ ProcessUtilitySlow(Node *parsetree,
{
Datum toast_options;
static char *validnsps[] = HEAP_RELOPT_NAMESPACES;
+ char relkind = ((CreateStmt *) stmt)->partby != NULL
+ ? RELKIND_PARTITIONED_REL
+ : RELKIND_RELATION;
/* Create the table itself */
address = DefineRelation((CreateStmt *) stmt,
- RELKIND_RELATION,
+ relkind,
InvalidOid, NULL);
EventTriggerCollectSimpleCommand(address,
secondaryObject,
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 2b47e95..49f6fad 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -33,6 +33,7 @@
#include "catalog/pg_language.h"
#include "catalog/pg_opclass.h"
#include "catalog/pg_operator.h"
+#include "catalog/pg_partitioned.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_trigger.h"
#include "catalog/pg_type.h"
@@ -315,6 +316,7 @@ static char *pg_get_indexdef_worker(Oid indexrelid, int colno,
const Oid *excludeOps,
bool attrsOnly, bool showTblSpc,
int prettyFlags);
+static char *pg_get_partkeydef_worker(Oid relId, int prettyFlags);
static char *pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
int prettyFlags);
static text *pg_get_expr_worker(text *expr, Oid relid, const char *relname,
@@ -1289,6 +1291,151 @@ pg_get_indexdef_worker(Oid indexrelid, int colno,
return buf.data;
}
+/*
+ * pg_get_partkeydef
+ *
+ * Returns the partition key specification, ie, the following:
+ *
+ * PARTITION BY { RANGE | LIST } (column [USING opclass_name] [, ...])
+ */
+Datum
+pg_get_partkeydef(PG_FUNCTION_ARGS)
+{
+ Oid relationId = PG_GETARG_OID(0);
+ int prettyFlags;
+
+ prettyFlags = PRETTYFLAG_INDENT;
+ PG_RETURN_TEXT_P(string_to_text(pg_get_partkeydef_worker(relationId,
+ prettyFlags)));
+}
+
+/*
+ * Internal workhorse to decompile a partition key definition.
+ */
+static char *
+pg_get_partkeydef_worker(Oid relId, int prettyFlags)
+{
+ Form_pg_partitioned form;
+ HeapTuple tuple;
+ oidvector *partclass;
+ List *partexprs;
+ ListCell *partexpr_item;
+ List *context;
+ Datum datum;
+ bool isnull;
+ StringInfoData buf;
+ int keyno;
+ char *str;
+ char *sep;
+
+ tuple = SearchSysCache1(PARTEDRELID, ObjectIdGetDatum(relId));
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "cache lookup failed for partition key of %u", relId);
+
+ form = (Form_pg_partitioned) GETSTRUCT(tuple);
+
+ Assert(form->partedrelid == relId);
+
+ /* Must get partclass, and partexprs the hard way */
+ datum = SysCacheGetAttr(PARTEDRELID, tuple,
+ Anum_pg_partitioned_partclass, &isnull);
+ Assert(!isnull);
+ partclass = (oidvector *) DatumGetPointer(datum);
+
+ /*
+ * Get the partition key expressions, if any. (NOTE: we do not use the
+ * relcache versions of the expressions, because we want to display
+ * non-const-folded expressions.)
+ */
+ if (!heap_attisnull(tuple, Anum_pg_partitioned_partexprs))
+ {
+ Datum exprsDatum;
+ bool isnull;
+ char *exprsString;
+
+ exprsDatum = SysCacheGetAttr(PARTEDRELID, tuple,
+ Anum_pg_partitioned_partexprs, &isnull);
+ Assert(!isnull);
+ exprsString = TextDatumGetCString(exprsDatum);
+ partexprs = (List *) stringToNode(exprsString);
+ pfree(exprsString);
+ }
+ else
+ partexprs = NIL;
+
+ partexpr_item = list_head(partexprs);
+ context = deparse_context_for(get_relation_name(relId), relId);
+
+ /*
+ * Start the partition key definition.
+ */
+ initStringInfo(&buf);
+
+ appendStringInfo(&buf, "PARTITION BY ");
+
+ switch (form->partstrat)
+ {
+ case 'l':
+ appendStringInfo(&buf, "LIST");
+ break;
+ case 'r':
+ appendStringInfo(&buf, "RANGE");
+ break;
+ }
+
+ /*
+ * Report the partition key columns
+ */
+ appendStringInfo(&buf, " (");
+ sep = "";
+ for (keyno = 0; keyno < form->partnatts; keyno++)
+ {
+ AttrNumber attnum = form->partattrs.values[keyno];
+ Oid keycoltype;
+
+ appendStringInfoString(&buf, sep);
+ sep = ", ";
+ if (attnum != 0)
+ {
+ /* Simple partition key column */
+ char *attname;
+
+ attname = get_relid_attribute_name(relId, attnum);
+ appendStringInfoString(&buf, quote_identifier(attname));
+ keycoltype = get_atttype(relId, attnum);
+ }
+ else
+ {
+ /* partition key expression */
+ Node *partkey;
+
+ if (partexpr_item == NULL)
+ elog(ERROR, "too few entries in partexprs list");
+ partkey = (Node *) lfirst(partexpr_item);
+ partexpr_item = lnext(partexpr_item);
+ /* Deparse */
+ str = deparse_expression_pretty(partkey, context, false, false,
+ prettyFlags, 0);
+
+ /* Need parens if it's not a bare function call */
+ if (partkey && IsA(partkey, FuncExpr) &&
+ ((FuncExpr *) partkey)->funcformat == COERCE_EXPLICIT_CALL)
+ appendStringInfoString(&buf, str);
+ else
+ appendStringInfo(&buf, "(%s)", str);
+ keycoltype = exprType(partkey);
+ }
+
+ /* Add the operator class name, if not default */
+ get_opclass_name(partclass->values[keyno], keycoltype, &buf);
+ }
+ appendStringInfoChar(&buf, ')');
+
+ /* Clean up */
+ ReleaseSysCache(tuple);
+
+ return buf.data;
+}
/*
* pg_get_constraintdef
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 432feef..2841745 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -40,6 +40,7 @@
#include "catalog/index.h"
#include "catalog/indexing.h"
#include "catalog/namespace.h"
+#include "catalog/partition.h"
#include "catalog/pg_am.h"
#include "catalog/pg_amproc.h"
#include "catalog/pg_attrdef.h"
@@ -431,6 +432,7 @@ RelationParseRelOptions(Relation relation, HeapTuple tuple)
switch (relation->rd_rel->relkind)
{
case RELKIND_RELATION:
+ case RELKIND_PARTITIONED_REL:
case RELKIND_TOASTVALUE:
case RELKIND_INDEX:
case RELKIND_VIEW:
@@ -1049,6 +1051,12 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
else
relation->rd_rsdesc = NULL;
+ /* if it's a partitioned table, initialize key info */
+ if (relation->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+ RelationBuildPartitionKey(relation);
+ else
+ relation->rd_partkey = NULL;
+
/*
* if it's an index, initialize index-related information
*/
@@ -2046,6 +2054,7 @@ RelationDestroyRelation(Relation relation, bool remember_tupdesc)
MemoryContextDelete(relation->rd_rulescxt);
if (relation->rd_rsdesc)
MemoryContextDelete(relation->rd_rsdesc->rscxt);
+ FreePartitionKey(relation->rd_partkey);
if (relation->rd_fdwroutine)
pfree(relation->rd_fdwroutine);
pfree(relation);
@@ -2987,7 +2996,9 @@ RelationBuildLocalRelation(const char *relname,
/* system relations and non-table objects don't have one */
if (!IsSystemNamespace(relnamespace) &&
- (relkind == RELKIND_RELATION || relkind == RELKIND_MATVIEW))
+ (relkind == RELKIND_RELATION ||
+ relkind == RELKIND_PARTITIONED_REL ||
+ relkind == RELKIND_MATVIEW))
rel->rd_rel->relreplident = REPLICA_IDENTITY_DEFAULT;
else
rel->rd_rel->relreplident = REPLICA_IDENTITY_NOTHING;
@@ -4972,6 +4983,7 @@ load_relcache_init_file(bool shared)
rel->rd_rulescxt = NULL;
rel->trigdesc = NULL;
rel->rd_rsdesc = NULL;
+ rel->rd_partkey = NULL;
rel->rd_indexprs = NIL;
rel->rd_indpred = NIL;
rel->rd_exclops = NULL;
diff --git a/src/backend/utils/cache/syscache.c b/src/backend/utils/cache/syscache.c
index 65ffe84..a6563fe 100644
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -48,6 +48,7 @@
#include "catalog/pg_opclass.h"
#include "catalog/pg_operator.h"
#include "catalog/pg_opfamily.h"
+#include "catalog/pg_partitioned.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_range.h"
#include "catalog/pg_rewrite.h"
@@ -568,6 +569,17 @@ static const struct cachedesc cacheinfo[] = {
},
8
},
+ {PartitionedRelationId, /* PARTEDRELID */
+ PartitionedRelidIndexId,
+ 1,
+ {
+ Anum_pg_partitioned_partedrelid,
+ 0,
+ 0,
+ 0
+ },
+ 128
+ },
{ProcedureRelationId, /* PROCNAMEARGSNSP */
ProcedureNameArgsNspIndexId,
3,
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index e1e5bee..4c95aca 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -1234,9 +1234,10 @@ expand_table_name_patterns(Archive *fout,
"SELECT c.oid"
"\nFROM pg_catalog.pg_class c"
"\n LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace"
- "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c')\n",
+ "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c')\n",
RELKIND_RELATION, RELKIND_SEQUENCE, RELKIND_VIEW,
- RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+ RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+ RELKIND_PARTITIONED_REL);
processSQLNamePattern(GetConnection(fout), query, cell->val, true,
false, "n.nspname", "c.relname", NULL,
"pg_catalog.pg_table_is_visible(c.oid)");
@@ -2064,6 +2065,9 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo, bool oids)
/* Skip FOREIGN TABLEs (no data to dump) */
if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
return;
+ /* Skip partitioned tables (data in partitions) */
+ if (tbinfo->relkind == RELKIND_PARTITIONED_REL)
+ return;
/* Don't dump data in unlogged tables, if so requested */
if (tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED &&
@@ -5131,6 +5135,7 @@ getTables(Archive *fout, int *numTables)
int i_toastreloptions;
int i_reloftype;
int i_relpages;
+ int i_partkeydef;
/* Make sure we are in proper schema */
selectSourceSchema(fout, "pg_catalog");
@@ -5196,7 +5201,8 @@ getTables(Archive *fout, int *numTables)
"array_remove(array_remove(c.reloptions,'check_option=local'),'check_option=cascaded') AS reloptions, "
"CASE WHEN 'check_option=local' = ANY (c.reloptions) THEN 'LOCAL'::text "
"WHEN 'check_option=cascaded' = ANY (c.reloptions) THEN 'CASCADED'::text ELSE NULL END AS checkoption, "
- "tc.reloptions AS toast_reloptions "
+ "tc.reloptions AS toast_reloptions, "
+ "CASE WHEN c.relkind = 'P' THEN pg_catalog.pg_get_partkeydef(c.oid) ELSE NULL END AS partkeydef "
"FROM pg_class c "
"LEFT JOIN pg_depend d ON "
"(c.relkind = '%c' AND "
@@ -5208,7 +5214,7 @@ getTables(Archive *fout, int *numTables)
"(c.oid = pip.objoid AND pip.classoid = "
"(SELECT oid FROM pg_class "
"WHERE relname = 'pg_class') AND pip.objsubid = 0) "
- "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c') "
+ "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c', '%c') "
"ORDER BY c.oid",
acl_subquery->data,
racl_subquery->data,
@@ -5218,7 +5224,8 @@ getTables(Archive *fout, int *numTables)
RELKIND_SEQUENCE,
RELKIND_RELATION, RELKIND_SEQUENCE,
RELKIND_VIEW, RELKIND_COMPOSITE_TYPE,
- RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+ RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+ RELKIND_PARTITIONED_REL);
destroyPQExpBuffer(acl_subquery);
destroyPQExpBuffer(racl_subquery);
@@ -5762,6 +5769,7 @@ getTables(Archive *fout, int *numTables)
i_checkoption = PQfnumber(res, "checkoption");
i_toastreloptions = PQfnumber(res, "toast_reloptions");
i_reloftype = PQfnumber(res, "reloftype");
+ i_partkeydef = PQfnumber(res, "partkeydef");
if (dopt->lockWaitTimeout && fout->remoteVersion >= 70300)
{
@@ -5832,6 +5840,7 @@ getTables(Archive *fout, int *numTables)
else
tblinfo[i].checkoption = pg_strdup(PQgetvalue(res, i, i_checkoption));
tblinfo[i].toast_reloptions = pg_strdup(PQgetvalue(res, i, i_toastreloptions));
+ tblinfo[i].partkeydef = pg_strdup(PQgetvalue(res, i, i_partkeydef));
/* other fields were zeroed above */
@@ -5858,7 +5867,9 @@ getTables(Archive *fout, int *numTables)
* NOTE: it'd be kinda nice to lock other relations too, not only
* plain tables, but the backend doesn't presently allow that.
*/
- if (tblinfo[i].dobj.dump && tblinfo[i].relkind == RELKIND_RELATION)
+ if (tblinfo[i].dobj.dump &&
+ (tblinfo[i].relkind == RELKIND_RELATION ||
+ tblinfo->relkind == RELKIND_PARTITIONED_REL))
{
resetPQExpBuffer(query);
appendPQExpBuffer(query,
@@ -5945,7 +5956,10 @@ getInherits(Archive *fout, int *numInherits)
/* find all the inheritance information */
- appendPQExpBufferStr(query, "SELECT inhrelid, inhparent FROM pg_inherits");
+ appendPQExpBufferStr(query,
+ "SELECT inhrelid, inhparent "
+ "FROM pg_inherits "
+ "WHERE inhparent NOT IN (SELECT oid FROM pg_class WHERE relkind = 'P')");
res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
@@ -15268,6 +15282,9 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
appendPQExpBufferChar(q, ')');
}
+ if (tbinfo->relkind == RELKIND_PARTITIONED_REL)
+ appendPQExpBuffer(q, "\n%s", tbinfo->partkeydef);
+
if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
appendPQExpBuffer(q, "\nSERVER %s", fmtId(srvname));
}
@@ -15327,6 +15344,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
*/
if (dopt->binary_upgrade &&
(tbinfo->relkind == RELKIND_RELATION ||
+ tbinfo->relkind == RELKIND_PARTITIONED_REL ||
tbinfo->relkind == RELKIND_FOREIGN_TABLE))
{
for (j = 0; j < tbinfo->numatts; j++)
@@ -15345,7 +15363,8 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
appendStringLiteralAH(q, fmtId(tbinfo->dobj.name), fout);
appendPQExpBufferStr(q, "::pg_catalog.regclass;\n");
- if (tbinfo->relkind == RELKIND_RELATION)
+ if (tbinfo->relkind == RELKIND_RELATION ||
+ tbinfo->relkind == RELKIND_PARTITIONED_REL)
appendPQExpBuffer(q, "ALTER TABLE ONLY %s ",
fmtId(tbinfo->dobj.name));
else
@@ -15562,6 +15581,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
* dump properties we only have ALTER TABLE syntax for
*/
if ((tbinfo->relkind == RELKIND_RELATION ||
+ tbinfo->relkind == RELKIND_PARTITIONED_REL ||
tbinfo->relkind == RELKIND_MATVIEW) &&
tbinfo->relreplident != REPLICA_IDENTITY_DEFAULT)
{
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 7314cbe..3094425 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -283,6 +283,7 @@ typedef struct _tableInfo
bool *inhNotNull; /* true if NOT NULL is inherited */
struct _attrDefInfo **attrdefs; /* DEFAULT expressions */
struct _constraintInfo *checkexprs; /* CHECK constraints */
+ char *partkeydef; /* partition key definition */
/*
* Stuff computed only for dumpable tables.
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 0a771bd..78a1fdb 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -764,6 +764,7 @@ permissionsList(const char *pattern)
" WHEN 'm' THEN '%s'"
" WHEN 'S' THEN '%s'"
" WHEN 'f' THEN '%s'"
+ " WHEN 'P' THEN '%s'"
" END as \"%s\",\n"
" ",
gettext_noop("Schema"),
@@ -773,6 +774,7 @@ permissionsList(const char *pattern)
gettext_noop("materialized view"),
gettext_noop("sequence"),
gettext_noop("foreign table"),
+ gettext_noop("partitioned table"),
gettext_noop("Type"));
printACLColumn(&buf, "c.relacl");
@@ -819,7 +821,7 @@ permissionsList(const char *pattern)
appendPQExpBufferStr(&buf, "\nFROM pg_catalog.pg_class c\n"
" LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace\n"
- "WHERE c.relkind IN ('r', 'v', 'm', 'S', 'f')\n");
+ "WHERE c.relkind IN ('r', 'v', 'm', 'S', 'f', 'P')\n");
/*
* Unless a schema pattern is specified, we suppress system and temp
@@ -1463,8 +1465,8 @@ describeOneTableDetails(const char *schemaname,
* types, and foreign tables (c.f. CommentObject() in comment.c).
*/
if (tableinfo.relkind == 'r' || tableinfo.relkind == 'v' ||
- tableinfo.relkind == 'm' ||
- tableinfo.relkind == 'f' || tableinfo.relkind == 'c')
+ tableinfo.relkind == 'm' || tableinfo.relkind == 'f' ||
+ tableinfo.relkind == 'c' || tableinfo.relkind == 'P')
appendPQExpBufferStr(&buf, ", pg_catalog.col_description(a.attrelid, a.attnum)");
}
@@ -1529,6 +1531,14 @@ describeOneTableDetails(const char *schemaname,
printfPQExpBuffer(&title, _("Foreign table \"%s.%s\""),
schemaname, relationname);
break;
+ case 'P':
+ if (tableinfo.relpersistence == 'u')
+ printfPQExpBuffer(&title, _("Unlogged partitioned table \"%s.%s\""),
+ schemaname, relationname);
+ else
+ printfPQExpBuffer(&title, _("Partitioned table \"%s.%s\""),
+ schemaname, relationname);
+ break;
default:
/* untranslated unknown relkind */
printfPQExpBuffer(&title, "?%c? \"%s.%s\"",
@@ -1542,8 +1552,8 @@ describeOneTableDetails(const char *schemaname,
cols = 2;
if (tableinfo.relkind == 'r' || tableinfo.relkind == 'v' ||
- tableinfo.relkind == 'm' ||
- tableinfo.relkind == 'f' || tableinfo.relkind == 'c')
+ tableinfo.relkind == 'm' || tableinfo.relkind == 'f' ||
+ tableinfo.relkind == 'c' || tableinfo.relkind == 'P')
{
show_modifiers = true;
headers[cols++] = gettext_noop("Modifiers");
@@ -1563,12 +1573,12 @@ describeOneTableDetails(const char *schemaname,
{
headers[cols++] = gettext_noop("Storage");
if (tableinfo.relkind == 'r' || tableinfo.relkind == 'm' ||
- tableinfo.relkind == 'f')
+ tableinfo.relkind == 'f' || tableinfo.relkind == 'P')
headers[cols++] = gettext_noop("Stats target");
/* Column comments, if the relkind supports this feature. */
if (tableinfo.relkind == 'r' || tableinfo.relkind == 'v' ||
- tableinfo.relkind == 'm' ||
- tableinfo.relkind == 'c' || tableinfo.relkind == 'f')
+ tableinfo.relkind == 'm' || tableinfo.relkind == 'c' ||
+ tableinfo.relkind == 'f' || tableinfo.relkind == 'P')
headers[cols++] = gettext_noop("Description");
}
@@ -1668,7 +1678,7 @@ describeOneTableDetails(const char *schemaname,
/* Statistics target, if the relkind supports this feature */
if (tableinfo.relkind == 'r' || tableinfo.relkind == 'm' ||
- tableinfo.relkind == 'f')
+ tableinfo.relkind == 'f' || tableinfo.relkind == 'P')
{
printTableAddCell(&cont, PQgetvalue(res, i, firstvcol + 1),
false, false);
@@ -1676,14 +1686,33 @@ describeOneTableDetails(const char *schemaname,
/* Column comments, if the relkind supports this feature. */
if (tableinfo.relkind == 'r' || tableinfo.relkind == 'v' ||
- tableinfo.relkind == 'm' ||
- tableinfo.relkind == 'c' || tableinfo.relkind == 'f')
+ tableinfo.relkind == 'm' || tableinfo.relkind == 'c' ||
+ tableinfo.relkind == 'f' || tableinfo.relkind == 'P')
printTableAddCell(&cont, PQgetvalue(res, i, firstvcol + 2),
false, false);
}
}
/* Make footers */
+ if (pset.sversion >= 90600 && tableinfo.relkind == 'P')
+ {
+ /* Get the partition key information */
+ PGresult *result;
+ char *partkeydef;
+
+ printfPQExpBuffer(&buf,
+ "SELECT pg_catalog.pg_get_partkeydef('%s'::pg_catalog.oid);",
+ oid);
+ result = PSQLexec(buf.data);
+ if (!result || PQntuples(result) != 1)
+ goto error_return;
+
+ partkeydef = PQgetvalue(result, 0, 0);
+ printfPQExpBuffer(&tmpbuf, _("Partition Key: %s"), partkeydef);
+ printTableAddFooter(&cont, tmpbuf.data);
+ PQclear(result);
+ }
+
if (tableinfo.relkind == 'i')
{
/* Footer information about an index */
@@ -1822,7 +1851,7 @@ describeOneTableDetails(const char *schemaname,
PQclear(result);
}
else if (tableinfo.relkind == 'r' || tableinfo.relkind == 'm' ||
- tableinfo.relkind == 'f')
+ tableinfo.relkind == 'f' || tableinfo.relkind == 'P')
{
/* Footer information about a table */
PGresult *result = NULL;
@@ -2381,7 +2410,7 @@ describeOneTableDetails(const char *schemaname,
* Finish printing the footer information about a table.
*/
if (tableinfo.relkind == 'r' || tableinfo.relkind == 'm' ||
- tableinfo.relkind == 'f')
+ tableinfo.relkind == 'f' || tableinfo.relkind == 'P')
{
PGresult *result;
int tuples;
@@ -2592,7 +2621,7 @@ add_tablespace_footer(printTableContent *const cont, char relkind,
Oid tablespace, const bool newline)
{
/* relkinds for which we support tablespaces */
- if (relkind == 'r' || relkind == 'm' || relkind == 'i')
+ if (relkind == 'r' || relkind == 'm' || relkind == 'i' || relkind == 'P')
{
/*
* We ignore the database default tablespace so that users not using
@@ -2926,6 +2955,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
" WHEN 'S' THEN '%s'"
" WHEN 's' THEN '%s'"
" WHEN 'f' THEN '%s'"
+ " WHEN 'P' THEN '%s'"
" END as \"%s\",\n"
" pg_catalog.pg_get_userbyid(c.relowner) as \"%s\"",
gettext_noop("Schema"),
@@ -2937,6 +2967,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
gettext_noop("sequence"),
gettext_noop("special"),
gettext_noop("foreign table"),
+ gettext_noop("partitioned table"),
gettext_noop("Type"),
gettext_noop("Owner"));
@@ -2975,7 +3006,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
appendPQExpBufferStr(&buf, "\nWHERE c.relkind IN (");
if (showTables)
- appendPQExpBufferStr(&buf, "'r',");
+ appendPQExpBufferStr(&buf, "'r', 'P',");
if (showViews)
appendPQExpBufferStr(&buf, "'v',");
if (showMatViews)
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index a62ffe6..741ac46 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -427,7 +427,7 @@ static const SchemaQuery Query_for_list_of_tables = {
/* catname */
"pg_catalog.pg_class c",
/* selcondition */
- "c.relkind IN ('r')",
+ "c.relkind IN ('r', 'P')",
/* viscondition */
"pg_catalog.pg_table_is_visible(c.oid)",
/* namespace */
@@ -458,7 +458,7 @@ static const SchemaQuery Query_for_list_of_updatables = {
/* catname */
"pg_catalog.pg_class c",
/* selcondition */
- "c.relkind IN ('r', 'f', 'v')",
+ "c.relkind IN ('r', 'f', 'v', 'P')",
/* viscondition */
"pg_catalog.pg_table_is_visible(c.oid)",
/* namespace */
@@ -488,7 +488,7 @@ static const SchemaQuery Query_for_list_of_tsvmf = {
/* catname */
"pg_catalog.pg_class c",
/* selcondition */
- "c.relkind IN ('r', 'S', 'v', 'm', 'f')",
+ "c.relkind IN ('r', 'S', 'v', 'm', 'f', 'P')",
/* viscondition */
"pg_catalog.pg_table_is_visible(c.oid)",
/* namespace */
diff --git a/src/include/catalog/indexing.h b/src/include/catalog/indexing.h
index ca5eb3d..cadb741 100644
--- a/src/include/catalog/indexing.h
+++ b/src/include/catalog/indexing.h
@@ -319,6 +319,9 @@ DECLARE_UNIQUE_INDEX(pg_replication_origin_roiident_index, 6001, on pg_replicati
DECLARE_UNIQUE_INDEX(pg_replication_origin_roname_index, 6002, on pg_replication_origin using btree(roname text_pattern_ops));
#define ReplicationOriginNameIndex 6002
+DECLARE_UNIQUE_INDEX(pg_partitioned_partedrelid_index, 3351, on pg_partitioned using btree(partedrelid oid_ops));
+#define PartitionedRelidIndexId 3351
+
/* last step of initialization script: build the indexes declared above */
BUILD_INDICES
diff --git a/src/include/catalog/partition.h b/src/include/catalog/partition.h
new file mode 100644
index 0000000..2c876c4
--- /dev/null
+++ b/src/include/catalog/partition.h
@@ -0,0 +1,55 @@
+/*-------------------------------------------------------------------------
+ *
+ * partition.h
+ * Header file for structures and utility functions related to
+ * partitioning
+ *
+ * Copyright (c) 2007-2016, PostgreSQL Global Development Group
+ *
+ * src/include/utils/partition.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PARTITION_H
+#define PARTITION_H
+
+#include "fmgr.h"
+#include "utils/relcache.h"
+
+/* Type and collation information for partition key columns */
+typedef struct
+{
+ Oid *typid;
+ int32 *typmod;
+ int16 *typlen;
+ bool *typbyval;
+ char *typalign;
+ Oid *typcoll;
+} KeyTypeCollInfo;
+
+/* Partition key information for one level */
+typedef struct PartitionKeyData
+{
+ char strategy; /* partition strategy */
+ int16 partnatts; /* number of partition attributes */
+ AttrNumber *partattrs; /* partition attnums */
+ Oid *partopfamily; /* OIDs of opfamily per col */
+ FmgrInfo *partsupfunc; /* lookup info for support funcs */
+ List *partexprs; /* partition key expressions, if any */
+ KeyTypeCollInfo *tcinfo; /* type and collation info (all columns) */
+} PartitionKeyData;
+
+typedef struct PartitionKeyData *PartitionKey;
+
+extern void StorePartitionKey(Relation rel,
+ char strategy,
+ int16 partnatts,
+ AttrNumber *partattrs,
+ List *partexprs,
+ Oid *partopclass);
+extern void RemovePartitionKeyByRelId(Oid relid);
+extern void RelationBuildPartitionKey(Relation relation);
+extern void FreePartitionKey(PartitionKey key);
+extern bool relid_is_partitioned(Oid relid);
+
+#endif /* PARTITION_H */
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index fdc67c2..149f619 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -153,6 +153,7 @@ DESCR("");
#define RELKIND_RELATION 'r' /* ordinary table */
+#define RELKIND_PARTITIONED_REL 'P' /* partitioned table */
#define RELKIND_INDEX 'i' /* secondary index */
#define RELKIND_SEQUENCE 'S' /* sequence object */
#define RELKIND_TOASTVALUE 't' /* for out-of-line values */
diff --git a/src/include/catalog/pg_partitioned.h b/src/include/catalog/pg_partitioned.h
new file mode 100644
index 0000000..edc52db
--- /dev/null
+++ b/src/include/catalog/pg_partitioned.h
@@ -0,0 +1,67 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_partitioned.h
+ * definition of the system "partitioned" relation (pg_partitioned)
+ * along with the relation's initial contents.
+ *
+ *
+ * Portions Copyright (c) 1996-2015, PostgreSQL Global Development Group
+ *
+ * $PostgreSQL: pgsql/src/include/catalog/pg_partitioned.h $
+ *
+ * NOTES
+ * the genbki.sh script reads this file and generates .bki
+ * information from the DATA() statements.
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_PARTITIONED_REL_H
+#define PG_PARTITIONED_REL_H
+
+#include "catalog/genbki.h"
+
+/* ----------------
+ * pg_partitioned definition. cpp turns this into
+ * typedef struct FormData_pg_partitioned
+ * ----------------
+ */
+#define PartitionedRelationId 3350
+
+CATALOG(pg_partitioned,3350) BKI_WITHOUT_OIDS
+{
+ Oid partedrelid; /* partitioned table oid */
+ char partstrat; /* partition key strategy */
+ int16 partnatts; /* number of partition key columns */
+
+ /* variable-length fields start here, but we allow direct access to partattrs */
+ int2vector partattrs; /* attribute numbers of partition key
+ * columns */
+
+#ifdef CATALOG_VARLEN
+ oidvector partclass; /* operator class to compare keys */
+ pg_node_tree partexprs; /* expression trees for partition key members
+ * that are not simple column references; one
+ * for each zero entry in partkey[] */
+#endif
+} FormData_pg_partitioned;
+
+/* ----------------
+ * Form_pg_partitioned corresponds to a pointer to a tuple with
+ * the format of pg_partitioned relation.
+ * ----------------
+ */
+typedef FormData_pg_partitioned *Form_pg_partitioned;
+
+/* ----------------
+ * compiler constants for pg_partitioned
+ * ----------------
+ */
+#define Natts_pg_partitioned 6
+#define Anum_pg_partitioned_partedrelid 1
+#define Anum_pg_partitioned_partstrat 2
+#define Anum_pg_partitioned_partnatts 3
+#define Anum_pg_partitioned_partattrs 4
+#define Anum_pg_partitioned_partclass 5
+#define Anum_pg_partitioned_partexprs 6
+
+#endif /* PG_PARTITIONED_REL_H */
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index bb539d4..92a7260 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -1969,6 +1969,8 @@ DATA(insert OID = 1642 ( pg_get_userbyid PGNSP PGUID 12 1 0 0 0 f f f f t f
DESCR("role name by OID (with fallback)");
DATA(insert OID = 1643 ( pg_get_indexdef PGNSP PGUID 12 1 0 0 0 f f f f t f s s 1 0 25 "26" _null_ _null_ _null_ _null_ _null_ pg_get_indexdef _null_ _null_ _null_ ));
DESCR("index description");
+DATA(insert OID = 3352 ( pg_get_partkeydef PGNSP PGUID 12 1 0 0 0 f f f f t f s s 1 0 25 "26" _null_ _null_ _null_ _null_ _null_ pg_get_partkeydef _null_ _null_ _null_ ));
+DESCR("partition key description");
DATA(insert OID = 1662 ( pg_get_triggerdef PGNSP PGUID 12 1 0 0 0 f f f f t f s s 1 0 25 "26" _null_ _null_ _null_ _null_ _null_ pg_get_triggerdef _null_ _null_ _null_ ));
DESCR("trigger description");
DATA(insert OID = 1387 ( pg_get_constraintdef PGNSP PGUID 12 1 0 0 0 f f f f t f s s 1 0 25 "26" _null_ _null_ _null_ _null_ _null_ pg_get_constraintdef _null_ _null_ _null_ ));
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index 28024f8..ceeb41f 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -42,8 +42,8 @@ extern bool CheckIndexCompatible(Oid oldId,
List *attributeList,
List *exclusionOpNames);
extern Oid GetDefaultOpClass(Oid type_id, Oid am_id);
-extern Oid GetIndexOpClass(List *opclass, Oid attrType,
- char *accessMethodName, Oid accessMethodId);
+extern Oid GetIndexOpClass(List *opclass, Oid attrType,
+ char *accessMethodName, Oid accessMethodId);
/* commands/functioncmds.c */
extern ObjectAddress CreateFunction(CreateFunctionStmt *stmt, const char *queryString);
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
index 01976a1..027665c 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -720,6 +720,7 @@ extern Datum pg_get_viewdef_wrap(PG_FUNCTION_ARGS);
extern Datum pg_get_viewdef_name(PG_FUNCTION_ARGS);
extern Datum pg_get_viewdef_name_ext(PG_FUNCTION_ARGS);
extern Datum pg_get_indexdef(PG_FUNCTION_ARGS);
+extern Datum pg_get_partkeydef(PG_FUNCTION_ARGS);
extern Datum pg_get_indexdef_ext(PG_FUNCTION_ARGS);
extern Datum pg_get_triggerdef(PG_FUNCTION_ARGS);
extern Datum pg_get_triggerdef_ext(PG_FUNCTION_ARGS);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index b5d82d6..cf91ef8 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -90,6 +90,8 @@ typedef struct RelationData
/* use "struct" here to avoid needing to include rowsecurity.h: */
struct RowSecurityDesc *rd_rsdesc; /* row security policies, or NULL */
+ struct PartitionKeyData *rd_partkey; /* partition key, or NULL */
+
/* data managed by RelationGetIndexList: */
List *rd_indexlist; /* list of OIDs of indexes on relation */
Oid rd_oidindex; /* OID of unique index on OID, if any */
diff --git a/src/include/utils/syscache.h b/src/include/utils/syscache.h
index 256615b..e727842 100644
--- a/src/include/utils/syscache.h
+++ b/src/include/utils/syscache.h
@@ -72,6 +72,7 @@ enum SysCacheIdentifier
OPEROID,
OPFAMILYAMNAMENSP,
OPFAMILYOID,
+ PARTEDRELID,
PROCNAMEARGSNSP,
PROCOID,
RANGETYPE,
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index aaae9f9..a4cc948 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -326,9 +326,35 @@ ERROR: cannot use a constant expression as partition key
CREATE TABLE fail_agg_in_key (
a int
) PARTITION BY RANGE ((avg(a)));
-ERROR: aggregate functions are not allowed in partition key expressions
+ERROR: aggregate functions are not allowed in partition key expression
CREATE TABLE fail_window_fun_in_key (
a int,
b int
) PARTITION BY RANGE ((avg(a) OVER (PARTITION BY b)));
-ERROR: window functions are not allowed in partition key expressions
+ERROR: window functions are not allowed in partition key expression
+-- check relkind
+CREATE TABLE check_relkind (
+ a int
+) PARTITION BY RANGE (a);
+SELECT relkind FROM pg_class WHERE relname = 'check_relkind';
+ relkind
+---------
+ P
+(1 row)
+
+-- cannot drop column that is part of the partition key
+CREATE TABLE no_drop_alter_partcol (
+ a int
+) PARTITION BY RANGE (a);
+ALTER TABLE no_drop_alter_partcol DROP COLUMN a;
+ERROR: cannot drop column named in partition key
+ALTER TABLE no_drop_alter_partcol ALTER COLUMN a TYPE char(5);
+ERROR: cannot alter column named in partition key
+CREATE TABLE no_drop_alter_partexpr (
+ a text
+) PARTITION BY RANGE ((substring(a from 1 for 1)));
+ALTER TABLE no_drop_alter_partexpr DROP COLUMN a;
+ERROR: cannot drop column referenced in partition key expression
+ALTER TABLE no_drop_alter_partcol ALTER COLUMN a TYPE char(5);
+ERROR: cannot alter column named in partition key
+DROP TABLE check_relkind, no_drop_alter_partcol, no_drop_alter_partexpr;
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index 1c087a3..b1e473d 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -118,6 +118,7 @@ pg_namespace|t
pg_opclass|t
pg_operator|t
pg_opfamily|t
+pg_partitioned|t
pg_pltemplate|t
pg_policy|t
pg_proc|t
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index df47656..022f38b 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -337,3 +337,24 @@ CREATE TABLE fail_window_fun_in_key (
a int,
b int
) PARTITION BY RANGE ((avg(a) OVER (PARTITION BY b)));
+
+-- check relkind
+CREATE TABLE check_relkind (
+ a int
+) PARTITION BY RANGE (a);
+SELECT relkind FROM pg_class WHERE relname = 'check_relkind';
+
+-- cannot drop column that is part of the partition key
+CREATE TABLE no_drop_alter_partcol (
+ a int
+) PARTITION BY RANGE (a);
+ALTER TABLE no_drop_alter_partcol DROP COLUMN a;
+ALTER TABLE no_drop_alter_partcol ALTER COLUMN a TYPE char(5);
+
+CREATE TABLE no_drop_alter_partexpr (
+ a text
+) PARTITION BY RANGE ((substring(a from 1 for 1)));
+ALTER TABLE no_drop_alter_partexpr DROP COLUMN a;
+ALTER TABLE no_drop_alter_partcol ALTER COLUMN a TYPE char(5);
+
+DROP TABLE check_relkind, no_drop_alter_partcol, no_drop_alter_partexpr;
--
1.7.1
[text/x-diff] 0003-Add-syntax-to-create-partitions-v3.patch (78.0K, ../../57183572.8020900@lab.ntt.co.jp/4-0003-Add-syntax-to-create-partitions-v3.patch)
download | inline diff:
From c43a7eb5ec4de93e2623b4bc718ebbb894c43c77 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Thu, 3 Mar 2016 18:29:15 +0900
Subject: [PATCH 3/5] Add syntax to create partitions.
As follows:
CREATE TABLE name PARTITION OF parent
[ ( { column_name WITH OPTION column_constraint | table_constraint } [, ...] ) ]
partition_bound_spec [ PARTITION BY partition_key_spec ]
ALTER TABLE parent ATTACH PARTITION name
partition_bound_spec [ VALIDATE | NO VALIDATE ]
ALTER TABLE parent DETACH PARTITION name
partition_bound_spec refers to either list partition bound of form:
FOR VALUES IN (list-of-expressions)
or range partition bound of form:
FOR VALUES range_spec
where range_spec is of one of:
START (lower-bound) [ INCLUSIVE | EXCLUSIVE ]
END (upper-bound) [ INCLUSIVE | EXCLUSIVE ]
START (lower-bound) [ INCLUSIVE | EXCLUSIVE ] END (upper-bound) [ INCLUSIVE | EXCLUSIVE ]
In the above, each of lower-bound and upper-bound is either a single
expression for a single column key or list of expressions for a multi-
column key or empty. Note that in the absence of explicit specification,
lower-bound is inclusive and upper-bound is exclusive.
A partition is created as inheritance child of the parent with certain
differences from regular inheritance behavior. For example, when a new
partition is created, it cannot contain its own column definitions or
constraints (unlike CREATE TABLE child(...) INHERITS(parent)). When
"attaching" a table as partition of parent, it cannot contain columns
that are not contained in the parent (unlike ALTER TABLE child INHERIT
parent). No dependency is created between a partition and the parent.
One cannot create regular inheritance children of a partitioned table.
Whereas this commit enables inheritance, it does not yet store partition
bound info anywhere, IOW, the fact that a given inheritance child is a
"partition" is not persisted.
---
doc/src/sgml/ref/alter_table.sgml | 57 +++++
doc/src/sgml/ref/create_table.sgml | 79 ++++++-
src/backend/commands/createas.c | 1 +
src/backend/commands/sequence.c | 1 +
src/backend/commands/tablecmds.c | 490 +++++++++++++++++++++++++++---------
src/backend/commands/view.c | 1 +
src/backend/nodes/copyfuncs.c | 48 ++++
src/backend/nodes/equalfuncs.c | 42 +++
src/backend/nodes/outfuncs.c | 27 ++
src/backend/parser/gram.y | 167 ++++++++++++-
src/backend/parser/parse_agg.c | 7 +-
src/backend/parser/parse_expr.c | 23 ++
src/backend/parser/parse_utilcmd.c | 318 +++++++++++++++++++++++
src/include/catalog/partition.h | 1 +
src/include/nodes/nodes.h | 3 +
src/include/nodes/parsenodes.h | 43 +++-
src/include/parser/kwlist.h | 3 +
src/include/parser/parse_node.h | 3 +-
18 files changed, 1184 insertions(+), 130 deletions(-)
diff --git a/doc/src/sgml/ref/alter_table.sgml b/doc/src/sgml/ref/alter_table.sgml
index 5ca211e..97744c9 100644
--- a/doc/src/sgml/ref/alter_table.sgml
+++ b/doc/src/sgml/ref/alter_table.sgml
@@ -77,12 +77,15 @@ ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable>
NOT OF
OWNER TO { <replaceable class="PARAMETER">new_owner</replaceable> | CURRENT_USER | SESSION_USER }
REPLICA IDENTITY { DEFAULT | USING INDEX <replaceable class="PARAMETER">index_name</replaceable> | FULL | NOTHING }
+ ATTACH PARTITION <replaceable class="PARAMETER">name</replaceable> <replaceable class="PARAMETER">partition_bound_spec</replaceable> [ VALIDATE | NO VALIDATE ]
+ DETACH PARTITION <replaceable class="PARAMETER">name</replaceable>
<phrase>and <replaceable class="PARAMETER">table_constraint_using_index</replaceable> is:</phrase>
[ CONSTRAINT <replaceable class="PARAMETER">constraint_name</replaceable> ]
{ UNIQUE | PRIMARY KEY } USING INDEX <replaceable class="PARAMETER">index_name</replaceable>
[ DEFERRABLE | NOT DEFERRABLE ] [ INITIALLY DEFERRED | INITIALLY IMMEDIATE ]
+
</synopsis>
</refsynopsisdiv>
@@ -704,6 +707,51 @@ ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable>
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>ATTACH PARTITION</literal></term>
+ <listitem>
+ <para>
+ This form attaches an existing table as partition of the target table,
+ provided the target table is partitioned. The partition bound
+ specification must correspond with partition method and key of the
+ partitioned table. The table being attached must have all the columns
+ as the target table and no more. Also, it must have all the matching
+ constraints as the target table. That includes <literal>NOT NULL</>
+ and <literal>CHECK</> constraints. <literal>UNIQUE</literal>,
+ <literal>PRIMARY KEY</literal>, and <literal>FOREIGN KEY</literal>
+ constraints are not considered, but this might change in the future.
+ </para>
+
+ <para>
+ If <literal>NO VALIDATE</literal> clause is specified, existing rows in
+ source table are are not checked to see if they violate the partition
+ bound specification. This allows to quickly load a large amount of data
+ into the partitioned table. The default behavior is to perform the
+ check, as if <literal>VALIDATE</literal> clause were specified.
+ </para>
+
+ <para>
+ Note that once the table becomes a partition, it will be dropped or
+ truncated when the parent table is dropped or truncated. Dropping
+ it directly using <literal>DROP TABLE</literal> will fail; it must
+ first be detached from the parent. However, truncating a partition
+ directly works.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><literal>DETACH PARTITION</literal></term>
+ <listitem>
+ <para>
+ This form detaches specified partition of the target table, provided the
+ target table is partitioned and the specified partition exists. The
+ resulting table continues to exist as a standalone table with no ties
+ remaining with the parent partitioned table.
+ </para>
+ </listitem>
+ </varlistentry>
+
</variablelist>
</para>
@@ -936,6 +984,15 @@ ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable>
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><replaceable class="PARAMETER">partition_bound_spec</replaceable></term>
+ <listitem>
+ <para>
+ The partition bound specification for a new partition.
+ </para>
+ </listitem>
+ </varlistentry>
+
</variablelist>
</refsect1>
diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml
index 70a7579..b377c07 100644
--- a/doc/src/sgml/ref/create_table.sgml
+++ b/doc/src/sgml/ref/create_table.sgml
@@ -44,6 +44,17 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
[ ON COMMIT { PRESERVE ROWS | DELETE ROWS | DROP } ]
[ TABLESPACE <replaceable class="PARAMETER">tablespace_name</replaceable> ]
+CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name</replaceable>
+ PARTITION OF <replaceable class="PARAMETER">parent_table</replaceable> [ (
+ { <replaceable class="PARAMETER">column_name</replaceable> WITH OPTIONS [ <replaceable class="PARAMETER">column_constraint</replaceable> [ ... ] ]
+ | <replaceable>table_constraint</replaceable> }
+ [, ... ]
+) ] <replaceable class="PARAMETER">partition_bound_spec</replaceable>
+[ PARTITION BY {RANGE | LIST} ( { <replaceable class="parameter">column_name</replaceable> | ( <replaceable class="parameter">expression</replaceable> ) } [ <replaceable class="parameter">opclass</replaceable> ] [, ...] )
+[ WITH ( <replaceable class="PARAMETER">storage_parameter</replaceable> [= <replaceable class="PARAMETER">value</replaceable>] [, ... ] ) | WITH OIDS | WITHOUT OIDS ]
+[ ON COMMIT { PRESERVE ROWS | DELETE ROWS | DROP } ]
+[ TABLESPACE <replaceable class="PARAMETER">tablespace_name</replaceable> ]
+
<phrase>where <replaceable class="PARAMETER">column_constraint</replaceable> is:</phrase>
[ CONSTRAINT <replaceable class="PARAMETER">constraint_name</replaceable> ]
@@ -72,6 +83,10 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
{ INCLUDING | EXCLUDING } { DEFAULTS | CONSTRAINTS | INDEXES | STORAGE | COMMENTS | ALL }
+<phrase>and <replaceable class="PARAMETER">partition_bound_spec</replaceable> is:</phrase>
+
+FOR VALUES { <replaceable class="PARAMETER">list_spec</replaceable> | <replaceable class="PARAMETER">range_spec</replaceable> }
+
<phrase><replaceable class="PARAMETER">index_parameters</replaceable> in <literal>UNIQUE</literal>, <literal>PRIMARY KEY</literal>, and <literal>EXCLUDE</literal> constraints are:</phrase>
[ WITH ( <replaceable class="PARAMETER">storage_parameter</replaceable> [= <replaceable class="PARAMETER">value</replaceable>] [, ... ] ) ]
@@ -80,8 +95,22 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
<phrase><replaceable class="PARAMETER">exclude_element</replaceable> in an <literal>EXCLUDE</literal> constraint is:</phrase>
{ <replaceable class="parameter">column_name</replaceable> | ( <replaceable class="parameter">expression</replaceable> ) } [ <replaceable class="parameter">opclass</replaceable> ] [ ASC | DESC ] [ NULLS { FIRST | LAST } ]
-</synopsis>
+<phrase><replaceable class="PARAMETER">list_spec</replaceable> in <literal>FOR VALUES</literal> is:</phrase>
+
+{ IN ( <replaceable class="PARAMETER">expression</replaceable> [, ...] ) }
+
+<phrase><replaceable class="PARAMETER">range_spec</replaceable> in <literal>FOR VALUES</literal> is:</phrase>
+
+{ START (<replaceable class="PARAMETER">lower-bound</replaceable>) [ INCLUSIVE | EXCLUSIVE ] |
+ END (<replaceable class="PARAMETER">upper-bound</replaceable>) [ INCLUSIVE | EXCLUSIVE ] |
+ START (<replaceable class="PARAMETER">lower-bound</replaceable>) [ INCLUSIVE | EXCLUSIVE ] END (<replaceable class="PARAMETER">upper-bound</replaceable>) [ INCLUSIVE | EXCLUSIVE ] }
+
+<phrase>where <replaceable class="PARAMETER">lower-bound</replaceable> and <replaceable class="PARAMETER">upper-bound</replaceable> are:</phrase>
+
+{ <replaceable class="PARAMETER">expression</replaceable> [, ...] }
+
+</synopsis>
</refsynopsisdiv>
<refsect1 id="SQL-CREATETABLE-description">
@@ -232,6 +261,49 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
</varlistentry>
<varlistentry>
+ <term><literal>PARTITION OF <replaceable class="PARAMETER">parent_table</replaceable></literal></term>
+ <listitem>
+ <para>
+ Creates the table as <firstterm>partition</firstterm> of the specified
+ parent table (name optionally schema-qualified).
+ </para>
+
+ <para>
+ A partition bound specification must be present and must correspond with
+ partition method and key of the parent table. It is checked using the
+ specification that the new partition does not overlap with any existing
+ partitions of the parent.
+ </para>
+
+ <para>
+ A partition cannot have columns other than those inherited from the
+ parent. That includes the <structfield>oid</> column, which can be
+ specified using the <literal>WITH (OIDS)</literal> clause. On the other
+ hand, if parent has the <structfield>oid</> column, the partition
+ inherits the same, overriding the <literal>WITH (OIDS=FALSE)</literal>
+ clause, if any. Defaults and constraints can optionally be specified
+ for each of the inherited columns, which override those in the parent.
+ One can also specify table constraints, in addition to those inherited
+ from the parent. Note that all subsequent schema modifications to the
+ parent propagate to partition.
+ </para>
+
+ <para>
+ Any data row subsequently inserted into the parent table is mapped to
+ and stored in the partition, provided partition key of the row falls
+ within the partition bounds.
+ </para>
+
+ <para>
+ A partition is dropped or truncated when the parent table is dropped or
+ truncated. Dropping it directly using <literal>DROP TABLE</literal>
+ will fail; it must first be <firstterm>detached</> from the parent.
+ However, truncating a partition directly works.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><replaceable class="PARAMETER">column_name</replaceable></term>
<listitem>
<para>
@@ -340,6 +412,11 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
</para>
<para>
+ When specifying for a table being created as partition, one needs to
+ use column names from the parent table as part of the key.
+ </para>
+
+ <para>
Currently, there are following limitations on definition of partitioned
tables: one cannot specify any UNIQUE, PRIMARY KEY, EXCLUDE and/or
FOREIGN KEY constraints.
diff --git a/src/backend/commands/createas.c b/src/backend/commands/createas.c
index cb7a145..427470c 100644
--- a/src/backend/commands/createas.c
+++ b/src/backend/commands/createas.c
@@ -342,6 +342,7 @@ intorel_startup(DestReceiver *self, int operation, TupleDesc typeinfo)
col->is_local = true;
col->is_not_null = false;
col->is_from_type = false;
+ col->is_for_partition = false;
col->storage = 0;
col->raw_default = NULL;
col->cooked_default = NULL;
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index 5dcd4cf..21d9f96 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -159,6 +159,7 @@ DefineSequence(CreateSeqStmt *seq)
coldef->is_local = true;
coldef->is_not_null = true;
coldef->is_from_type = false;
+ coldef->is_for_partition = false;
coldef->storage = 0;
coldef->raw_default = NULL;
coldef->cooked_default = NULL;
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index fcae323..6e66405 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -279,7 +279,8 @@ typedef struct
static void truncate_check_rel(Relation rel);
static List *MergeAttributes(List *schema, List *supers, char relpersistence,
- List **supOids, List **supconstr, int *supOidCount);
+ List **supOids, List **supconstr, int *supOidCount,
+ bool is_partition);
static bool MergeCheckConstraint(List *constraints, char *name, Node *expr);
static void MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel);
static void MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel);
@@ -448,6 +449,11 @@ static void ComputePartitionAttrs(Oid relid, List *partParams,
static bool find_attr_reference_walker(Node *node,
find_attr_reference_context *context);
static bool is_partition_attr(Relation rel, AttrNumber attnum, bool *is_expr);
+static void CreateInheritance(Relation child_rel, Relation parent_rel);
+static void RemoveInheritance(Relation child_rel, Relation parent_rel);
+static ObjectAddress ATExecAttachPartition(List **wqueue, Relation rel,
+ PartitionCmd *cmd);
+static ObjectAddress ATExecDetachPartition(Relation rel, RangeVar *name);
/* ----------------------------------------------------------------
@@ -590,10 +596,16 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
/*
* Look up inheritance ancestors and generate relation schema, including
* inherited attributes.
+ *
+ * The last parameter implicitly specifies that the table is being created
+ * as partition and schema consists of columns definitions corresponding
+ * to non-dropped columns of the parent constructed such that each
+ * attribute of the table is created as inherited and non-local.
*/
schema = MergeAttributes(schema, stmt->inhRelations,
stmt->relation->relpersistence,
- &inheritOids, &old_constraints, &parentOidCount);
+ &inheritOids, &old_constraints, &parentOidCount,
+ stmt->partbound != NULL);
/*
* Create a tuple descriptor from the relation schema. Note that this
@@ -1452,7 +1464,8 @@ storage_name(char c)
*/
static List *
MergeAttributes(List *schema, List *supers, char relpersistence,
- List **supOids, List **supconstr, int *supOidCount)
+ List **supOids, List **supconstr, int *supOidCount,
+ bool is_partition)
{
ListCell *entry;
List *inhSchema = NIL;
@@ -1497,8 +1510,8 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
/*
* Typed table column option that does not belong to a column from
- * the type. This works because the columns from the type come
- * first in the list.
+ * the type or the partition parent. This works because the columns
+ * from the type or the partition parent come first in the list.
*/
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_COLUMN),
@@ -1525,6 +1538,18 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
coldef->is_from_type = false;
list_delete_cell(schema, rest, prev);
}
+ else if (coldef->is_for_partition)
+ {
+ /*
+ * merge the column options into the column from the parent
+ */
+ coldef->is_not_null = restdef->is_not_null;
+ coldef->raw_default = restdef->raw_default;
+ coldef->cooked_default = restdef->cooked_default;
+ coldef->constraints = restdef->constraints;
+ coldef->is_for_partition = false; /* job is done */
+ list_delete_cell(schema, rest, prev);
+ }
else
ereport(ERROR,
(errcode(ERRCODE_DUPLICATE_COLUMN),
@@ -1563,8 +1588,12 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
*/
relation = heap_openrv(parent, ShareUpdateExclusiveLock);
- /* Cannot inherit from partitioned tables */
- if (relation->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+ /*
+ * Cannot inherit from a partitioned table unless being created as
+ * partition.
+ */
+ if (relation->rd_rel->relkind == RELKIND_PARTITIONED_REL &&
+ !is_partition)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot inherit from partitioned table \"%s\"",
@@ -1710,6 +1739,7 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
def->is_local = false;
def->is_not_null = attribute->attnotnull;
def->is_from_type = false;
+ def->is_for_partition = false;
def->storage = attribute->attstorage;
def->raw_default = NULL;
def->cooked_default = NULL;
@@ -1838,6 +1868,9 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
* If we had no inherited attributes, the result schema is just the
* explicitly declared columns. Otherwise, we need to merge the declared
* columns into the inherited schema list.
+ *
+ * Note: In case of a partition, there are only inherited attributes that
+ * we copied from the parent as so, in transformPartitionOf().
*/
if (inhSchema != NIL)
{
@@ -1867,16 +1900,20 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
/*
* Yes, try to merge the two column definitions. They must
- * have the same type, typmod, and collation.
+ * have the same type, typmod, and collation. Don't output
+ * the notices, if partition, per note above.
*/
- if (exist_attno == schema_attno)
- ereport(NOTICE,
- (errmsg("merging column \"%s\" with inherited definition",
- attributeName)));
- else
- ereport(NOTICE,
- (errmsg("moving and merging column \"%s\" with inherited definition", attributeName),
- errdetail("User-specified column moved to the position of the inherited column.")));
+ if (!is_partition)
+ {
+ if (exist_attno == schema_attno)
+ ereport(NOTICE,
+ (errmsg("merging column \"%s\" with inherited definition",
+ attributeName)));
+ else
+ ereport(NOTICE,
+ (errmsg("moving and merging column \"%s\" with inherited definition", attributeName),
+ errdetail("User-specified column moved to the position of the inherited column.")));
+ }
def = (ColumnDef *) list_nth(inhSchema, exist_attno - 1);
typenameTypeIdAndMod(NULL, def->typeName, &defTypeId, &deftypmod);
typenameTypeIdAndMod(NULL, newdef->typeName, &newTypeId, &newtypmod);
@@ -1913,8 +1950,9 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
storage_name(def->storage),
storage_name(newdef->storage))));
- /* Mark the column as locally defined */
- def->is_local = true;
+ /* Mark the column as locally defined, unless partition */
+ if (!is_partition)
+ def->is_local = true;
/* Merge of NOT NULL constraints = OR 'em together */
def->is_not_null |= newdef->is_not_null;
/* If new def has a default, override previous default */
@@ -2093,16 +2131,20 @@ StoreCatalogInheritance1(Oid relationId, Oid parentOid,
heap_freetuple(tuple);
/*
- * Store a dependency too
+ * Store a dependency too, iff a regular inheritance relationship, which
+ * it's not, if parent is a partitioned table.
*/
- parentobject.classId = RelationRelationId;
- parentobject.objectId = parentOid;
- parentobject.objectSubId = 0;
- childobject.classId = RelationRelationId;
- childobject.objectId = relationId;
- childobject.objectSubId = 0;
+ if (!relid_is_partitioned(parentOid))
+ {
+ parentobject.classId = RelationRelationId;
+ parentobject.objectId = parentOid;
+ parentobject.objectSubId = 0;
+ childobject.classId = RelationRelationId;
+ childobject.objectId = relationId;
+ childobject.objectSubId = 0;
- recordDependencyOn(&childobject, &parentobject, DEPENDENCY_NORMAL);
+ recordDependencyOn(&childobject, &parentobject, DEPENDENCY_NORMAL);
+ }
/*
* Post creation hook of this inheritance. Since object_access_hook
@@ -3108,6 +3150,11 @@ AlterTableGetLockLevel(List *cmds)
cmd_lockmode = AlterTableGetRelOptionsLockLevel((List *) cmd->def);
break;
+ case AT_AttachPartition:
+ case AT_DetachPartition:
+ cmd_lockmode = AccessExclusiveLock;
+ break;
+
default: /* oops */
elog(ERROR, "unrecognized alter table type: %d",
(int) cmd->subtype);
@@ -3425,6 +3472,12 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
/* No command-specific prep needed */
pass = AT_PASS_MISC;
break;
+ case AT_AttachPartition:
+ case AT_DetachPartition:
+ ATSimplePermissions(rel, ATT_TABLE);
+ /* No command-specific prep needed */
+ pass = AT_PASS_MISC;
+ break;
default: /* oops */
elog(ERROR, "unrecognized alter table type: %d",
(int) cmd->subtype);
@@ -3746,6 +3799,12 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
case AT_GenericOptions:
ATExecGenericOptions(rel, (List *) cmd->def);
break;
+ case AT_AttachPartition:
+ ATExecAttachPartition(wqueue, rel, (PartitionCmd *) cmd->def);
+ break;
+ case AT_DetachPartition:
+ ATExecDetachPartition(rel, ((PartitionCmd *) cmd->def)->name);
+ break;
default: /* oops */
elog(ERROR, "unrecognized alter table type: %d",
(int) cmd->subtype);
@@ -10190,12 +10249,7 @@ ATPrepAddInherit(Relation child_rel)
static ObjectAddress
ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode)
{
- Relation parent_rel,
- catalogRelation;
- SysScanDesc scan;
- ScanKeyData key;
- HeapTuple inheritsTuple;
- int32 inhseqno;
+ Relation parent_rel;
List *children;
ObjectAddress address;
@@ -10240,38 +10294,6 @@ ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode)
errmsg("cannot inherit from partitioned table \"%s\"", parent->relname)));
/*
- * Check for duplicates in the list of parents, and determine the highest
- * inhseqno already present; we'll use the next one for the new parent.
- * (Note: get RowExclusiveLock because we will write pg_inherits below.)
- *
- * Note: we do not reject the case where the child already inherits from
- * the parent indirectly; CREATE TABLE doesn't reject comparable cases.
- */
- catalogRelation = heap_open(InheritsRelationId, RowExclusiveLock);
- ScanKeyInit(&key,
- Anum_pg_inherits_inhrelid,
- BTEqualStrategyNumber, F_OIDEQ,
- ObjectIdGetDatum(RelationGetRelid(child_rel)));
- scan = systable_beginscan(catalogRelation, InheritsRelidSeqnoIndexId,
- true, NULL, 1, &key);
-
- /* inhseqno sequences start at 1 */
- inhseqno = 0;
- while (HeapTupleIsValid(inheritsTuple = systable_getnext(scan)))
- {
- Form_pg_inherits inh = (Form_pg_inherits) GETSTRUCT(inheritsTuple);
-
- if (inh->inhparent == RelationGetRelid(parent_rel))
- ereport(ERROR,
- (errcode(ERRCODE_DUPLICATE_TABLE),
- errmsg("relation \"%s\" would be inherited from more than once",
- RelationGetRelationName(parent_rel))));
- if (inh->inhseqno > inhseqno)
- inhseqno = inh->inhseqno;
- }
- systable_endscan(scan);
-
- /*
* Prevent circularity by seeing if proposed parent inherits from child.
* (In particular, this disallows making a rel inherit from itself.)
*
@@ -10304,6 +10326,69 @@ ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode)
RelationGetRelationName(child_rel),
RelationGetRelationName(parent_rel))));
+ /* OK to create inheritance */
+ CreateInheritance(child_rel, parent_rel);
+
+ ObjectAddressSet(address, RelationRelationId,
+ RelationGetRelid(parent_rel));
+
+ /* keep our lock on the parent relation until commit */
+ heap_close(parent_rel, NoLock);
+
+ return address;
+}
+
+/*
+ * CreateInheritance
+ * Catalog manipulation portion of creating inheritance between a child
+ * table and a parent table.
+ *
+ * Common to ATExecAddInherit() and ATExecAttachPartition().
+ */
+static void
+CreateInheritance(Relation child_rel, Relation parent_rel)
+{
+ Relation catalogRelation;
+ SysScanDesc scan;
+ ScanKeyData key;
+ HeapTuple inheritsTuple;
+ int32 inhseqno;
+
+ /* Note: get RowExclusiveLock because we will write pg_inherits below. */
+ catalogRelation = heap_open(InheritsRelationId, RowExclusiveLock);
+
+ /*
+ * Check for duplicates in the list of parents, and determine the highest
+ * inhseqno already present; we'll use the next one for the new parent.
+ * Also, if proposed child is a partition, it cannot already be inheriting.
+ *
+ * Note: we do not reject the case where the child already inherits from
+ * the parent indirectly; CREATE TABLE doesn't reject comparable cases.
+ */
+ ScanKeyInit(&key,
+ Anum_pg_inherits_inhrelid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(RelationGetRelid(child_rel)));
+ scan = systable_beginscan(catalogRelation, InheritsRelidSeqnoIndexId,
+ true, NULL, 1, &key);
+
+ /* inhseqno sequences start at 1 */
+ inhseqno = 0;
+ while (HeapTupleIsValid(inheritsTuple = systable_getnext(scan)))
+ {
+ Form_pg_inherits inh = (Form_pg_inherits) GETSTRUCT(inheritsTuple);
+
+ if (inh->inhparent == RelationGetRelid(parent_rel))
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_TABLE),
+ errmsg("relation \"%s\" would be inherited from more than once",
+ RelationGetRelationName(parent_rel))));
+
+ if (inh->inhseqno > inhseqno)
+ inhseqno = inh->inhseqno;
+ }
+ systable_endscan(scan);
+
/* Match up the columns and bump attinhcount as needed */
MergeAttributesIntoExisting(child_rel, parent_rel);
@@ -10318,16 +10403,8 @@ ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode)
inhseqno + 1,
catalogRelation);
- ObjectAddressSet(address, RelationRelationId,
- RelationGetRelid(parent_rel));
-
/* Now we're done with pg_inherits */
heap_close(catalogRelation, RowExclusiveLock);
-
- /* keep our lock on the parent relation until commit */
- heap_close(parent_rel, NoLock);
-
- return address;
}
/*
@@ -10378,7 +10455,7 @@ constraints_equivalent(HeapTuple a, HeapTuple b, TupleDesc tupleDesc)
* Check columns in child table match up with columns in parent, and increment
* their attinhcount.
*
- * Called by ATExecAddInherit
+ * Called by CreateInheritance
*
* Currently all parent columns must be found in child. Missing columns are an
* error. One day we might consider creating new columns like CREATE TABLE
@@ -10396,11 +10473,13 @@ MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel)
int parent_natts;
TupleDesc tupleDesc;
HeapTuple tuple;
+ bool is_attach_partition;
attrrel = heap_open(AttributeRelationId, RowExclusiveLock);
tupleDesc = RelationGetDescr(parent_rel);
parent_natts = tupleDesc->natts;
+ is_attach_partition = relid_is_partitioned(RelationGetRelid(parent_rel));
for (parent_attno = 1; parent_attno <= parent_natts; parent_attno++)
{
@@ -10423,14 +10502,16 @@ MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel)
attribute->atttypmod != childatt->atttypmod)
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
- errmsg("child table \"%s\" has different type for column \"%s\"",
+ errmsg("%s table \"%s\" has different type for column \"%s\"",
+ is_attach_partition ? "source" : "child",
RelationGetRelationName(child_rel),
attributeName)));
if (attribute->attcollation != childatt->attcollation)
ereport(ERROR,
(errcode(ERRCODE_COLLATION_MISMATCH),
- errmsg("child table \"%s\" has different collation for column \"%s\"",
+ errmsg("%s table \"%s\" has different collation for column \"%s\"",
+ is_attach_partition ? "source" : "child",
RelationGetRelationName(child_rel),
attributeName)));
@@ -10441,8 +10522,9 @@ MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel)
if (attribute->attnotnull && !childatt->attnotnull)
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
- errmsg("column \"%s\" in child table must be marked NOT NULL",
- attributeName)));
+ errmsg("column \"%s\" in %s table must be marked NOT NULL",
+ attributeName,
+ is_attach_partition ? "source" : "child")));
/*
* OK, bump the child column's inheritance count. (If we fail
@@ -10457,7 +10539,8 @@ MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel)
{
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
- errmsg("child table is missing column \"%s\"",
+ errmsg("%s table is missing column \"%s\"",
+ is_attach_partition ? "source" : "child",
attributeName)));
}
}
@@ -10471,7 +10554,7 @@ MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel)
*
* Constraints that are marked ONLY in the parent are ignored.
*
- * Called by ATExecAddInherit
+ * Called by CreateInheritance
*
* Currently all constraints in parent must be present in the child. One day we
* may consider adding new constraints like CREATE TABLE does.
@@ -10490,9 +10573,11 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
SysScanDesc parent_scan;
ScanKeyData parent_key;
HeapTuple parent_tuple;
+ bool is_attach_partition;
catalog_relation = heap_open(ConstraintRelationId, RowExclusiveLock);
tuple_desc = RelationGetDescr(catalog_relation);
+ is_attach_partition = relid_is_partitioned(RelationGetRelid(parent_rel));
/* Outer loop scans through the parent's constraint definitions */
ScanKeyInit(&parent_key,
@@ -10540,7 +10625,8 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
if (!constraints_equivalent(parent_tuple, child_tuple, tuple_desc))
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
- errmsg("child table \"%s\" has different definition for check constraint \"%s\"",
+ errmsg("%s table \"%s\" has different definition for check constraint \"%s\"",
+ is_attach_partition ? "source" : "child",
RelationGetRelationName(child_rel),
NameStr(parent_con->conname))));
@@ -10548,8 +10634,9 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
if (child_con->connoinherit)
ereport(ERROR,
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
- errmsg("constraint \"%s\" conflicts with non-inherited constraint on child table \"%s\"",
+ errmsg("constraint \"%s\" conflicts with non-inherited constraint on %s table \"%s\"",
NameStr(child_con->conname),
+ is_attach_partition ? "source" : "child",
RelationGetRelationName(child_rel))));
/*
@@ -10572,7 +10659,8 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
if (!found)
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
- errmsg("child table is missing constraint \"%s\"",
+ errmsg("%s table is missing constraint \"%s\"",
+ is_attach_partition ? "source" : "child",
NameStr(parent_con->conname))));
}
@@ -10583,6 +10671,41 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
/*
* ALTER TABLE NO INHERIT
*
+ * Return value is the address of the relation that is no longer parent.
+ */
+static ObjectAddress
+ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
+{
+ ObjectAddress address;
+ Relation parent_rel;
+
+ /*
+ * AccessShareLock on the parent is probably enough, seeing that DROP
+ * TABLE doesn't lock parent tables at all. We need some lock since we'll
+ * be inspecting the parent's schema.
+ */
+ parent_rel = heap_openrv(parent, AccessShareLock);
+
+ /*
+ * We don't bother to check ownership of the parent table --- ownership of
+ * the child is presumed enough rights.
+ */
+
+ /* Off to RemoveInheritance() where most of the work happens */
+ RemoveInheritance(rel, parent_rel);
+
+ /* keep our lock on the parent relation until commit */
+ heap_close(parent_rel, NoLock);
+
+ ObjectAddressSet(address, RelationRelationId,
+ RelationGetRelid(parent_rel));
+
+ return address;
+}
+
+/*
+ * RemoveInheritance
+ *
* Drop a parent from the child's parents. This just adjusts the attinhcount
* and attislocal of the columns and removes the pg_inherit and pg_depend
* entries.
@@ -10596,13 +10719,11 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
* coninhcount and conislocal for inherited constraints are adjusted in
* exactly the same way.
*
- * Return value is the address of the relation that is no longer parent.
+ * Common to ATExecDropInherit() and ATExecDetachPartition().
*/
-static ObjectAddress
-ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
+static void
+RemoveInheritance(Relation child_rel, Relation parent_rel)
{
- Relation parent_rel;
- Oid parent_oid;
Relation catalogRelation;
SysScanDesc scan;
ScanKeyData key[3];
@@ -10611,19 +10732,9 @@ ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
constraintTuple;
List *connames;
bool found = false;
- ObjectAddress address;
+ bool is_detach_partition;
- /*
- * AccessShareLock on the parent is probably enough, seeing that DROP
- * TABLE doesn't lock parent tables at all. We need some lock since we'll
- * be inspecting the parent's schema.
- */
- parent_rel = heap_openrv(parent, AccessShareLock);
-
- /*
- * We don't bother to check ownership of the parent table --- ownership of
- * the child is presumed enough rights.
- */
+ is_detach_partition = relid_is_partitioned(RelationGetRelid(parent_rel));
/*
* Find and destroy the pg_inherits entry linking the two, or error out if
@@ -10633,7 +10744,7 @@ ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
ScanKeyInit(&key[0],
Anum_pg_inherits_inhrelid,
BTEqualStrategyNumber, F_OIDEQ,
- ObjectIdGetDatum(RelationGetRelid(rel)));
+ ObjectIdGetDatum(RelationGetRelid(child_rel)));
scan = systable_beginscan(catalogRelation, InheritsRelidSeqnoIndexId,
true, NULL, 1, key);
@@ -10654,11 +10765,20 @@ ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
heap_close(catalogRelation, RowExclusiveLock);
if (!found)
- ereport(ERROR,
- (errcode(ERRCODE_UNDEFINED_TABLE),
- errmsg("relation \"%s\" is not a parent of relation \"%s\"",
- RelationGetRelationName(parent_rel),
- RelationGetRelationName(rel))));
+ {
+ if (!is_detach_partition)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_TABLE),
+ errmsg("relation \"%s\" is not a parent of relation \"%s\"",
+ RelationGetRelationName(parent_rel),
+ RelationGetRelationName(child_rel))));
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_TABLE),
+ errmsg("relation \"%s\" is not a partition of relation \"%s\"",
+ RelationGetRelationName(child_rel),
+ RelationGetRelationName(parent_rel))));
+ }
/*
* Search through child columns looking for ones matching parent rel
@@ -10667,7 +10787,7 @@ ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
ScanKeyInit(&key[0],
Anum_pg_attribute_attrelid,
BTEqualStrategyNumber, F_OIDEQ,
- ObjectIdGetDatum(RelationGetRelid(rel)));
+ ObjectIdGetDatum(RelationGetRelid(child_rel)));
scan = systable_beginscan(catalogRelation, AttributeRelidNumIndexId,
true, NULL, 1, key);
while (HeapTupleIsValid(attributeTuple = systable_getnext(scan)))
@@ -10729,7 +10849,7 @@ ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
ScanKeyInit(&key[0],
Anum_pg_constraint_conrelid,
BTEqualStrategyNumber, F_OIDEQ,
- ObjectIdGetDatum(RelationGetRelid(rel)));
+ ObjectIdGetDatum(RelationGetRelid(child_rel)));
scan = systable_beginscan(catalogRelation, ConstraintRelidIndexId,
true, NULL, 1, key);
@@ -10760,7 +10880,7 @@ ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
if (copy_con->coninhcount <= 0) /* shouldn't happen */
elog(ERROR, "relation %u has non-inherited constraint \"%s\"",
- RelationGetRelid(rel), NameStr(copy_con->conname));
+ RelationGetRelid(child_rel), NameStr(copy_con->conname));
copy_con->coninhcount--;
if (copy_con->coninhcount == 0)
@@ -10772,30 +10892,22 @@ ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
}
}
- parent_oid = RelationGetRelid(parent_rel);
-
systable_endscan(scan);
heap_close(catalogRelation, RowExclusiveLock);
- drop_parent_dependency(RelationGetRelid(rel),
- RelationRelationId,
- RelationGetRelid(parent_rel));
-
+ /* Remember that dependency exists iff regular inheritance. */
+ if (!is_detach_partition)
+ drop_parent_dependency(RelationGetRelid(child_rel),
+ RelationRelationId,
+ RelationGetRelid(parent_rel));
/*
* Post alter hook of this inherits. Since object_access_hook doesn't take
* multiple object identifiers, we relay oid of parent relation using
* auxiliary_id argument.
*/
InvokeObjectPostAlterHookArg(InheritsRelationId,
- RelationGetRelid(rel), 0,
+ RelationGetRelid(child_rel), 0,
RelationGetRelid(parent_rel), false);
-
- /* keep our lock on the parent relation until commit */
- heap_close(parent_rel, NoLock);
-
- ObjectAddressSet(address, RelationRelationId, parent_oid);
-
- return address;
}
/*
@@ -12428,3 +12540,137 @@ ComputePartitionAttrs(Oid relid, List *partParams, AttrNumber *partattrs,
partopclass[attn++] = opclassOid;
}
}
+
+
+/*
+ * ALTER TABLE <name> ATTACH PARTITION <partition-name> FOR VALUES
+ *
+ * Return the address of the newly attached partition.
+ */
+static ObjectAddress
+ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
+{
+ Relation attachRel,
+ inheritsRelation;
+ SysScanDesc scan;
+ ScanKeyData key;
+ HeapTuple tuple;
+ AttrNumber attno;
+ int natts;
+ TupleDesc tupleDesc;
+ ObjectAddress address;
+
+ attachRel = heap_openrv(cmd->name, AccessShareLock);
+
+ /*
+ * Must be owner of both parent and source table -- parent was checked by
+ * ATSimplePermissions call in ATPrepCmd
+ */
+ ATSimplePermissions(attachRel, ATT_TABLE);
+
+ if (attachRel->rd_rel->reloftype)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot attach a typed table as partition")));
+
+ if (attachRel->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot attach a partitioned table as partition")));
+
+ /* attachRel should not already be a inheritance child of some relation */
+ inheritsRelation = heap_open(InheritsRelationId, AccessShareLock);
+ ScanKeyInit(&key,
+ Anum_pg_inherits_inhrelid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(RelationGetRelid(attachRel)));
+ scan = systable_beginscan(inheritsRelation, InheritsRelidSeqnoIndexId,
+ true, NULL, 1, &key);
+ if (HeapTupleIsValid(systable_getnext(scan)))
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot attach table that inherits as partition")));
+ systable_endscan(scan);
+ heap_close(inheritsRelation, AccessShareLock);
+
+ /* If attachRel is temp, it must belong to this session */
+ if (attachRel->rd_rel->relpersistence == RELPERSISTENCE_TEMP &&
+ !attachRel->rd_islocaltemp)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot attach a temporary relation of another session as partition ")));
+
+ /* If parent has OIDs, attachRel must have OIDs */
+ if (rel->rd_rel->relhasoids && !attachRel->rd_rel->relhasoids)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot attach table \"%s\" without OIDs as partition of"
+ " table \"%s\" with OIDs", RelationGetRelationName(attachRel),
+ RelationGetRelationName(rel))));
+
+ /* OTOH, if parent doesn't have them, do not allow in attachRel either */
+ if (attachRel->rd_rel->relhasoids && !rel->rd_rel->relhasoids)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot attach table \"%s\" with OIDs as partition of table"
+ " \"%s\" without OIDs", RelationGetRelationName(attachRel),
+ RelationGetRelationName(rel))));
+
+ /* Check if there are any columns in attachRel that aren't in the parent */
+ tupleDesc = RelationGetDescr(attachRel);
+ natts = tupleDesc->natts;
+ for (attno = 1; attno <= natts; attno++)
+ {
+ Form_pg_attribute attribute = tupleDesc->attrs[attno - 1];
+ char *attributeName = NameStr(attribute->attname);
+
+ /* Ignore dropped */
+ if (attribute->attisdropped)
+ continue;
+
+ /* Find same column in parent (matching on column name). */
+ tuple = SearchSysCacheCopyAttName(RelationGetRelid(rel), attributeName);
+ if (!HeapTupleIsValid(tuple))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("table \"%s\" contains column \"%s\" not found in parent \"%s\"",
+ RelationGetRelationName(attachRel), attributeName,
+ RelationGetRelationName(rel)),
+ errdetail("Table being attached should contain only the columns"
+ " present in parent.")));
+ }
+
+ /* OK to create inheritance. Rest of the checks performed there */
+ CreateInheritance(attachRel, rel);
+
+ ObjectAddressSet(address, RelationRelationId, RelationGetRelid(attachRel));
+
+ /* keep our lock until commit */
+ heap_close(attachRel, NoLock);
+
+ return address;
+}
+
+/*
+ * ALTER TABLE DETACH PARTITION
+ *
+ * Return the address of the relation that is no longer a partition of rel.
+ */
+static ObjectAddress
+ATExecDetachPartition(Relation rel, RangeVar *name)
+{
+ Relation partRel;
+ ObjectAddress address;
+
+ partRel = heap_openrv(name, AccessShareLock);
+
+ /* All inheritance related checks are performed within the function */
+ RemoveInheritance(partRel, rel);
+
+ ObjectAddressSet(address, RelationRelationId, RelationGetRelid(partRel));
+
+ /* keep our lock until commit */
+ heap_close(partRel, NoLock);
+
+ return address;
+}
diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c
index e9d9ba2..c9c72d9 100644
--- a/src/backend/commands/view.c
+++ b/src/backend/commands/view.c
@@ -95,6 +95,7 @@ DefineVirtualRelation(RangeVar *relation, List *tlist, bool replace,
def->is_local = true;
def->is_not_null = false;
def->is_from_type = false;
+ def->is_for_partition = false;
def->storage = 0;
def->raw_default = NULL;
def->cooked_default = NULL;
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 5f4f66f..e924ddb 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2604,6 +2604,7 @@ _copyColumnDef(const ColumnDef *from)
COPY_SCALAR_FIELD(is_local);
COPY_SCALAR_FIELD(is_not_null);
COPY_SCALAR_FIELD(is_from_type);
+ COPY_SCALAR_FIELD(is_for_partition);
COPY_SCALAR_FIELD(storage);
COPY_NODE_FIELD(raw_default);
COPY_NODE_FIELD(cooked_default);
@@ -2999,6 +3000,7 @@ CopyCreateStmtFields(const CreateStmt *from, CreateStmt *newnode)
COPY_NODE_FIELD(relation);
COPY_NODE_FIELD(tableElts);
COPY_NODE_FIELD(inhRelations);
+ COPY_NODE_FIELD(partbound);
COPY_NODE_FIELD(partby);
COPY_NODE_FIELD(ofTypename);
COPY_NODE_FIELD(constraints);
@@ -4181,6 +4183,43 @@ _copyPartitionElem(const PartitionElem *from)
return newnode;
}
+static PartitionListSpec *
+_copyPartitionListSpec(const PartitionListSpec *from)
+{
+ PartitionListSpec *newnode = makeNode(PartitionListSpec);
+
+ COPY_NODE_FIELD(values);
+ COPY_LOCATION_FIELD(location);
+
+ return newnode;
+}
+
+static PartitionRangeSpec *
+_copyPartitionRangeSpec(const PartitionRangeSpec *from)
+{
+ PartitionRangeSpec *newnode = makeNode(PartitionRangeSpec);
+
+ COPY_SCALAR_FIELD(lowerinc);
+ COPY_NODE_FIELD(lower);
+ COPY_SCALAR_FIELD(upperinc);
+ COPY_NODE_FIELD(upper);
+ COPY_LOCATION_FIELD(location);
+
+ return newnode;
+}
+
+static PartitionCmd *
+_copyPartitionCmd(const PartitionCmd *from)
+{
+ PartitionCmd *newnode = makeNode(PartitionCmd);
+
+ COPY_NODE_FIELD(name);
+ COPY_NODE_FIELD(bound);
+ COPY_SCALAR_FIELD(skip_validate);
+
+ return newnode;
+}
+
/* ****************************************************************
* pg_list.h copy functions
* ****************************************************************
@@ -5080,6 +5119,15 @@ copyObject(const void *from)
case T_PartitionElem:
retval = _copyPartitionElem(from);
break;
+ case T_PartitionListSpec:
+ retval = _copyPartitionListSpec(from);
+ break;
+ case T_PartitionRangeSpec:
+ retval = _copyPartitionRangeSpec(from);
+ break;
+ case T_PartitionCmd:
+ retval = _copyPartitionCmd(from);
+ break;
default:
elog(ERROR, "unrecognized node type: %d", (int) nodeTag(from));
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 001a468..51b1e86 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1154,6 +1154,7 @@ _equalCreateStmt(const CreateStmt *a, const CreateStmt *b)
COMPARE_NODE_FIELD(relation);
COMPARE_NODE_FIELD(tableElts);
COMPARE_NODE_FIELD(inhRelations);
+ COMPARE_NODE_FIELD(partbound);
COMPARE_NODE_FIELD(partby);
COMPARE_NODE_FIELD(ofTypename);
COMPARE_NODE_FIELD(constraints);
@@ -2361,6 +2362,7 @@ _equalColumnDef(const ColumnDef *a, const ColumnDef *b)
COMPARE_SCALAR_FIELD(is_local);
COMPARE_SCALAR_FIELD(is_not_null);
COMPARE_SCALAR_FIELD(is_from_type);
+ COMPARE_SCALAR_FIELD(is_for_partition);
COMPARE_SCALAR_FIELD(storage);
COMPARE_NODE_FIELD(raw_default);
COMPARE_NODE_FIELD(cooked_default);
@@ -2639,6 +2641,37 @@ _equalPartitionElem(const PartitionElem *a, const PartitionElem *b)
return true;
}
+static bool
+_equalPartitionListSpec(const PartitionListSpec *a, const PartitionListSpec *b)
+{
+ COMPARE_NODE_FIELD(values);
+ COMPARE_LOCATION_FIELD(location);
+
+ return true;
+}
+
+static bool
+_equalPartitionRangeSpec(const PartitionRangeSpec *a, const PartitionRangeSpec *b)
+{
+ COMPARE_SCALAR_FIELD(lowerinc);
+ COMPARE_NODE_FIELD(lower);
+ COMPARE_SCALAR_FIELD(upperinc);
+ COMPARE_NODE_FIELD(upper);
+ COMPARE_LOCATION_FIELD(location);
+
+ return true;
+}
+
+static bool
+_equalPartitionCmd(const PartitionCmd *a, const PartitionCmd *b)
+{
+ COMPARE_NODE_FIELD(name);
+ COMPARE_NODE_FIELD(bound);
+ COMPARE_SCALAR_FIELD(skip_validate);
+
+ return true;
+}
+
/*
* Stuff from pg_list.h
*/
@@ -3395,6 +3428,15 @@ equal(const void *a, const void *b)
case T_PartitionElem:
retval = _equalPartitionElem(a, b);
break;
+ case T_PartitionListSpec:
+ retval = _equalPartitionListSpec(a, b);
+ break;
+ case T_PartitionRangeSpec:
+ retval = _equalPartitionRangeSpec(a, b);
+ break;
+ case T_PartitionCmd:
+ retval = _equalPartitionCmd(a, b);
+ break;
default:
elog(ERROR, "unrecognized node type: %d",
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 4e70a69..b531af3 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2367,6 +2367,7 @@ _outCreateStmtInfo(StringInfo str, const CreateStmt *node)
WRITE_NODE_FIELD(relation);
WRITE_NODE_FIELD(tableElts);
WRITE_NODE_FIELD(inhRelations);
+ WRITE_NODE_FIELD(partbound);
WRITE_NODE_FIELD(partby);
WRITE_NODE_FIELD(ofTypename);
WRITE_NODE_FIELD(constraints);
@@ -2547,6 +2548,7 @@ _outColumnDef(StringInfo str, const ColumnDef *node)
WRITE_BOOL_FIELD(is_local);
WRITE_BOOL_FIELD(is_not_null);
WRITE_BOOL_FIELD(is_from_type);
+ WRITE_BOOL_FIELD(is_for_partition);
WRITE_CHAR_FIELD(storage);
WRITE_NODE_FIELD(raw_default);
WRITE_NODE_FIELD(cooked_default);
@@ -3237,6 +3239,25 @@ _outPartitionElem(StringInfo str, const PartitionElem *node)
WRITE_LOCATION_FIELD(location);
}
+static void
+_outPartitionListSpec(StringInfo str, const PartitionListSpec *node)
+{
+ WRITE_NODE_TYPE("PARTITIONLISTVALUES");
+
+ WRITE_NODE_FIELD(values);
+}
+
+static void
+_outPartitionRangeSpec(StringInfo str, const PartitionRangeSpec *node)
+{
+ WRITE_NODE_TYPE("PARTITIONRANGE");
+
+ WRITE_BOOL_FIELD(lowerinc);
+ WRITE_NODE_FIELD(lower);
+ WRITE_BOOL_FIELD(upperinc);
+ WRITE_NODE_FIELD(upper);
+}
+
/*
* outNode -
* converts a Node into ascii string and append it to 'str'
@@ -3821,6 +3842,12 @@ outNode(StringInfo str, const void *obj)
case T_PartitionElem:
_outPartitionElem(str, obj);
break;
+ case T_PartitionListSpec:
+ _outPartitionListSpec(str, obj);
+ break;
+ case T_PartitionRangeSpec:
+ _outPartitionRangeSpec(str, obj);
+ break;
default:
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 0607439..79fbb32 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -229,6 +229,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
VariableSetStmt *vsetstmt;
PartitionElem *partelem;
PartitionBy *partby;
+ PartitionRangeSpec *partrange;
}
%type <node> stmt schema_stmt
@@ -544,6 +545,14 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <partby> PartitionBy OptPartitionBy
%type <partelem> part_elem
%type <list> part_params
+%type <list> OptPartitionElementList PartitionElementList
+%type <node> PartitionElement
+%type <node> ForValues
+%type <node> partvalue
+%type <list> partvalue_list
+%type <boolean> lb_inc ub_inc
+%type <list> RangeBound
+%type <boolean> opt_validate_spec
/*
* Non-keyword token types. These are hard-wired into the "flex" lexer.
@@ -569,7 +578,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
/* ordinary key words in alphabetical order */
%token <keyword> ABORT_P ABSOLUTE_P ACCESS ACTION ADD_P ADMIN AFTER
AGGREGATE ALL ALSO ALTER ALWAYS ANALYSE ANALYZE AND ANY ARRAY AS ASC
- ASSERTION ASSIGNMENT ASYMMETRIC AT ATTRIBUTE AUTHORIZATION
+ ASSERTION ASSIGNMENT ASYMMETRIC AT ATTACH ATTRIBUTE AUTHORIZATION
BACKWARD BEFORE BEGIN_P BETWEEN BIGINT BINARY BIT
BOOLEAN_P BOTH BY
@@ -585,7 +594,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
DATA_P DATABASE DAY_P DEALLOCATE DEC DECIMAL_P DECLARE DEFAULT DEFAULTS
DEFERRABLE DEFERRED DEFINER DELETE_P DELIMITER DELIMITERS DEPENDS DESC
- DICTIONARY DISABLE_P DISCARD DISTINCT DO DOCUMENT_P DOMAIN_P DOUBLE_P DROP
+ DETACH DICTIONARY DISABLE_P DISCARD DISTINCT DO DOCUMENT_P DOMAIN_P
+ DOUBLE_P DROP
EACH ELSE ENABLE_P ENCODING ENCRYPTED END_P ENUM_P ESCAPE EVENT EXCEPT
EXCLUDE EXCLUDING EXCLUSIVE EXECUTE EXISTS EXPLAIN
@@ -599,7 +609,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
HANDLER HAVING HEADER_P HOLD HOUR_P
IDENTITY_P IF_P ILIKE IMMEDIATE IMMUTABLE IMPLICIT_P IMPORT_P IN_P
- INCLUDING INCREMENT INDEX INDEXES INHERIT INHERITS INITIALLY INLINE_P
+ INCLUDING INCLUSIVE INCREMENT INDEX INDEXES INHERIT INHERITS INITIALLY INLINE_P
INNER_P INOUT INPUT_P INSENSITIVE INSERT INSTEAD INT_P INTEGER
INTERSECT INTERVAL INTO INVOKER IS ISNULL ISOLATION
@@ -2373,6 +2383,38 @@ alter_table_cmd:
n->def = (Node *)$1;
$$ = (Node *) n;
}
+ /* ALTER TABLE <name> ATTACH PARTITION <table_name> FOR VALUES */
+ | ATTACH PARTITION qualified_name ForValues opt_validate_spec
+ {
+ AlterTableCmd *n = makeNode(AlterTableCmd);
+ PartitionCmd *cmd = makeNode(PartitionCmd);
+
+ n->subtype = AT_AttachPartition;
+ cmd->name = $3;
+ cmd->bound = (Node *) $4;
+ cmd->skip_validate = $5;
+ n->def = (Node *) cmd;
+
+ $$ = (Node *) n;
+ }
+ /* ALTER TABLE <name> DETACH PARTITION <partition_name> */
+ | DETACH PARTITION qualified_name
+ {
+ AlterTableCmd *n = makeNode(AlterTableCmd);
+ PartitionCmd *cmd = makeNode(PartitionCmd);
+
+ n->subtype = AT_DetachPartition;
+ cmd->name = $3;
+ n->def = (Node *) cmd;
+
+ $$ = (Node *) n;
+ }
+ ;
+
+opt_validate_spec:
+ NO VALIDATE { $$ = true; }
+ | VALIDATE { $$ = false; }
+ | /* EMPTY */ { $$ = false; }
;
alter_column_default:
@@ -2468,6 +2510,60 @@ reloption_elem:
}
;
+ForValues:
+ /* a LIST partition */
+ FOR VALUES IN_P '(' partvalue_list ')'
+ {
+ PartitionListSpec *n = makeNode(PartitionListSpec);
+
+ n->values = $5;
+ n->location = @1;
+
+ $$ = (Node *) n;
+ }
+
+ /* a RANGE partition */
+ | FOR VALUES START RangeBound lb_inc END_P RangeBound ub_inc
+ {
+ PartitionRangeSpec *n = makeNode(PartitionRangeSpec);
+
+ n->lowerinc = $5;
+ n->lower = $4;
+ n->upperinc = $8;
+ n->upper = $7;
+ n->location = @1;
+
+ $$ = (Node *) n;
+ }
+ ;
+
+RangeBound:
+ UNBOUNDED { $$ = NIL; }
+ | '(' partvalue_list ')' { $$ = $2; }
+ ;
+
+lb_inc:
+ EXCLUSIVE { $$ = false;}
+ | INCLUSIVE { $$ = true; }
+ | /* EMPTY */ { $$ = true; }
+ ;
+
+ub_inc:
+ INCLUSIVE { $$ = true; }
+ | EXCLUSIVE { $$ = false; }
+ | /* EMPTY */ { $$ = false; }
+ ;
+
+partvalue:
+ Sconst { $$ = makeStringConst($1, @1); }
+ | NumericOnly { $$ = makeAConst($1, @1); }
+ | NULL_P { $$ = makeNullAConst(@1); }
+ ;
+
+partvalue_list:
+ partvalue { $$ = list_make1($1); }
+ | partvalue_list ',' partvalue { $$ = lappend($1, $3); }
+ ;
/*****************************************************************************
*
@@ -2885,6 +2981,44 @@ CreateStmt: CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
n->if_not_exists = true;
$$ = (Node *)n;
}
+ | CREATE OptTemp TABLE qualified_name PARTITION OF qualified_name
+ OptPartitionElementList ForValues OptPartitionBy OptWith
+ OnCommitOption OptTableSpace
+ {
+ CreateStmt *n = makeNode(CreateStmt);
+ $4->relpersistence = $2;
+ n->relation = $4;
+ n->tableElts = $8;
+ n->inhRelations = list_make1($7);
+ n->partbound = (Node *) $9;
+ n->partby = $10;
+ n->ofTypename = NULL;
+ n->constraints = NIL;
+ n->options = $11;
+ n->oncommit = $12;
+ n->tablespacename = $13;
+ n->if_not_exists = false;
+ $$ = (Node *)n;
+ }
+ | CREATE OptTemp TABLE IF_P NOT EXISTS qualified_name PARTITION OF
+ qualified_name OptPartitionElementList ForValues OptPartitionBy
+ OptWith OnCommitOption OptTableSpace
+ {
+ CreateStmt *n = makeNode(CreateStmt);
+ $7->relpersistence = $2;
+ n->relation = $7;
+ n->tableElts = $11;
+ n->inhRelations = list_make1($10);
+ n->partbound = (Node *) $12;
+ n->partby = $13;
+ n->ofTypename = NULL;
+ n->constraints = NIL;
+ n->options = $14;
+ n->oncommit = $15;
+ n->tablespacename = $16;
+ n->if_not_exists = true;
+ $$ = (Node *)n;
+ }
;
/*
@@ -2930,6 +3064,11 @@ OptTypedTableElementList:
| /*EMPTY*/ { $$ = NIL; }
;
+OptPartitionElementList:
+ '(' PartitionElementList ')' { $$ = $2; }
+ | /*EMPTY*/ { $$ = NIL; }
+ ;
+
TableElementList:
TableElement
{
@@ -2952,6 +3091,17 @@ TypedTableElementList:
}
;
+PartitionElementList:
+ PartitionElement
+ {
+ $$ = list_make1($1);
+ }
+ | PartitionElementList ',' PartitionElement
+ {
+ $$ = lappend($1, $3);
+ }
+ ;
+
TableElement:
columnDef { $$ = $1; }
| TableLikeClause { $$ = $1; }
@@ -2963,6 +3113,11 @@ TypedTableElement:
| TableConstraint { $$ = $1; }
;
+PartitionElement:
+ columnOptions { $$ = $1; }
+ | TableConstraint { $$ = $1; }
+ ;
+
columnDef: ColId Typename create_generic_options ColQualList
{
ColumnDef *n = makeNode(ColumnDef);
@@ -2972,6 +3127,7 @@ columnDef: ColId Typename create_generic_options ColQualList
n->is_local = true;
n->is_not_null = false;
n->is_from_type = false;
+ n->is_for_partition = false;
n->storage = 0;
n->raw_default = NULL;
n->cooked_default = NULL;
@@ -2993,6 +3149,7 @@ columnOptions: ColId WITH OPTIONS ColQualList
n->is_local = true;
n->is_not_null = false;
n->is_from_type = false;
+ n->is_for_partition = false;
n->storage = 0;
n->raw_default = NULL;
n->cooked_default = NULL;
@@ -11122,6 +11279,7 @@ TableFuncElement: ColId Typename opt_collate_clause
n->is_local = true;
n->is_not_null = false;
n->is_from_type = false;
+ n->is_for_partition = false;
n->storage = 0;
n->raw_default = NULL;
n->cooked_default = NULL;
@@ -13805,6 +13963,7 @@ unreserved_keyword:
| ASSERTION
| ASSIGNMENT
| AT
+ | ATTACH
| ATTRIBUTE
| BACKWARD
| BEFORE
@@ -13851,6 +14010,7 @@ unreserved_keyword:
| DELIMITER
| DELIMITERS
| DEPENDS
+ | DETACH
| DICTIONARY
| DISABLE_P
| DISCARD
@@ -13893,6 +14053,7 @@ unreserved_keyword:
| IMPLICIT_P
| IMPORT_P
| INCLUDING
+ | INCLUSIVE
| INCREMENT
| INDEX
| INDEXES
diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c
index 77ffd84..d71fdfe 100644
--- a/src/backend/parser/parse_agg.c
+++ b/src/backend/parser/parse_agg.c
@@ -493,7 +493,9 @@ check_agglevels_and_constraints(ParseState *pstate, Node *expr)
err = _("grouping operations are not allowed in partition key expression");
break;
-
+ case EXPR_KIND_PARTITION_FOR_VALUES:
+ errkind = true;
+ break;
/*
* There is intentionally no default: case here, so that the
@@ -854,6 +856,9 @@ transformWindowFuncCall(ParseState *pstate, WindowFunc *wfunc,
case EXPR_KIND_PARTITION_KEY:
err = _("window functions are not allowed in partition key expression");
break;
+ case EXPR_KIND_PARTITION_FOR_VALUES:
+ errkind = true;
+ break;
/*
* There is intentionally no default: case here, so that the
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 6a74c28..85d7828 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -352,6 +352,26 @@ transformExprRecurse(ParseState *pstate, Node *expr)
result = (Node *) expr;
break;
}
+ case T_PartitionListSpec:
+ {
+ PartitionListSpec *list = (PartitionListSpec *) expr;
+
+ list->values = transformExpressionList(pstate, list->values,
+ pstate->p_expr_kind);
+ result = expr;
+ break;
+ }
+ case T_PartitionRangeSpec:
+ {
+ PartitionRangeSpec *range = (PartitionRangeSpec *) expr;
+
+ range->lower = transformExpressionList(pstate, range->lower,
+ pstate->p_expr_kind);
+ range->upper = transformExpressionList(pstate, range->upper,
+ pstate->p_expr_kind);
+ result = expr;
+ break;
+ }
default:
/* should not reach here */
@@ -1690,6 +1710,7 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
case EXPR_KIND_OFFSET:
case EXPR_KIND_RETURNING:
case EXPR_KIND_VALUES:
+ case EXPR_KIND_PARTITION_FOR_VALUES:
/* okay */
break;
case EXPR_KIND_CHECK_CONSTRAINT:
@@ -3230,6 +3251,8 @@ ParseExprKindName(ParseExprKind exprKind)
return "WHEN";
case EXPR_KIND_PARTITION_KEY:
return "partition key expression";
+ case EXPR_KIND_PARTITION_FOR_VALUES:
+ return "FOR VALUES";
/*
* There is intentionally no default: case here, so that the
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 465597d..c0a1bb0 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -48,8 +48,10 @@
#include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "nodes/nodeFuncs.h"
+#include "optimizer/planner.h"
#include "parser/analyze.h"
#include "parser/parse_clause.h"
+#include "parser/parse_coerce.h"
#include "parser/parse_collate.h"
#include "parser/parse_expr.h"
#include "parser/parse_relation.h"
@@ -89,6 +91,7 @@ typedef struct
* the table */
IndexStmt *pkey; /* PRIMARY KEY index, if any */
bool ispartitioned; /* true if table is partitioned */
+ Node *partbound; /* transformed FOR VALUES */
} CreateStmtContext;
/* State shared by transformCreateSchemaStmt and its subroutines */
@@ -131,6 +134,10 @@ static void transformConstraintAttrs(CreateStmtContext *cxt,
List *constraintList);
static void transformColumnType(CreateStmtContext *cxt, ColumnDef *column);
static void setSchemaName(char *context_schema, char **stmt_schema_name);
+static void transformPartitionOf(CreateStmtContext *cxt, Node *bound);
+static void transformAttachPartition(CreateStmtContext *cxt, PartitionCmd *cmd);
+static Node *transformPartitionBound(CreateStmtContext *cxt, Relation parent,
+ Node *bound);
/*
@@ -232,6 +239,7 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
cxt.alist = NIL;
cxt.pkey = NULL;
cxt.ispartitioned = stmt->partby != NULL;
+ cxt.partbound = NULL;
/*
* Notice that we allow OIDs here only for plain tables, even though
@@ -250,6 +258,9 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
if (stmt->ofTypename)
transformOfType(&cxt, stmt->ofTypename);
+ if (stmt->partbound)
+ transformPartitionOf(&cxt, stmt->partbound);
+
if (stmt->partby)
{
int partnatts = list_length(stmt->partby->partParams);
@@ -355,6 +366,7 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
*/
stmt->tableElts = cxt.columns;
stmt->constraints = cxt.ckconstraints;
+ stmt->partbound = cxt.partbound;
result = lappend(cxt.blist, stmt);
result = list_concat(result, cxt.alist);
@@ -894,6 +906,7 @@ transformTableLikeClause(CreateStmtContext *cxt, TableLikeClause *table_like_cla
def->is_local = true;
def->is_not_null = attribute->attnotnull;
def->is_from_type = false;
+ def->is_for_partition = false;
def->storage = 0;
def->raw_default = NULL;
def->cooked_default = NULL;
@@ -1113,6 +1126,7 @@ transformOfType(CreateStmtContext *cxt, TypeName *ofTypename)
n->is_local = true;
n->is_not_null = false;
n->is_from_type = true;
+ n->is_for_partition = false;
n->storage = 0;
n->raw_default = NULL;
n->cooked_default = NULL;
@@ -2580,6 +2594,7 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
cxt.alist = NIL;
cxt.pkey = NULL;
cxt.ispartitioned = relid_is_partitioned(relid);
+ cxt.partbound = NULL;
/*
* The only subtypes that currently require parse transformation handling
@@ -2668,6 +2683,22 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
break;
}
+ case AT_AttachPartition:
+ {
+ PartitionCmd *partcmd = (PartitionCmd *) cmd->def;
+
+ transformAttachPartition(&cxt, partcmd);
+
+ /* assign transformed values */
+ partcmd->bound = cxt.partbound;
+ }
+
+ newcmds = lappend(newcmds, cmd);
+ break;
+ case AT_DetachPartition:
+ newcmds = lappend(newcmds, cmd);
+ break;
+
default:
newcmds = lappend(newcmds, cmd);
break;
@@ -3032,3 +3063,290 @@ setSchemaName(char *context_schema, char **stmt_schema_name)
"different from the one being created (%s)",
*stmt_schema_name, context_schema)));
}
+
+/*
+ * transformPartitionOf
+ * Analyze PARTITION OF ... FOR VALUES ...
+ */
+static void
+transformPartitionOf(CreateStmtContext *cxt, Node *bound)
+{
+ TupleDesc tupdesc;
+ int i;
+ Relation parentRel;
+ RangeVar *partof = linitial(cxt->inhRelations);
+
+ parentRel = heap_openrv(partof, AccessShareLock);
+
+ /* Check if the target table is partitioned at all */
+ if (parentRel->rd_rel->relkind != RELKIND_PARTITIONED_REL)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("\"%s\" is not partitioned",
+ RelationGetRelationName(parentRel))));
+ Assert(parentRel->rd_partkey != NULL);
+
+ /* If parent rel is temp, it must belong to this session */
+ if (parentRel->rd_rel->relpersistence == RELPERSISTENCE_TEMP &&
+ !parentRel->rd_islocaltemp)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot create as partition of temporary relation of another session")));
+
+ /*
+ * Do not allow OIDs in a partition, if not present in the parent. But
+ * force them in partition, if they are present in the parent.
+ */
+ if (cxt->hasoids && !parentRel->rd_rel->relhasoids)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot create table with OIDs as partition of table without OIDs")));
+
+ if (parentRel->rd_rel->relhasoids)
+ cxt->hasoids = true;
+
+ tupdesc = RelationGetDescr(parentRel);
+ for (i = 0; i < tupdesc->natts; i++)
+ {
+ Form_pg_attribute attribute = tupdesc->attrs[i];
+ ColumnDef *def;
+
+ if (attribute->attisdropped)
+ continue;
+
+ def = makeNode(ColumnDef);
+ def->colname = pstrdup(NameStr(attribute->attname));
+ def->typeName = makeTypeNameFromOid(attribute->atttypid,
+ attribute->atttypmod);
+ def->inhcount = 1;
+ def->is_local = false;
+ def->is_not_null = attribute->attnotnull;
+ def->is_from_type = false;
+ def->is_for_partition = true;
+ def->storage = attribute->attstorage;
+ def->raw_default = NULL;
+ def->cooked_default = NULL;
+ def->collClause = NULL;
+ def->collOid = attribute->attcollation;
+ def->constraints = NIL;
+ def->location = -1;
+
+ cxt->columns = lappend(cxt->columns, def);
+ }
+
+ /* tranform the values */
+ cxt->partbound = transformPartitionBound(cxt, parentRel, bound);
+
+ heap_close(parentRel, AccessShareLock);
+}
+
+/*
+ * transformAttachPartition
+ * Analyze ATTACH PARTITION ... FOR VALUES ...
+ */
+static void
+transformAttachPartition(CreateStmtContext *cxt, PartitionCmd *cmd)
+{
+ Relation parentRel = cxt->rel;
+
+ /* Check if the target table is partitioned at all */
+ if (parentRel->rd_rel->relkind != RELKIND_PARTITIONED_REL)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("\"%s\" is not partitioned", cxt->relation->relname)));
+ Assert(parentRel->rd_partkey != NULL);
+
+ /* tranform the values */
+ cxt->partbound = transformPartitionBound(cxt, parentRel, cmd->bound);
+}
+
+/*
+ * transformPartitionBound
+ *
+ * Transform partition bound per the partition key
+ */
+static Node *
+transformPartitionBound(CreateStmtContext *cxt, Relation parent, Node *bound)
+{
+ int i;
+ ListCell *cell;
+ Oid parentId = RelationGetRelid(parent);
+ PartitionKey key = parent->rd_partkey;
+ PartitionListSpec *list, *result_list;
+ PartitionRangeSpec *range, *result_range;
+
+ switch (key->strategy)
+ {
+ case PARTITION_STRAT_LIST:
+ if (!IsA(bound, PartitionListSpec))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("invalid bound specification for a list partition"),
+ parser_errposition(cxt->pstate, exprLocation(bound))));
+
+ list = (PartitionListSpec *) transformExpr(cxt->pstate, bound,
+ EXPR_KIND_PARTITION_FOR_VALUES);
+
+ result_list = makeNode(PartitionListSpec);
+
+ foreach(cell, list->values)
+ {
+ Node *value = (Node *) lfirst(cell);
+ Node *orig_value = value;
+ Oid valuetype;
+
+ valuetype = exprType(value);
+ value = coerce_to_target_type(cxt->pstate,
+ value, valuetype,
+ key->tcinfo->typid[0],
+ key->tcinfo->typmod[0],
+ COERCION_ASSIGNMENT,
+ COERCE_IMPLICIT_CAST,
+ -1);
+ if (value == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("specified value cannot be cast to type \"%s\""
+ " of key column \"%s\"",
+ format_type_be(key->tcinfo->typid[0]),
+ get_relid_attribute_name(parentId, key->partattrs[0])),
+ parser_errposition(cxt->pstate, exprLocation(orig_value))));
+
+ /* Simplify the expression */
+ value = (Node *) expression_planner((Expr *) value);
+
+ result_list->values = lappend(result_list->values, value);
+ }
+ return (Node *) result_list;
+
+ case PARTITION_STRAT_RANGE:
+ if (!IsA(bound, PartitionRangeSpec))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("invalid bound specification for a range partition"),
+ parser_errposition(cxt->pstate, exprLocation(bound))));
+
+ range = (PartitionRangeSpec *) transformExpr(cxt->pstate, bound,
+ EXPR_KIND_PARTITION_FOR_VALUES);
+
+ if (!range->lower && !range->upper)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("both START and END cannot be UNBOUNDED"),
+ parser_errposition(cxt->pstate, range->location)));
+
+ result_range = makeNode(PartitionRangeSpec);
+ result_range->lowerinc = range->lowerinc;
+ result_range->upperinc = range->upperinc;
+
+ if (range->lower)
+ {
+ if (list_length(range->lower) > key->partnatts)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("START has more values specified than number"
+ " of columns in the partition key"),
+ parser_errposition(cxt->pstate,
+ exprLocation(list_nth(range->lower,
+ list_length(range->lower) - 1)))));
+ else if (list_length(range->lower) < key->partnatts)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("START has fewer values specified than number"
+ " of columns in the partition key"),
+ parser_errposition(cxt->pstate,
+ exprLocation(list_nth(range->lower,
+ list_length(range->lower) - 1)))));
+
+ i = 0;
+ foreach (cell, range->lower)
+ {
+ Node *value = (Node *) lfirst(cell);
+ Node *orig_value = value;
+ Oid valuetype;
+
+ valuetype = exprType(value);
+ value = coerce_to_target_type(cxt->pstate,
+ value, valuetype,
+ key->tcinfo->typid[i],
+ key->tcinfo->typmod[i],
+ COERCION_ASSIGNMENT,
+ COERCE_IMPLICIT_CAST,
+ -1);
+ if (value == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("specified value cannot be cast to type"
+ " \"%s\" of key column \"%s\"",
+ format_type_be(key->tcinfo->typid[i]),
+ get_relid_attribute_name(parentId, key->partattrs[i])),
+ parser_errposition(cxt->pstate, exprLocation(orig_value))));
+
+ /* Simplify the expression */
+ value = (Node *) expression_planner((Expr *) value);
+
+ result_range->lower = lappend(result_range->lower, value);
+ ++i;
+ }
+ }
+ else
+ result_range->lowerinc = false;
+
+ if (range->upper)
+ {
+ if (list_length(range->upper) > key->partnatts)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("END has more values specified than number"
+ " of columns in the partition key"),
+ parser_errposition(cxt->pstate,
+ exprLocation(list_nth(range->upper,
+ list_length(range->upper) - 1)))));
+ else if (list_length(range->upper) < key->partnatts)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("END has fewer values specified than number"
+ " of columns in the partition key"),
+ parser_errposition(cxt->pstate,
+ exprLocation(list_nth(range->upper,
+ list_length(range->upper) - 1)))));
+
+ i = 0;
+ foreach (cell, range->upper)
+ {
+ Node *value = (Node *) lfirst(cell);
+ Node *orig_value = value;
+ Oid valuetype;
+
+ valuetype = exprType(value);
+ value = coerce_to_target_type(cxt->pstate,
+ value, valuetype,
+ key->tcinfo->typid[i],
+ key->tcinfo->typmod[i],
+ COERCION_ASSIGNMENT,
+ COERCE_IMPLICIT_CAST,
+ -1);
+ if (value == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("specified value cannot be cast to type"
+ " \"%s\" of key column \"%s\"",
+ format_type_be(key->tcinfo->typid[i]),
+ get_relid_attribute_name(parentId, key->partattrs[i])),
+ parser_errposition(cxt->pstate, exprLocation(orig_value))));
+
+ /* Simplify the expression */
+ value = (Node *) expression_planner((Expr *) value);
+
+ result_range->upper = lappend(result_range->upper, value);
+ ++i;
+ }
+ }
+ else
+ result_range->upperinc = false;
+
+ return (Node *) result_range;
+ }
+
+ return NULL;
+}
diff --git a/src/include/catalog/partition.h b/src/include/catalog/partition.h
index 2c876c4..31dbe17 100644
--- a/src/include/catalog/partition.h
+++ b/src/include/catalog/partition.h
@@ -14,6 +14,7 @@
#define PARTITION_H
#include "fmgr.h"
+#include "parser/parse_node.h"
#include "utils/relcache.h"
/* Type and collation information for partition key columns */
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 9e582a5..56bbe43 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -405,6 +405,7 @@ typedef enum NodeTag
T_AlterPolicyStmt,
T_CreateTransformStmt,
T_CreateAmStmt,
+ T_PartitionCmd,
/*
* TAGS FOR PARSE TREE NODES (parsenodes.h)
@@ -454,6 +455,8 @@ typedef enum NodeTag
T_RoleSpec,
T_PartitionElem,
T_PartitionBy,
+ T_PartitionListSpec,
+ T_PartitionRangeSpec,
/*
* TAGS FOR REPLICATION GRAMMAR PARSE NODES (replnodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 4f697b8..d9dff96 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -591,6 +591,7 @@ typedef struct ColumnDef
bool is_local; /* column has local (non-inherited) def'n */
bool is_not_null; /* NOT NULL constraint specified? */
bool is_from_type; /* column definition came from table type */
+ bool is_for_partition; /* column definition is for a partition */
char storage; /* attstorage setting, or 0 for default */
Node *raw_default; /* default value (untransformed parse tree) */
Node *cooked_default; /* default value (transformed expr tree) */
@@ -731,6 +732,40 @@ typedef struct PartitionBy
int location; /* token location, or -1 if unknown */
} PartitionBy;
+/*
+ * PartitionRangeSpec - range type syntax for range partition bound
+ */
+typedef struct PartitionListSpec
+{
+ NodeTag type;
+ List *values;
+ int location;
+} PartitionListSpec;
+
+/*
+ * PartitionRangeSpec - range type syntax for range partition bound
+ */
+typedef struct PartitionRangeSpec
+{
+ NodeTag type;
+ bool lowerinc;
+ List *lower;
+ bool upperinc;
+ List *upper;
+ int location; /* token location, or -1 if unknown */
+} PartitionRangeSpec;
+
+/*
+ * PartitionCmd - ALTER TABLE partition commands
+ */
+typedef struct PartitionCmd
+{
+ NodeTag type;
+ RangeVar *name;
+ Node *bound;
+ bool skip_validate;
+} PartitionCmd;
+
/****************************************************************************
* Nodes for a Query tree
****************************************************************************/
@@ -1558,7 +1593,9 @@ typedef enum AlterTableType
AT_DisableRowSecurity, /* DISABLE ROW SECURITY */
AT_ForceRowSecurity, /* FORCE ROW SECURITY */
AT_NoForceRowSecurity, /* NO FORCE ROW SECURITY */
- AT_GenericOptions /* OPTIONS (...) */
+ AT_GenericOptions, /* OPTIONS (...) */
+ AT_AttachPartition, /* ATTACH PARTITION */
+ AT_DetachPartition /* DETACH PARTITION */
} AlterTableType;
typedef struct ReplicaIdentityStmt
@@ -1783,7 +1820,9 @@ typedef struct CreateStmt
RangeVar *relation; /* relation to create */
List *tableElts; /* column definitions (list of ColumnDef) */
List *inhRelations; /* relations to inherit from (list of
- * inhRelation) */
+ * inhRelation); (ab)used also as partition
+ * parent */
+ Node *partbound; /* FOR VALUES clause */
PartitionBy *partby; /* PARTITION BY clause */
TypeName *ofTypename; /* OF typename */
List *constraints; /* constraints (list of Constraint nodes) */
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 40da67a..70c264c 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -49,6 +49,7 @@ PG_KEYWORD("assertion", ASSERTION, UNRESERVED_KEYWORD)
PG_KEYWORD("assignment", ASSIGNMENT, UNRESERVED_KEYWORD)
PG_KEYWORD("asymmetric", ASYMMETRIC, RESERVED_KEYWORD)
PG_KEYWORD("at", AT, UNRESERVED_KEYWORD)
+PG_KEYWORD("attach", ATTACH, UNRESERVED_KEYWORD)
PG_KEYWORD("attribute", ATTRIBUTE, UNRESERVED_KEYWORD)
PG_KEYWORD("authorization", AUTHORIZATION, TYPE_FUNC_NAME_KEYWORD)
PG_KEYWORD("backward", BACKWARD, UNRESERVED_KEYWORD)
@@ -127,6 +128,7 @@ PG_KEYWORD("delimiter", DELIMITER, UNRESERVED_KEYWORD)
PG_KEYWORD("delimiters", DELIMITERS, UNRESERVED_KEYWORD)
PG_KEYWORD("depends", DEPENDS, UNRESERVED_KEYWORD)
PG_KEYWORD("desc", DESC, RESERVED_KEYWORD)
+PG_KEYWORD("detach", DETACH, UNRESERVED_KEYWORD)
PG_KEYWORD("dictionary", DICTIONARY, UNRESERVED_KEYWORD)
PG_KEYWORD("disable", DISABLE_P, UNRESERVED_KEYWORD)
PG_KEYWORD("discard", DISCARD, UNRESERVED_KEYWORD)
@@ -191,6 +193,7 @@ PG_KEYWORD("implicit", IMPLICIT_P, UNRESERVED_KEYWORD)
PG_KEYWORD("import", IMPORT_P, UNRESERVED_KEYWORD)
PG_KEYWORD("in", IN_P, RESERVED_KEYWORD)
PG_KEYWORD("including", INCLUDING, UNRESERVED_KEYWORD)
+PG_KEYWORD("inclusive", INCLUSIVE, UNRESERVED_KEYWORD)
PG_KEYWORD("increment", INCREMENT, UNRESERVED_KEYWORD)
PG_KEYWORD("index", INDEX, UNRESERVED_KEYWORD)
PG_KEYWORD("indexes", INDEXES, UNRESERVED_KEYWORD)
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index a13c6fb..3d45663 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -65,7 +65,8 @@ typedef enum ParseExprKind
EXPR_KIND_EXECUTE_PARAMETER, /* parameter value in EXECUTE */
EXPR_KIND_TRIGGER_WHEN, /* WHEN condition in CREATE TRIGGER */
EXPR_KIND_POLICY, /* USING or WITH CHECK expr in policy */
- EXPR_KIND_PARTITION_KEY /* partition key expression */
+ EXPR_KIND_PARTITION_KEY, /* partition key expression */
+ EXPR_KIND_PARTITION_FOR_VALUES /* partition bound */
} ParseExprKind;
--
1.7.1
[text/x-diff] 0004-Infrastructure-for-partition-metadata-storage-and-ma-v3.patch (98.6K, ../../57183572.8020900@lab.ntt.co.jp/5-0004-Infrastructure-for-partition-metadata-storage-and-ma-v3.patch)
download | inline diff:
From d18ae125f6b6988b109a6e174503e8dacc6c1866 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Fri, 18 Mar 2016 15:54:17 +0900
Subject: [PATCH 4/5] Infrastructure for partition metadata storage and manipulation.
Catalog pg_partition with:
partrelid oid
partbound pg_node_tree
Now since partition metadata is stored in a dedicated catalog, it is possible
to use that knowledge to enforce special behaviors for inheritance children
(or relations in general) that are partitions. Some such behaviors include:
* Cannot drop using DROP TABLE without first "detaching" from its parent
* Cannot add, drop new columns
* Cannot rename, change type of existing columns
It also becomes now possible to check, from the FOR VALUES specification, that
the new partition (in CREATE TABLE PARTITION OF parent or ALTER TABLE parent
ATTACH PARTITION) does not overlap with any existing partition of parent.
One can get a partition's check expression (that is, an expression tree that
can be used as a check constraint for the rows in it). ExecConstraints() now
checks using that expression, whether a row being inserted or new row value in
update case, violate the partition boundary specification. Also, optimizer's
constraint exclusion uses it remove useless partitions. This expression tree
is cached in the partition's relcache.
When attaching a partition, rows in the table are checked whether any violates
the partition bound specification (AND'ed with the parent's check expression,
if parent itself is a partition), unless NO VALIDATE clause is specified with
the command.
This commit also invents an internal representation of partition bounds in
the form of structs PartitionListBound and PartitionRangeBound, which consist
of datums and information like number of values in a list, booleans for
whether a range bound is infinite, inclusive (vs. exclusive), lower bound
(vs. upper bound), etc. One can get "partition descriptor" for a partitioned
table, which comes in the form of a struct PartitionDesc. It consists of
of an array of oids of partitions, array of pointers to PartitionListBound
structs (NULL if not list partitioned, and 2 arrays of pointers to
PartitionRangeBound structs, (each NULL, if not range partitioned).
A pointer to this structure is also cached in relcache.
One more data structure that this commit invents for future use is struct
PartitionDescNode. It represents a node in partition tree where each node
of the tree corresponds to a partitioned table in the tree. The tree
structure is such that a parent node links to the first child node (which
corresponds with the first partition of the parent that is partitioned
itself), which in turn, links to its right sibling (the next partition
that is partitioned) and so on. Each node consists of some book-keeping
information including a pointer to PartitionDesc of the corresponding table
and recursive pointers just mentioned viz. downlink and next.
Support to route tuples inserted into a partitioned parent to the appropriate
leaf partition will be added shortly which leverages PartitionDescNode.
---
doc/src/sgml/catalogs.sgml | 56 +
src/backend/catalog/Makefile | 2 +-
src/backend/catalog/heap.c | 6 +
src/backend/catalog/partition.c | 1531 ++++++++++++++++++++++++++++
src/backend/commands/copy.c | 2 +-
src/backend/commands/lockcmds.c | 4 +-
src/backend/commands/tablecmds.c | 210 ++++-
src/backend/executor/execMain.c | 75 ++-
src/backend/executor/nodeModifyTable.c | 4 +-
src/backend/nodes/readfuncs.c | 33 +
src/backend/optimizer/util/plancat.c | 13 +
src/backend/parser/analyze.c | 1 +
src/backend/parser/parse_clause.c | 4 +-
src/backend/parser/parse_relation.c | 3 +-
src/backend/parser/parse_utilcmd.c | 6 +
src/backend/utils/adt/ruleutils.c | 78 ++
src/backend/utils/cache/relcache.c | 7 +
src/backend/utils/cache/syscache.c | 12 +
src/bin/pg_dump/common.c | 86 ++
src/bin/pg_dump/pg_dump.c | 98 ++-
src/bin/pg_dump/pg_dump.h | 12 +
src/bin/psql/describe.c | 46 +-
src/include/catalog/indexing.h | 3 +
src/include/catalog/partition.h | 57 +
src/include/catalog/pg_partition.h | 54 +
src/include/nodes/execnodes.h | 4 +
src/include/utils/rel.h | 4 +
src/include/utils/syscache.h | 1 +
src/test/regress/expected/sanity_check.out | 1 +
29 files changed, 2377 insertions(+), 36 deletions(-)
create mode 100644 src/include/catalog/pg_partition.h
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index ba658b1..089fa5b 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -224,6 +224,11 @@
</row>
<row>
+ <entry><link linkend="catalog-pg-partition"><structname>pg_partition</structname></link></entry>
+ <entry>information about partitions</entry>
+ </row>
+
+ <row>
<entry><link linkend="catalog-pg-partitioned"><structname>pg_partitioned</structname></link></entry>
<entry>information about partitioned tables, including the partition key</entry>
</row>
@@ -4687,6 +4692,57 @@
</sect1>
+ <sect1 id="catalog-pg-partition">
+ <title><structname>pg_partition</structname></title>
+
+ <indexterm zone="catalog-pg-partition">
+ <primary>pg_partition</primary>
+ </indexterm>
+
+ <para>
+ The catalog <structname>pg_partition</structname> stores partition bounds
+ of tables that are partitions.
+ </para>
+
+ <table>
+ <title><structname>pg_partition</> Columns</title>
+
+ <tgroup cols="4">
+ <thead>
+ <row>
+ <entry>Name</entry>
+ <entry>Type</entry>
+ <entry>References</entry>
+ <entry>Description</entry>
+ </row>
+ </thead>
+
+ <tbody>
+
+ <row>
+ <entry><structfield>partrelid</structfield></entry>
+ <entry><type>oid</type></entry>
+ <entry><literal><link linkend="catalog-pg-class"><structname>pg_class</structname></link>.oid</literal></entry>
+ <entry>The OID of the <structname>pg_class</> entry for this partition</entry>
+ </row>
+
+ <row>
+ <entry><structfield>partbound</structfield></entry>
+ <entry><type>pg_node_tree</type></entry>
+ <entry></entry>
+ <entry>
+ Expression tree (in <function>nodeToString()</function> representation)
+ for partition boundary value. For list partitions, it is simply a list
+ of values. For range partitions, it consists of lower and upper bound
+ values along with their inclusivity.
+ </entry>
+ </row>
+
+ </tbody>
+ </tgroup>
+ </table>
+ </sect1>
+
<sect1 id="catalog-pg-partitioned">
<title><structname>pg_partitioned</structname></title>
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index 4dc6300..ee1be31 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -41,7 +41,7 @@ POSTGRES_BKI_SRCS = $(addprefix $(top_srcdir)/src/include/catalog/,\
pg_foreign_data_wrapper.h pg_foreign_server.h pg_user_mapping.h \
pg_foreign_table.h pg_policy.h pg_replication_origin.h \
pg_default_acl.h pg_init_privs.h pg_seclabel.h pg_shseclabel.h \
- pg_collation.h pg_range.h pg_transform.h pg_partitioned.h\
+ pg_collation.h pg_range.h pg_transform.h pg_partitioned.h pg_partition.h\
toasting.h indexing.h \
)
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 932a48b..2e99661 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -1808,7 +1808,13 @@ heap_drop_with_catalog(Oid relid)
* If a partitioned table, delete the pg_partitioned tuples.
*/
if (rel->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+ {
RemovePartitionKeyByRelId(relid);
+ RelationDropPartitions(relid);
+ }
+
+ if (relid_is_partition(relid))
+ RemovePartitionEntryByRelId(relid);
/*
* Schedule unlinking of the relation's physical files at commit.
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index 5e238dc..46bbb9e 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -18,19 +18,26 @@
#include "access/heapam.h"
#include "access/htup_details.h"
#include "access/nbtree.h"
+#include "access/sysattr.h"
#include "catalog/dependency.h"
#include "catalog/indexing.h"
#include "catalog/objectaddress.h"
#include "catalog/partition.h"
#include "catalog/pg_collation.h"
+#include "catalog/pg_inherits.h"
+#include "catalog/pg_inherits_fn.h"
#include "catalog/pg_opclass.h"
#include "catalog/pg_partitioned.h"
+#include "catalog/pg_partition.h"
#include "catalog/pg_type.h"
#include "executor/executor.h"
#include "miscadmin.h"
+#include "nodes/makefuncs.h"
#include "nodes/nodeFuncs.h"
+#include "nodes/parsenodes.h"
#include "optimizer/clauses.h"
#include "optimizer/planmain.h"
+#include "optimizer/var.h"
#include "storage/lmgr.h"
#include "utils/array.h"
#include "utils/builtins.h"
@@ -41,10 +48,88 @@
#include "utils/lsyscache.h"
#include "utils/syscache.h"
+/*
+ * Information about a single partition
+ */
+typedef struct PartitionInfo
+{
+ Oid oid; /* partition OID */
+ PartitionListBound *list; /* list partition bound */
+ PartitionRangeBound *rangelower; /* range partition lower bound */
+ PartitionRangeBound *rangeupper; /* range partition upper bound */
+} PartitionInfo;
+
+typedef struct translate_var_attno_mutator_context
+{
+ AttrNumber old_attno;
+ AttrNumber new_attno;
+} translate_var_attno_mutator_context;
+
+/*
+ * Information about partitions of a partitioned table.
+ */
+typedef struct PartitionDescData
+{
+ int nparts; /* number of partitions */
+ Oid *oids; /* OIDs of the partitions */
+ PartitionListBound **lists; /* list bounds */
+ PartitionRangeBound **rangelowers; /* range lower bounds */
+ PartitionRangeBound **rangeuppers; /* range upper bounds */
+} PartitionDescData;
+
static PartitionKey CopyPartitionKey(PartitionKey fromkey);
static KeyTypeCollInfo *copy_key_type_coll_info(int nkeycols,
KeyTypeCollInfo *tcinfo);
static void free_key_type_coll_info(KeyTypeCollInfo *tcinfo);
+static bool list_overlaps_existing_partition(PartitionKey key,
+ PartitionListSpec *list_spec,
+ PartitionDesc pdesc,
+ Oid *with);
+static bool partition_range_empty(PartitionKey key,
+ PartitionRangeSpec *range_spec);
+static int32 compare_range_keys(PartitionKey key, Datum *val1, Datum *val2);
+static bool range_overlaps_existing_partition(PartitionKey key,
+ PartitionRangeSpec *range_spec,
+ PartitionDesc pdesc,
+ Oid *with);
+static PartitionListBound *make_list_from_spec(PartitionKey key, List *values);
+static PartitionRangeBound *make_range_from_spec(PartitionKey key, List *val,
+ bool inclusive, bool lower);
+static bool partition_range_overlaps(PartitionKey key,
+ PartitionRangeBound *part1_lower,
+ PartitionRangeBound *part1_upper,
+ PartitionRangeBound *part2_lower,
+ PartitionRangeBound *part2_upper);
+static int32 partition_range_cmp_bounds(PartitionKey key,
+ PartitionRangeBound *b1,
+ PartitionRangeBound *b2);
+
+static PartitionDesc RelationGetPartitionDesc(Relation relation);
+static PartitionDesc GetPartitionDescCopy(PartitionKey key, PartitionDesc src);
+static PartitionInfo **GetRelPartitions(Relation rel, int *nparts);
+static List *get_partitions(Oid relid, int lockmode);
+static int32 range_partition_cmp_upper(const void *a, const void *b,
+ void *arg);
+static PartitionListBound *copyPartitionListBound(PartitionListBound *src,
+ PartitionKey key);
+static PartitionRangeBound *copyPartitionRangeBound(PartitionRangeBound *src,
+ PartitionKey key);
+static void free_partition_info_array(PartitionInfo **p, int num);
+
+static Expr *get_check_expr_for_list(PartitionKey key, List *values);
+static Expr *get_check_expr_for_range(PartitionKey key,
+ List *lowervalues,
+ bool lowerinc,
+ List *uppervalues,
+ bool upperinc);
+static Node *translate_var_attno(Node *expr, AttrNumber attno,
+ AttrNumber new_attno);
+static Node *translate_var_attno_mutator(Node *node,
+ translate_var_attno_mutator_context *cxt);
+static Node *generate_partition_check_expr(Relation rel);
+
+static PartitionDescNode GetPartitionDescNodeRecurse(Relation rel, int offset);
+static int get_leaf_partition_count(PartitionDescNode pdnode);
/*
* StorePartitionKey
@@ -449,3 +534,1449 @@ FreePartitionKey(PartitionKey key)
free_key_type_coll_info(key->tcinfo);
pfree(key);
}
+
+/*
+ * StorePartitionBound
+ *
+ * Store partition boundary info in pg_partition after checking for its
+ * validity.
+ */
+void
+StorePartitionBound(Oid relid, Oid parentId, Node *bound)
+{
+ Relation pg_partition;
+ Relation parent;
+ PartitionKey key;
+ PartitionDesc pdesc;
+ Oid with;
+ HeapTuple tuple;
+ char *boundString;
+ Datum values[Natts_pg_partition];
+ bool nulls[Natts_pg_partition];
+ ParseState *pstate = make_parsestate(NULL);
+
+ parent = heap_open(parentId, AccessShareLock);
+ key = parent->rd_partkey;
+ pdesc = RelationGetPartitionDesc(parent);
+
+ /*
+ * Check if 'bound' happens to overlap with any of parent's existing
+ * partitions.
+ */
+ switch (key->strategy)
+ {
+ case PARTITION_STRAT_LIST:
+ {
+ PartitionListSpec *list;
+
+ Assert(IsA(bound, PartitionListSpec));
+ list = (PartitionListSpec *) bound;
+ if (list_overlaps_existing_partition(key, list, pdesc, &with))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("new partition's list of values overlaps with that"
+ " of partition \"%s\" of \"%s\"",
+ get_rel_name(with), get_rel_name(parentId)),
+ errhint("Please specify a list of values that does not"
+ " overlap with any existing list partition's list."),
+ parser_errposition(pstate, list->location)));
+ break;
+ }
+
+ case PARTITION_STRAT_RANGE:
+ {
+ PartitionRangeSpec *range;
+
+ Assert(IsA(bound, PartitionRangeSpec));
+ range = (PartitionRangeSpec *) bound;
+ if (partition_range_empty(key, range))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("new partition's range contains no element"),
+ errhint("Please specify a non-empty range."),
+ parser_errposition(pstate, range->location)));
+
+ if (range_overlaps_existing_partition(key, range, pdesc, &with))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("new partition's range overlaps with that of"
+ " partition \"%s\" of \"%s\"",
+ get_rel_name(with), get_rel_name(parentId)),
+ errhint("Please specify a range that does not overlap"
+ " with any existing partition's range."),
+ parser_errposition(pstate, range->location)));
+ break;
+ }
+ }
+
+ pg_partition = heap_open(PartitionRelationId, RowExclusiveLock);
+
+ boundString = nodeToString(bound);
+
+ /* Build the tuple - bound can never be null */
+ MemSet(nulls, false, sizeof(nulls));
+
+ values[Anum_pg_partition_partrelid - 1] = ObjectIdGetDatum(relid);
+ values[Anum_pg_partition_partbound - 1] = CStringGetTextDatum(boundString);
+
+ tuple = heap_form_tuple(RelationGetDescr(pg_partition), values, nulls);
+
+ simple_heap_insert(pg_partition, tuple);
+
+ /* Update the indexes on pg_partition */
+ CatalogUpdateIndexes(pg_partition, tuple);
+
+ /*
+ * Invalid the parent's relcache so that the partition is now included in
+ * its partition descriptor.
+ */
+ CacheInvalidateRelcache(parent);
+
+ heap_close(parent, AccessShareLock);
+ heap_close(pg_partition, RowExclusiveLock);
+}
+
+/*
+ * Does new list partition 'bound' overlap any of existing list partitions?
+ */
+static bool
+list_overlaps_existing_partition(PartitionKey key,
+ PartitionListSpec *list_spec,
+ PartitionDesc pdesc,
+ Oid *with)
+{
+ PartitionListBound *new_list;
+ int i;
+
+ if (pdesc->nparts == 0)
+ return false;
+
+ new_list = make_list_from_spec(key, list_spec->values);
+
+ for (i = 0; i < pdesc->nparts; i++)
+ {
+ int j;
+
+ if (pdesc->lists[i]->contains_null && new_list->contains_null)
+ return true;
+
+ for (j = 0; j < pdesc->lists[i]->nvalues; j++)
+ {
+ int k;
+
+ for (k = 0; k < new_list->nvalues; k++)
+ {
+ int32 result;
+
+ result = DatumGetInt32(FunctionCall2Coll(&key->partsupfunc[0],
+ key->tcinfo->typcoll[0],
+ pdesc->lists[i]->values[j],
+ new_list->values[k]));
+ if (!result)
+ {
+ *with = pdesc->oids[i];
+ return true;
+ }
+ }
+ }
+ }
+
+ return false;
+}
+
+/*
+ * Is new partition's range empty?
+ */
+static bool
+partition_range_empty(PartitionKey key, PartitionRangeSpec *range_spec)
+{
+ PartitionRangeBound *lower,
+ *upper;
+
+ lower = make_range_from_spec(key, range_spec->lower, range_spec->lowerinc,
+ true);
+ upper = make_range_from_spec(key, range_spec->upper, range_spec->upperinc,
+ false);
+
+ /*
+ * Only either lower or upper is allowed to be unbounded, which if
+ * one of them is, the range is not empty.
+ */
+ if (lower->infinite || upper->infinite)
+ return false;
+
+ /*
+ * If upper > lower, ouright empty or if lower = upper and either of
+ * them is exclusive.
+ */
+ if (compare_range_keys(key, upper->val, lower->val) < 0 ||
+ (compare_range_keys(key, lower->val, upper->val) == 0 &&
+ (!lower->inclusive || !upper->inclusive)))
+ return true;
+
+ return false;
+}
+
+/* Compare range composite keys using logic similar to RowCompareExpr */
+static int32
+compare_range_keys(PartitionKey key, Datum *val1, Datum *val2)
+{
+ int32 result;
+ int i;
+
+ for (i = 0; i < key->partnatts; i++)
+ {
+ result = DatumGetInt32(FunctionCall2Coll(&key->partsupfunc[i],
+ key->tcinfo->typcoll[i],
+ val1[i], val2[i]));
+ if (!result)
+ continue;
+ break;
+ }
+
+ return result;
+}
+
+/*
+ * Does new range partition's range overlap any of existing range partitions?
+ */
+static bool
+range_overlaps_existing_partition(PartitionKey key,
+ PartitionRangeSpec *range_spec,
+ PartitionDesc pdesc,
+ Oid *with)
+{
+ int i;
+ PartitionRangeBound *new_lower,
+ *new_upper;
+
+ if (pdesc->nparts == 0)
+ return false;
+
+ /*
+ * Create internal representation of rangelower and rangeupper from
+ * range_spec.
+ */
+ new_lower = make_range_from_spec(key, range_spec->lower, range_spec->lowerinc,
+ true);
+ new_upper = make_range_from_spec(key, range_spec->upper, range_spec->upperinc,
+ false);
+
+ /* Check with existing partitions for overlap */
+ for (i = 0; i < pdesc->nparts; i++)
+ {
+ if (partition_range_overlaps(key, new_lower, new_upper,
+ pdesc->rangelowers[i],
+ pdesc->rangeuppers[i]))
+ {
+ *with = pdesc->oids[i];
+ return true;
+ }
+ }
+
+ return false;
+}
+
+/*
+ * Make a PartitionListBound from list of values using the information in key
+ */
+static PartitionListBound *
+make_list_from_spec(PartitionKey key, List *values)
+{
+ PartitionListBound *list;
+ ListCell *cell;
+ int i;
+
+ list = (PartitionListBound *) palloc0(sizeof(PartitionListBound));
+ list->nvalues = list_length(values);
+ list->values = (Datum *) palloc(list->nvalues * sizeof(Datum));
+
+ i = 0;
+ foreach (cell, values)
+ {
+ Const *val = lfirst(cell);
+
+ if (val->constisnull)
+ list->contains_null = true;
+ else
+ list->values[i] = datumCopy(val->constvalue,
+ key->tcinfo->typbyval[0],
+ key->tcinfo->typlen[0]);
+ i++;
+ }
+
+ return list;
+}
+
+/*
+ * Make a PartitionRangeBound from arguments using the information in key
+ */
+static PartitionRangeBound *
+make_range_from_spec(PartitionKey key, List *val, bool inclusive, bool lower)
+{
+ PartitionRangeBound *range;
+ ListCell *cell;
+
+ range = (PartitionRangeBound *) palloc0(sizeof(PartitionRangeBound));
+ range->infinite = (val == NIL);
+ range->inclusive = inclusive;
+ range->lower = lower;
+
+ if (val)
+ {
+ int i;
+
+ range->val = (Datum *) palloc0(key->partnatts * sizeof(Datum));
+
+ i = 0;
+ foreach (cell, val)
+ {
+ Const *val = lfirst(cell);
+
+ if (val->constisnull)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("cannot specify NULL in range bound")));
+ else
+ range->val[i] = datumCopy(val->constvalue,
+ key->tcinfo->typbyval[i],
+ key->tcinfo->typlen[i]);
+ i++;
+ }
+ }
+
+ return range;
+}
+
+/* Check two range partitions for overlap */
+static bool
+partition_range_overlaps(PartitionKey key,
+ PartitionRangeBound *part1_lower,
+ PartitionRangeBound *part1_upper,
+ PartitionRangeBound *part2_lower,
+ PartitionRangeBound *part2_upper)
+{
+ if (partition_range_cmp_bounds(key, part1_lower, part2_lower) >= 0 &&
+ partition_range_cmp_bounds(key, part1_lower, part2_upper) <= 0)
+ return true;
+
+ if (partition_range_cmp_bounds(key, part2_lower, part1_lower) >= 0 &&
+ partition_range_cmp_bounds(key, part2_lower, part1_upper) <= 0)
+ return true;
+
+ return false;
+}
+
+/* Returns for two range partition bounds whether, b1 <=, =, >= b2 */
+static int32
+partition_range_cmp_bounds(PartitionKey key,
+ PartitionRangeBound *b1,
+ PartitionRangeBound *b2)
+{
+ int32 result;
+
+ /*
+ * First, handle cases involving infinity, which don't require invoking
+ * the comparison proc.
+ */
+ if (b1->infinite && b2->infinite)
+ {
+ /*
+ * Both are infinity, so they are equal unless one is lower and the
+ * other not.
+ */
+ if (b1->lower == b2->lower)
+ return 0;
+ else
+ return b1->lower ? -1 : 1;
+ }
+ else if (b1->infinite)
+ return b1->lower ? -1 : 1;
+ else if (b2->infinite)
+ return b2->lower ? 1 : -1;
+
+ /*
+ * Both boundaries are finite, so compare the held values.
+ */
+ result = compare_range_keys(key, b1->val, b2->val);
+
+ /*
+ * If the comparison is anything other than equal, we're done. If they
+ * compare equal though, we still have to consider whether the boundaries
+ * are inclusive or exclusive.
+ */
+ if (result == 0)
+ {
+ if (!b1->inclusive && !b2->inclusive)
+ {
+ /* both are exclusive */
+ if (b1->lower == b2->lower)
+ return 0;
+ else
+ return b1->lower ? 1 : -1;
+ }
+ else if (!b1->inclusive)
+ return b1->lower ? 1 : -1;
+ else if (!b2->inclusive)
+ return b2->lower ? -1 : 1;
+ else
+ {
+ /*
+ * Both are inclusive and the values held are equal, so they are
+ * equal regardless of whether they are upper or lower boundaries,
+ * or a mix.
+ */
+ return 0;
+ }
+ }
+
+ return result;
+}
+
+/*
+ * RemovePartitionByRelId
+ * Remove a pg_partition entry for a partition.
+ */
+void
+RemovePartitionEntryByRelId(Oid relid)
+{
+ Relation rel;
+ HeapTuple tuple;
+
+ rel = heap_open(PartitionRelationId, RowExclusiveLock);
+ tuple = SearchSysCache1(PARTRELID, ObjectIdGetDatum(relid));
+
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "cache lookup failed for partition entry of relation %u",
+ relid);
+
+ simple_heap_delete(rel, &tuple->t_self);
+
+ /* Update the indexes on pg_partitioned_rel */
+ CatalogUpdateIndexes(rel, tuple);
+
+ ReleaseSysCache(tuple);
+ heap_close(rel, RowExclusiveLock);
+}
+
+/*
+ * does relid have pg_partition entry?
+ */
+bool
+relid_is_partition(Oid relid)
+{
+ return SearchSysCacheExists1(PARTRELID, ObjectIdGetDatum(relid));
+}
+
+/*
+ * Returns inheritance parent of relid by scanning pg_inherits
+ */
+Oid
+get_partition_parent(Oid relid)
+{
+ Form_pg_inherits form;
+ Relation catalogRelation;
+ SysScanDesc scan;
+ ScanKeyData key[2];
+ HeapTuple tuple;
+
+ catalogRelation = heap_open(InheritsRelationId, AccessShareLock);
+
+ ScanKeyInit(&key[0],
+ Anum_pg_inherits_inhrelid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(relid));
+ ScanKeyInit(&key[1],
+ Anum_pg_inherits_inhseqno,
+ BTEqualStrategyNumber, F_INT4EQ,
+ Int32GetDatum(1));
+
+ scan = systable_beginscan(catalogRelation, InheritsRelidSeqnoIndexId, true,
+ NULL, 2, key);
+
+ tuple = systable_getnext(scan);
+ Assert(HeapTupleIsValid(tuple));
+
+ form = (Form_pg_inherits) GETSTRUCT(tuple);
+
+ systable_endscan(scan);
+ heap_close(catalogRelation, AccessShareLock);
+
+ return form->inhparent;
+}
+
+/*
+ * RelationDropPartitions
+ * Find and drop all partitions of relation with OID 'relid'
+ */
+void
+RelationDropPartitions(Oid relid)
+{
+ List *partitionOids;
+ ListCell *cell;
+
+ partitionOids = get_partitions(relid, AccessExclusiveLock);
+
+ foreach (cell, partitionOids)
+ {
+ Oid partitionOid = lfirst_oid(cell);
+ ObjectAddress obj;
+
+ obj.classId = RelationRelationId;
+ obj.objectId = partitionOid;
+ obj.objectSubId = 0;
+
+ performDeletion(&obj, DROP_CASCADE, 0);
+ }
+}
+
+/*
+ * RelationGetPartitionDesc
+ * Get the PartitionDesc of a partitioned table
+ *
+ * Memory for the struct is allocated in the caller's memory context while
+ * a copy is cached in rd_partdesc so that we don't compute the result
+ * unnecessarily (We don't want to return a pointer to the relcache copy
+ * though, since it could disappear due to relcache invalidation.)
+ *
+ * Never returns NULL. If there are no partitions yet, returns the struct
+ * with nparts set to 0.
+ */
+static PartitionDesc
+RelationGetPartitionDesc(Relation relation)
+{
+ int i,
+ nparts;
+ PartitionKey key = relation->rd_partkey;
+ MemoryContext oldcxt;
+ PartitionDesc result;
+ PartitionInfo **parts;
+
+ /* Quick copy and exit if already computed the descriptor */
+ if (relation->rd_partdesc != NULL)
+ return GetPartitionDescCopy(key, relation->rd_partdesc);
+
+ result = (PartitionDesc) palloc0(sizeof(PartitionDescData));
+
+ parts = GetRelPartitions(relation, &nparts);
+ result->nparts = nparts;
+
+ if (parts)
+ {
+ result->oids = (Oid *) palloc0(nparts * sizeof(Oid));
+
+ switch (key->strategy)
+ {
+ case PARTITION_STRAT_LIST:
+ result->lists = (PartitionListBound **) palloc0(nparts *
+ sizeof(PartitionListBound *));
+ break;
+
+ case PARTITION_STRAT_RANGE:
+ result->rangelowers = (PartitionRangeBound **) palloc0(nparts *
+ sizeof(PartitionRangeBound *));
+ result->rangeuppers = (PartitionRangeBound **) palloc0(nparts *
+ sizeof(PartitionRangeBound *));
+
+ /* Sort on range->upper */
+ qsort_arg(parts, nparts, sizeof(PartitionInfo *),
+ range_partition_cmp_upper, key);
+ break;
+ }
+
+ for (i = 0; i < nparts; i++)
+ {
+ result->oids[i] = parts[i]->oid;
+
+ if (parts[i]->list)
+ result->lists[i] = copyPartitionListBound(parts[i]->list, key);
+ else
+ {
+ result->rangelowers[i] = copyPartitionRangeBound(parts[i]->rangelower, key);
+ result->rangeuppers[i] = copyPartitionRangeBound(parts[i]->rangeupper, key);
+ }
+ }
+
+ /* Clean up */
+ free_partition_info_array(parts, nparts);
+ }
+
+ /* Save a copy in the relcache */
+ relation->rd_pdcxt = AllocSetContextCreate(CacheMemoryContext,
+ RelationGetRelationName(relation),
+ ALLOCSET_DEFAULT_MINSIZE,
+ ALLOCSET_DEFAULT_INITSIZE,
+ ALLOCSET_DEFAULT_MAXSIZE);
+
+ relation->rd_partdesc = MemoryContextAllocZero(relation->rd_pdcxt,
+ sizeof(PartitionDescData));
+ oldcxt = MemoryContextSwitchTo(relation->rd_pdcxt);
+ relation->rd_partdesc = GetPartitionDescCopy(key, result);
+ MemoryContextSwitchTo(oldcxt);
+
+ return result;
+}
+
+/*
+ * GetPartitionDescCopy
+ * Copy PartitionDesc of a partitioned relation into caller's context
+ *
+ * Like its caller, never returns NULL.
+ */
+static PartitionDesc
+GetPartitionDescCopy(PartitionKey key, PartitionDesc src)
+{
+ PartitionDesc result;
+ int i;
+
+ result = (PartitionDesc) palloc0(sizeof(PartitionDescData));
+ result->nparts = src->nparts;
+
+ /* If there are partitions, actually copy partitions */
+ if (src->nparts > 0)
+ {
+ result->oids = (Oid *) palloc0(result->nparts * sizeof(Oid));
+
+ switch (key->strategy)
+ {
+ case PARTITION_STRAT_LIST:
+ result->lists = (PartitionListBound **) palloc0(src->nparts *
+ sizeof(PartitionListBound *));
+ break;
+
+ case PARTITION_STRAT_RANGE:
+ result->rangelowers = (PartitionRangeBound **) palloc0(src->nparts *
+ sizeof(PartitionRangeBound *));
+ result->rangeuppers = (PartitionRangeBound **) palloc0(src->nparts *
+ sizeof(PartitionRangeBound *));
+ break;
+ }
+
+ for (i = 0; i < src->nparts; i++)
+ {
+ result->oids[i] = src->oids[i];
+
+ switch (key->strategy)
+ {
+ case PARTITION_STRAT_LIST:
+ result->lists[i] = copyPartitionListBound(src->lists[i], key);
+ break;
+
+ case PARTITION_STRAT_RANGE:
+ result->rangelowers[i] = copyPartitionRangeBound(src->rangelowers[i], key);
+ result->rangeuppers[i] = copyPartitionRangeBound(src->rangeuppers[i], key);
+ break;
+ }
+ }
+ }
+
+ return result;
+}
+
+/*
+ * GetRelPartitions
+ * Return information of partitions of rel
+ *
+ * Returns number of partitions in *nparts.
+ */
+static PartitionInfo **
+GetRelPartitions(Relation rel, int *nparts)
+{
+ int i;
+ Relation partrel;
+ List *partoids;
+ ListCell *cell;
+ PartitionInfo **parts;
+ PartitionKey key = rel->rd_partkey;
+
+ partoids = get_partitions(RelationGetRelid(rel), NoLock);
+
+ /* no partitions found */
+ if ((*nparts = list_length(partoids)) == 0)
+ return NULL;
+
+ partrel = heap_open(PartitionRelationId, AccessShareLock);
+
+ parts = (PartitionInfo **) palloc0(*nparts * sizeof(PartitionInfo *));
+
+ i = 0;
+ foreach(cell, partoids)
+ {
+ Oid partrelid = lfirst_oid(cell);
+ HeapTuple tuple;
+ Datum datum;
+ bool isnull;
+ Node *bound;
+
+ tuple = SearchSysCache1(PARTRELID, partrelid);
+ Assert(HeapTupleIsValid(tuple));
+
+ parts[i] = (PartitionInfo *) palloc0(sizeof(PartitionInfo));
+ parts[i]->oid = partrelid;
+
+ datum = fastgetattr(tuple,
+ Anum_pg_partition_partbound,
+ partrel->rd_att,
+ &isnull);
+
+ bound = stringToNode(TextDatumGetCString(datum));
+
+ switch (key->strategy)
+ {
+ case PARTITION_STRAT_LIST:
+ {
+ PartitionListSpec *list_spec;
+
+ Assert(IsA(bound, PartitionListSpec));
+ list_spec = (PartitionListSpec *) bound;
+ parts[i]->list = make_list_from_spec(key, list_spec->values);
+ }
+ break;
+
+ case PARTITION_STRAT_RANGE:
+ {
+ PartitionRangeSpec *range_spec;
+
+ Assert(IsA(bound, PartitionRangeSpec));
+ range_spec = (PartitionRangeSpec *) bound;
+
+ parts[i]->rangelower = make_range_from_spec(key,
+ range_spec->lower,
+ range_spec->lowerinc,
+ true);
+ parts[i]->rangeupper = make_range_from_spec(key,
+ range_spec->upper,
+ range_spec->upperinc,
+ false);
+ }
+ break;
+ }
+
+ ReleaseSysCache(tuple);
+ i++;
+ }
+
+ heap_close(partrel, AccessShareLock);
+
+ return parts;
+}
+
+/*
+ * Wrapper around find_inheritance_children for use by partitioning
+ * related code
+ */
+static List *
+get_partitions(Oid relid, int lockmode)
+{
+ return find_inheritance_children(relid, lockmode);
+}
+
+/*
+ * range_partition_cmp_upper
+ * Compare two range partition PartitionInfos using their rangeupper
+ * bounds
+ *
+ * Used as a callback to qsort_arg for sorting range partitions.
+ */
+static int32
+range_partition_cmp_upper(const void *a, const void *b, void *arg)
+{
+ PartitionRangeBound *b1 = (*(const PartitionInfo **) a)->rangeupper,
+ *b2 = (*(const PartitionInfo **) b)->rangeupper;
+
+ if (b1->infinite || b2->infinite)
+ return b1->infinite ? 1 : -1;
+
+ return compare_range_keys((PartitionKey) arg, b1->val, b2->val);
+}
+
+/*
+ * Helper routine to copy a list partition bound struct
+ */
+static PartitionListBound *
+copyPartitionListBound(PartitionListBound *src, PartitionKey key)
+{
+ int i;
+ PartitionListBound *result;
+
+ result = (PartitionListBound *) palloc0(sizeof(PartitionListBound));
+ result->nvalues = src->nvalues;
+ result->contains_null = src->contains_null;
+ result->values = (Datum *) palloc0(src->nvalues * sizeof(Datum));
+
+ for (i = 0; i < src->nvalues; i++)
+ result->values[i] = datumCopy(src->values[i],
+ key->tcinfo->typbyval[0],
+ key->tcinfo->typlen[0]);
+ return result;
+}
+
+/*
+ * Helper routine to copy a range partition bound struct
+ */
+static PartitionRangeBound *
+copyPartitionRangeBound(PartitionRangeBound *src, PartitionKey key)
+{
+ int i;
+ PartitionRangeBound *result;
+
+ result = (PartitionRangeBound *) palloc0(sizeof(PartitionRangeBound));
+ result->infinite = src->infinite;
+ result->inclusive = src->inclusive;
+ result->lower = src->lower;
+
+ if (src->val)
+ {
+ result->val = (Datum *) palloc0(key->partnatts * sizeof(Datum));
+ for (i = 0; i < key->partnatts; i++)
+ result->val[i] = datumCopy(src->val[i],
+ key->tcinfo->typbyval[i],
+ key->tcinfo->typlen[i]);
+ }
+
+ return result;
+}
+
+/*
+ * Helper routine to deep-free a PartitionInfo array
+ */
+static void
+free_partition_info_array(PartitionInfo **p, int num)
+{
+ int i;
+
+ for (i = 0; i < num; i++)
+ {
+ if (p[i]->list)
+ {
+ pfree(p[i]->list->values);
+ pfree(p[i]->list);
+ }
+
+ if (p[i]->rangelower)
+ {
+ if (p[i]->rangelower->val)
+ pfree(p[i]->rangelower->val);
+ pfree(p[i]->rangelower);
+ }
+
+ if (p[i]->rangeupper)
+ {
+ if (p[i]->rangeupper->val)
+ pfree(p[i]->rangeupper->val);
+ pfree(p[i]->rangeupper);
+ }
+
+ pfree(p[i]);
+ }
+
+ pfree(p);
+}
+
+/*
+ * get_check_expr_from_partbound - for direct use by tablecmds.c
+ */
+Expr *
+get_check_expr_from_partbound(Relation rel, Relation parent, Node *bound)
+{
+ PartitionKey key = parent->rd_partkey;
+ Expr *my_expr;
+ int i;
+ ListCell *partexprs_item;
+
+ if (IsA(bound, PartitionListSpec))
+ {
+ PartitionListSpec *list_spec = (PartitionListSpec *) bound;
+
+ my_expr = get_check_expr_for_list(key, list_spec->values);
+ }
+ else if (IsA(bound, PartitionRangeSpec))
+ {
+ PartitionRangeSpec *range_spec = (PartitionRangeSpec *) bound;
+
+ my_expr = get_check_expr_for_range(key,
+ range_spec->lower,
+ range_spec->lowerinc,
+ range_spec->upper,
+ range_spec->upperinc);
+ }
+
+ /*
+ * Translate vars in the generated expression with the correct attnos.
+ * Note that the vars in my_expr bear attnos dictated by key which carries
+ * attnos of the parent.
+ */
+ partexprs_item = list_head(key->partexprs);
+ for (i = 0; i < key->partnatts; i++)
+ {
+ AttrNumber attno = key->partattrs[i],
+ new_attno;
+ char *attname;
+
+ if (attno != 0)
+ {
+ attname = get_attname(RelationGetRelid(parent), attno);
+ new_attno = get_attnum(RelationGetRelid(rel), attname);
+
+ my_expr = (Expr *) translate_var_attno((Node *) my_expr,
+ attno,
+ new_attno);
+ }
+ else
+ {
+ Node *expr = (Node *) lfirst(partexprs_item);
+ Bitmapset *expr_attrs = NULL;
+ int index;
+
+ pull_varattnos(expr, 1, &expr_attrs);
+ partexprs_item = lnext(partexprs_item);
+
+ index = -1;
+ while ((index = bms_next_member(expr_attrs, index)) > 0)
+ {
+ AttrNumber attno = index + FirstLowInvalidHeapAttributeNumber;
+ attname = get_attname(RelationGetRelid(parent), attno);
+ new_attno = get_attnum(RelationGetRelid(rel), attname);
+ my_expr = (Expr *) translate_var_attno((Node *) my_expr,
+ attno,
+ new_attno);
+ }
+ }
+ }
+
+ return my_expr;
+}
+
+/*
+ * get_check_expr_for_list
+ */
+static Expr *
+get_check_expr_for_list(PartitionKey key, List *values)
+{
+ ArrayExpr *values_arr = makeNode(ArrayExpr);
+ ScalarArrayOpExpr *arrayop_expr;
+ Node *key_col;
+ Oid operoid;
+ StrategyNumber strategy = BTEqualStrategyNumber;
+
+ /* Btree equality operator of specified opfamily */
+ operoid = get_opfamily_member(key->partopfamily[0],
+ key->tcinfo->typid[0],
+ key->tcinfo->typid[0],
+ strategy);
+
+ /* lexpr */
+ if (key->partattrs[0] != 0)
+ key_col = (Node *) makeVar(1, key->partattrs[0],
+ key->tcinfo->typid[0],
+ key->tcinfo->typmod[0],
+ key->tcinfo->typcoll[0],
+ 0);
+ else
+ key_col = (Node *) linitial(key->partexprs);
+
+ /* rexpr */
+ values_arr->array_typeid = get_array_type(key->tcinfo->typid[0]);
+ values_arr->array_collid = key->tcinfo->typcoll[0];
+ values_arr->element_typeid = key->tcinfo->typid[0];
+ values_arr->elements = values;
+ values_arr->multidims = false;
+ values_arr->location = -1;
+
+ /* build the actual predicate expression node */
+ arrayop_expr = makeNode(ScalarArrayOpExpr);
+ arrayop_expr->opno = operoid;
+ arrayop_expr->opfuncid = get_opcode(operoid);
+ arrayop_expr->useOr = true;
+ arrayop_expr->inputcollid = key->tcinfo->typcoll[0];
+ arrayop_expr->args = list_make2(key_col, values_arr);
+ arrayop_expr->location = -1;
+
+ return (Expr *) arrayop_expr;
+}
+
+/*
+ * get_check_expr_for_range
+ */
+static Expr *
+get_check_expr_for_range(PartitionKey key,
+ List *lowervalues,
+ bool lowerinc,
+ List *uppervalues,
+ bool upperinc)
+{
+ List *and_args = NIL;
+ ListCell *cell1,
+ *cell2,
+ *partexprs_item;
+ int i;
+ uint16 strategy;
+
+ /*
+ * Handle the case with only one bound.
+ *
+ * In this case, consider only the first column of the key and generate
+ * appropriate opclause since only the comparison with the first column
+ * would have determined whether a rows went into such a partition.
+ * It's always true that -INF < someval and someval < +INF.
+ */
+ if (lowervalues == NIL || uppervalues == NIL)
+ {
+ List *values;
+ Const *key_val;
+ Node *key_col;
+ Oid operoid;
+ bool islower,
+ inclusive;
+
+ if (lowervalues != NIL)
+ {
+ values = lowervalues;
+ islower = true;
+ inclusive = lowerinc;
+ }
+ else
+ {
+ values = uppervalues;
+ islower = false;
+ inclusive = upperinc;
+ }
+
+ /* lexpr */
+ if (key->partattrs[0] != 0)
+ key_col = (Node *) makeVar(1, key->partattrs[0],
+ key->tcinfo->typid[0],
+ key->tcinfo->typmod[0],
+ key->tcinfo->typcoll[0],
+ 0);
+ else
+ key_col = (Node *) linitial(key->partexprs);
+
+ /* rexpr */
+ key_val = linitial(values);
+
+ if (islower)
+ strategy = inclusive ? BTGreaterEqualStrategyNumber : BTGreaterStrategyNumber;
+ else
+ strategy = inclusive ? BTLessEqualStrategyNumber : BTLessStrategyNumber;
+
+ operoid = get_opfamily_member(key->partopfamily[0],
+ key->tcinfo->typid[0],
+ key->tcinfo->typid[0],
+ strategy);
+
+ and_args = lappend(and_args,
+ make_opclause(operoid, BOOLOID,
+ false,
+ (Expr *) key_col,
+ (Expr *) key_val,
+ InvalidOid,
+ key->tcinfo->typcoll[0]));
+
+ return makeBoolExpr(AND_EXPR, and_args, -1);
+ }
+
+ /* Both bounds constrain values going into the partition. */
+ i = 0;
+ partexprs_item = list_head(key->partexprs);
+ forboth (cell1, lowervalues, cell2, uppervalues)
+ {
+ Node *key_col;
+ Const *lower_val = lfirst(cell1);
+ Const *upper_val = lfirst(cell2);
+ Expr *lower_clause,
+ *upper_clause;
+ Oid eqop,
+ lowerop,
+ upperop;
+ EState *estate;
+ MemoryContext oldcxt;
+ Expr *test_expr;
+ ExprState *test_exprstate;
+ Datum test_result;
+ bool isNull;
+
+ /* lexpr */
+ if (key->partattrs[i] != 0)
+ {
+ key_col = (Node *) makeVar(1, key->partattrs[i],
+ key->tcinfo->typid[i],
+ key->tcinfo->typmod[i],
+ key->tcinfo->typcoll[i],
+ 0);
+ }
+ else
+ {
+ key_col = (Node *) lfirst(partexprs_item);
+ partexprs_item = lnext(partexprs_item);
+ }
+
+ /*
+ * If lower_val = upper_val (= val, say), create a column = val
+ * constraint.
+ */
+ eqop = get_opfamily_member(key->partopfamily[i],
+ key->tcinfo->typid[i],
+ key->tcinfo->typid[i],
+ BTEqualStrategyNumber);
+
+ estate = CreateExecutorState();
+ oldcxt = MemoryContextSwitchTo(estate->es_query_cxt);
+ test_expr = make_opclause(eqop,
+ BOOLOID,
+ false,
+ (Expr *) lower_val,
+ (Expr *) upper_val,
+ InvalidOid,
+ key->tcinfo->typcoll[i]);
+ fix_opfuncids((Node *) test_expr);
+ test_exprstate = ExecInitExpr(test_expr, NULL);
+ test_result = ExecEvalExprSwitchContext(test_exprstate,
+ GetPerTupleExprContext(estate),
+ &isNull, NULL);
+ MemoryContextSwitchTo(oldcxt);
+ FreeExecutorState(estate);
+
+ if (DatumGetBool(test_result))
+ {
+ and_args = lappend(and_args,
+ make_opclause(eqop,
+ BOOLOID,
+ false,
+ (Expr *) key_col,
+ /* could've been upper_val */
+ (Expr *) lower_val,
+ InvalidOid,
+ key->tcinfo->typcoll[i]));
+
+ /* Go to the next column. */
+ }
+ else
+ {
+ /*
+ * For all columns but last, use inclusive strategy (<= or >=).
+ *
+ * For the last column, the bound's inclusivity determines.
+ */
+ strategy = i < key->partnatts - 1 || lowerinc
+ ? BTGreaterEqualStrategyNumber
+ : BTGreaterStrategyNumber;
+ lowerop = get_opfamily_member(key->partopfamily[i],
+ key->tcinfo->typid[i],
+ key->tcinfo->typid[i],
+ strategy);
+ strategy = i < key->partnatts - 1 || upperinc
+ ? BTLessEqualStrategyNumber
+ : BTLessStrategyNumber;
+ upperop = get_opfamily_member(key->partopfamily[i],
+ key->tcinfo->typid[i],
+ key->tcinfo->typid[i],
+ strategy);
+
+ lower_clause = make_opclause(lowerop,
+ BOOLOID,
+ false,
+ (Expr *) key_col,
+ (Expr *) lower_val,
+ InvalidOid,
+ key->tcinfo->typcoll[i]);
+ upper_clause = make_opclause(upperop,
+ BOOLOID,
+ false,
+ (Expr *) key_col,
+ (Expr *) upper_val,
+ InvalidOid,
+ key->tcinfo->typcoll[i]);
+
+ and_args = lappend(and_args,
+ makeBoolExpr(AND_EXPR,
+ list_make2(lower_clause, upper_clause), -1));
+
+ /* No need to constraint further columns. */
+ break;
+ }
+
+ i++;
+ }
+
+ return makeBoolExpr(AND_EXPR, and_args, -1);
+}
+
+/*
+ * translate_var_attno
+ * Changes Vars with a given attno in the provided expression tree to
+ * Vars with new_attno
+ */
+static Node *
+translate_var_attno(Node *expr, AttrNumber attno, AttrNumber new_attno)
+{
+ translate_var_attno_mutator_context cxt;
+
+ cxt.old_attno = attno;
+ cxt.new_attno = new_attno;
+
+ return expression_tree_mutator(expr, translate_var_attno_mutator, &cxt);
+}
+
+/*
+ * translate_var_attno_mutator
+ */
+static Node *
+translate_var_attno_mutator(Node *node,
+ translate_var_attno_mutator_context *cxt)
+{
+ if (node == NULL)
+ return NULL;
+
+ if (IsA(node, Var) && ((Var *) node)->varattno == cxt->old_attno)
+ {
+ Var *newvar = copyObject(node);
+
+ newvar->varattno = cxt->new_attno;
+
+ return (Node *) newvar;
+ }
+
+ return expression_tree_mutator(node, translate_var_attno_mutator,
+ (void *) cxt);
+}
+
+/*
+ * RelationGetPartitionConstraint
+ * Returns the equivelent CHECK constraint expression for a partition
+ */
+Node *
+RelationGetPartitionCheckExpr(Relation rel)
+{
+ /* Quick exit */
+ if (!relid_is_partition(RelationGetRelid(rel)))
+ return NULL;
+
+ /* Quick copy */
+ if (rel->rd_partcheck)
+ return copyObject(rel->rd_partcheck);
+
+ /* None of those, generate */
+ return generate_partition_check_expr(rel);
+}
+
+/*
+ * generate_partition_constraints
+ * Generate partition check constraint expression rel's pg_partition
+ * entry
+ *
+ * Result expression tree is stored CacheMemoryContext to ensure it survives
+ * as long as the relcache entry. But we should be running in a less long-lived
+ * working context. To avoid leaking cache memory if this routine fails partway
+ * routine fails partway through, we build in working memory and then copy
+ * the completed structure into cache memory.
+ */
+static Node *
+generate_partition_check_expr(Relation rel)
+{
+ HeapTuple tuple;
+ MemoryContext oldcxt;
+ Datum boundDatum;
+ bool isnull;
+ Node *bound;
+ Node *my_expr = NULL,
+ *result = NULL;
+ Oid relid = RelationGetRelid(rel);
+ Relation parent = heap_open(get_partition_parent(relid), NoLock);
+
+ /* quick copy */
+ if (rel->rd_partcheck)
+ {
+ heap_close(parent, NoLock);
+ return copyObject(rel->rd_partcheck);
+ }
+
+ tuple = SearchSysCache1(PARTRELID, relid);
+ Assert(HeapTupleIsValid(tuple));
+ boundDatum = SysCacheGetAttr(PARTRELID, tuple,
+ Anum_pg_partition_partbound,
+ &isnull);
+ Assert(!isnull);
+ bound = stringToNode(TextDatumGetCString(boundDatum));
+
+ my_expr = (Node *) get_check_expr_from_partbound(rel, parent, bound);
+
+ /*
+ * If parent is also a partition, create a AND_EXPR BoolExpr with just
+ * created expression and parent's check expression, as its args.
+ */
+ if (relid_is_partition(RelationGetRelid(parent)))
+ {
+ Node *parent_expr;
+
+ parent_expr = generate_partition_check_expr(parent);
+ result = (Node *) makeBoolExpr(AND_EXPR,
+ list_make2(parent_expr, my_expr), -1);
+ }
+ else
+ result = my_expr;
+
+ oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
+ rel->rd_partcheck = copyObject(result);
+ MemoryContextSwitchTo(oldcxt);
+
+ ReleaseSysCache(tuple);
+ heap_close(parent, NoLock);
+
+ return result;
+}
+
+/*
+ * RelationGetPartitionDescNode
+ * Recursively form partition node tree rooted at rel's node
+ */
+PartitionDescNode
+RelationGetPartitionDescNode(Relation rel)
+{
+ PartitionDescNode root;
+
+ /*
+ * Recurse to build the tree of partition descriptor nodes with
+ * rel's descriptor as the root of the tree.
+ */
+ root = GetPartitionDescNodeRecurse(rel, 0);
+ root->index = 0;
+ root->num_leaf_partitions = get_leaf_partition_count(root);
+
+ return root;
+}
+
+/*
+ * GetPartitionDescNodeRecurse
+ * Workhorse of RelationGetPartitionDescNode
+ *
+ * offset is the index (0-based) of the first leaf node in the tree, as
+ * as determined and passed by the parent or the left sibling.
+ */
+static PartitionDescNode
+GetPartitionDescNodeRecurse(Relation rel, int offset)
+{
+ PartitionDescNode root,
+ prev;
+ PartitionDesc pdesc = RelationGetPartitionDesc(rel);
+ int i;
+
+ /* First build our own node */
+ root = (PartitionDescNode) palloc0(sizeof(PartitionDescNodeData));
+ root->pdesc = pdesc;
+ root->relid = RelationGetRelid(rel);
+ root->offset = offset;
+ root->downlink = NULL;
+ root->next = NULL;
+
+ /*
+ * Recursively add nodes for partitions that are themselves partitioned,
+ * linking ourselves to the first such node via 'downlink'. Each node
+ * thus created (being on the same level) is linked to the right sibling
+ * via 'next'. For each node, determine the offset to pass and once
+ * created, update its index and num_leaf_partitions.
+ */
+ prev = NULL;
+ for (i = 0; i < pdesc->nparts; i++)
+ {
+ Oid relid = pdesc->oids[i];
+ int offset;
+ Relation rel;
+ PartitionDescNode pdnode;
+
+ if (!relid_is_partitioned(relid))
+ continue;
+
+ rel = heap_open(relid, AccessShareLock);
+
+ /* Offset for the new node */
+ if (prev)
+ /*
+ * Count leaves on the left - prev node has the account of those
+ * on its own left and then there may have been some siblings
+ * in-between that were leaves themselves.
+ */
+ offset = prev->offset + prev->num_leaf_partitions +
+ (i - prev->index - 1);
+ else
+ /*
+ * We're the first child - account of leaf nodes comes from the
+ * parent. Left siblings were all leaves themselves.
+ */
+ offset = root->offset + i;
+
+ pdnode = GetPartitionDescNodeRecurse(rel, offset);
+ pdnode->index = i;
+ pdnode->num_leaf_partitions = get_leaf_partition_count(pdnode);
+
+ heap_close(rel, AccessShareLock);
+
+ /* Found our first child; link to it. */
+ if (root->downlink == NULL)
+ root->downlink = pdnode;
+
+ /* Link new node to the left sibling, if any */
+ if (prev)
+ prev->next = pdnode;
+
+ prev = pdnode;
+ }
+
+ return root;
+}
+
+/*
+ * get_leaf_partition_count
+ * Recursively count the number of leaf partitions in the partition
+ * tree rooted at pdnode
+ */
+static int
+get_leaf_partition_count(PartitionDescNode pdnode)
+{
+ int i;
+ int result = 0;
+ PartitionDescNode node = pdnode->downlink;
+
+ for (i = 0; i < pdnode->pdesc->nparts; i++)
+ {
+ /* Indexes 0..(node->index - 1) are of leaf partitions */
+ if (node && i == node->index)
+ {
+ result += get_leaf_partition_count(node);
+ node = node->next;
+ }
+ else
+ result += 1;
+ }
+
+ return result;
+}
+
+/*
+ * get_leaf_partition_count
+ * Recursively compute the list of OIDs of leaf partitions in the
+ * partition tree rooted at pdnode
+ */
+List *
+get_leaf_partition_oids(PartitionDescNode pdnode)
+{
+ int i;
+ List *result = NIL;
+ Oid *myparts = pdnode->pdesc->oids;
+ int n_myparts = pdnode->pdesc->nparts;
+ PartitionDescNode node = pdnode->downlink;
+
+ for (i = 0; i < n_myparts; i++)
+ {
+ /* Indexes 0..(node->index - 1) are leaf partitions */
+ if (node && i == node->index)
+ {
+ result = list_concat(result, get_leaf_partition_oids(node));
+ node = node->next;
+ }
+ else
+ result = lappend_oid(result, myparts[i]);
+ }
+
+ return result;
+}
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 403738d..14e81de 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -2464,7 +2464,7 @@ CopyFrom(CopyState cstate)
if (!skip_tuple)
{
/* Check the constraints of the tuple */
- if (cstate->rel->rd_att->constr)
+ if (cstate->rel->rd_att->constr || resultRelInfo->ri_PartitionCheck)
ExecConstraints(resultRelInfo, slot, estate);
if (useHeapMultiInsert)
diff --git a/src/backend/commands/lockcmds.c b/src/backend/commands/lockcmds.c
index 62671a8..5120c93 100644
--- a/src/backend/commands/lockcmds.c
+++ b/src/backend/commands/lockcmds.c
@@ -16,6 +16,7 @@
#include "access/heapam.h"
#include "catalog/namespace.h"
+#include "catalog/partition.h"
#include "catalog/pg_inherits_fn.h"
#include "commands/lockcmds.h"
#include "miscadmin.h"
@@ -63,7 +64,8 @@ LockTableCommand(LockStmt *lockstmt)
RangeVarCallbackForLockTable,
(void *) &lockstmt->mode);
- if (recurse)
+ /* Force inheritance recursion, if partitioned table. */
+ if (recurse || relid_is_partitioned(reloid))
LockTableRecurse(reloid, lockstmt->mode, lockstmt->nowait);
}
}
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 6e66405..b763a1a 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -121,6 +121,19 @@ static List *on_commits = NIL;
/*
+ * For AT ATTACH PARTITION <table> FOR VALUES <partition_bound_spec>,
+ *
+ * Struct describing partition check expression corresponding to
+ * <partition_bound_spec> to validate for rows in <table> during
+ * Phase 3 scan.
+ */
+typedef struct PartitionCheck
+{
+ Expr *expr;
+ List *state;
+} PartitionCheck;
+
+/*
* State information for ALTER TABLE
*
* The pending-work queue for an ALTER TABLE is a List of AlteredTableInfo
@@ -163,6 +176,8 @@ typedef struct AlteredTableInfo
Oid newTableSpace; /* new tablespace; 0 means no change */
bool chgPersistence; /* T if SET LOGGED/UNLOGGED is used */
char newrelpersistence; /* if above is true */
+ PartitionCheck *partitionCheck; /* partition check expression for
+ * ATTACH PARTITION validation */
/* Objects to rebuild after completing ALTER TYPE operations */
List *changedConstraintOids; /* OIDs of constraints to rebuild */
List *changedConstraintDefs; /* string definitions of same */
@@ -712,6 +727,19 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
StoreCatalogInheritance(relationId, inheritOids);
/*
+ * Process and store partition bound. Do this before
+ * CommandCounterIncrement() below, so that we are not deemed an existing
+ * partition by StorePartitionBound() (which is basically found out by
+ * querying pg_inherits).
+ */
+ if (stmt->partbound)
+ {
+ Oid parentId = linitial_oid(inheritOids);
+
+ StorePartitionBound(relationId, parentId, stmt->partbound);
+ }
+
+ /*
* We must bump the command counter to make the newly-created relation
* tuple visible for opening.
*/
@@ -995,6 +1023,13 @@ RangeVarCallbackForDropRelation(const RangeVar *rel, Oid relOid, Oid oldRelOid,
if (!OidIsValid(relOid))
return;
+ if (relid_is_partition(relOid))
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("\"%s\" is a partition of \"%s\"", rel->relname,
+ get_rel_name(get_partition_parent(relOid))),
+ errhint("Use ALTER TABLE DETACH PARTITION to be able to drop it.")));
+
tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relOid));
if (!HeapTupleIsValid(tuple))
return; /* concurrently dropped, so nothing to do */
@@ -1086,7 +1121,8 @@ ExecuteTruncate(TruncateStmt *stmt)
rels = lappend(rels, rel);
relids = lappend_oid(relids, myrelid);
- if (recurse)
+ /* Force inheritance recursion, if partitioned table. */
+ if (recurse || relid_is_partitioned(myrelid))
{
ListCell *child;
List *children;
@@ -1810,8 +1846,12 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
Node *expr;
bool found_whole_row;
- /* ignore if the constraint is non-inheritable */
- if (check[i].ccnoinherit)
+ /*
+ * ignore if the constraint is non-inheritable and this is not
+ * the partition case (constraints of partitioned tables are
+ * always present in their partitions)
+ */
+ if (check[i].ccnoinherit && !is_partition)
continue;
/* Adjust Vars to match new table's column numbering */
@@ -2246,6 +2286,11 @@ renameatt_check(Oid myrelid, Form_pg_class classform, bool recursing)
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot rename column of typed table")));
+ if (relid_is_partition(myrelid) && !recursing)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot rename column of a partition")));
+
/*
* Renaming the columns of sequences or toast tables doesn't actually
* break anything from the system's point of view, since internal
@@ -2472,11 +2517,13 @@ renameatt(RenameStmt *stmt)
return InvalidObjectAddress;
}
+ /* Force inheritance recursion, if partitioned table. */
attnum =
renameatt_internal(relid,
stmt->subname, /* old att name */
stmt->newname, /* new att name */
- interpretInhOption(stmt->relation->inhOpt), /* recursive? */
+ interpretInhOption(stmt->relation->inhOpt) ||
+ relid_is_partitioned(relid), /* recursive? */
false, /* recursing? */
0, /* expected inhcount */
stmt->behavior);
@@ -2624,11 +2671,13 @@ RenameConstraint(RenameStmt *stmt)
}
}
+ /* Force inheritance recursion, if partitioned table. */
return
rename_constraint_internal(relid, typid,
stmt->subname,
stmt->newname,
- stmt->relation ? interpretInhOption(stmt->relation->inhOpt) : false, /* recursive? */
+ stmt->relation ? interpretInhOption(stmt->relation->inhOpt) ||
+ relid_is_partitioned(relid) : false, /* recursive? */
false, /* recursing? */
0 /* expected inhcount */ );
@@ -2870,8 +2919,11 @@ AlterTable(Oid relid, LOCKMODE lockmode, AlterTableStmt *stmt)
CheckTableNotInUse(rel, "ALTER TABLE");
+ /* Force inheritance recursion, if partitioned table */
ATController(stmt,
- rel, stmt->cmds, interpretInhOption(stmt->relation->inhOpt),
+ rel, stmt->cmds,
+ interpretInhOption(stmt->relation->inhOpt) ||
+ relid_is_partitioned(RelationGetRelid(rel)),
lockmode);
}
@@ -3990,7 +4042,7 @@ ATRewriteTables(AlterTableStmt *parsetree, List **wqueue, LOCKMODE lockmode)
* Test the current data within the table against new constraints
* generated by ALTER TABLE commands, but don't rebuild data.
*/
- if (tab->constraints != NIL || tab->new_notnull)
+ if (tab->constraints != NIL || tab->new_notnull || tab->partitionCheck)
ATRewriteTable(tab, InvalidOid, lockmode);
/*
@@ -4134,6 +4186,21 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
}
}
+ /*
+ * Build expression execution states for partition check expression
+ * as well.
+ */
+ if (tab->partitionCheck)
+ {
+ List *expr;
+
+ needscan = true;
+ Assert(tab->partitionCheck->expr);
+ expr = list_make1(tab->partitionCheck->expr);
+ tab->partitionCheck->state = (List *)
+ ExecPrepareExpr((Expr *) expr, estate);
+ }
+
foreach(l, tab->newvals)
{
NewColumnValue *ex = lfirst(l);
@@ -4323,6 +4390,12 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
}
}
+ if (tab->partitionCheck &&
+ !ExecQual(tab->partitionCheck->state, econtext, true))
+ ereport(ERROR,
+ (errcode(ERRCODE_CHECK_VIOLATION),
+ errmsg("table contains a row violating the partition bound specification")));
+
/* Write the tuple out to the new relation */
if (newrel)
heap_insert(newrel, tuple, mycid, hi_options, bistate);
@@ -4520,7 +4593,8 @@ ATSimpleRecursion(List **wqueue, Relation rel,
*/
if (recurse &&
(rel->rd_rel->relkind == RELKIND_RELATION ||
- rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE))
+ rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE ||
+ rel->rd_rel->relkind == RELKIND_PARTITIONED_REL))
{
Oid relid = RelationGetRelid(rel);
ListCell *child;
@@ -4842,6 +4916,11 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
if (recursing)
ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
+ if (relid_is_partition(RelationGetRelid(rel)) && !recursing)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot add column to a partition")));
+
attrdesc = heap_open(AttributeRelationId, RowExclusiveLock);
/*
@@ -5297,6 +5376,7 @@ ATExecDropNotNull(Relation rel, const char *colName, LOCKMODE lockmode)
List *indexoidlist;
ListCell *indexoidscan;
ObjectAddress address;
+ Oid relid;
/*
* lookup the attribute
@@ -5364,6 +5444,28 @@ ATExecDropNotNull(Relation rel, const char *colName, LOCKMODE lockmode)
list_free(indexoidlist);
/*
+ * If rel is partition, throw error if the attribute is not null in
+ * parent
+ */
+ relid = RelationGetRelid(rel);
+ if (relid_is_partition(relid))
+ {
+ Oid parentId = get_partition_parent(relid);
+ Relation parent = heap_open(parentId, AccessShareLock);
+ TupleDesc tupDesc = RelationGetDescr(parent);
+ AttrNumber parent_attnum;
+
+ parent_attnum = get_attnum(parentId, colName);
+ if (tupDesc->attrs[parent_attnum - 1]->attnotnull)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("column \"%s\" is not null in parent",
+ colName),
+ errhint("Please drop not null in the parent instead")));
+ heap_close(parent, AccessShareLock);
+ }
+
+ /*
* Okay, actually perform the catalog change ... if needed
*/
if (((Form_pg_attribute) GETSTRUCT(tuple))->attnotnull)
@@ -5893,6 +5995,11 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
if (recursing)
ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
+ if (relid_is_partition(RelationGetRelid(rel)) && !recursing)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot drop column from a partition")));
+
/*
* get the number of the attribute
*/
@@ -5938,11 +6045,13 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
if (!is_expr)
ereport(ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
- errmsg("cannot drop column named in partition key")));
+ errmsg("cannot drop column named in%s partition key",
+ recursing ? " downstream" : "")));
else
ereport(ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
- errmsg("cannot drop column referenced in partition key expression")));
+ errmsg("cannot drop column referenced in%s partition key expression",
+ recursing ? " downstream" : "")));
}
ReleaseSysCache(tuple);
@@ -6376,8 +6485,9 @@ ATAddCheckConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
/*
* If adding a NO INHERIT constraint, no need to find our children.
+ * However, that applies only to regular inheritance parents.
*/
- if (constr->is_no_inherit)
+ if (constr->is_no_inherit && !relid_is_partitioned(RelationGetRelid(rel)))
return address;
/*
@@ -7938,9 +8048,12 @@ ATExecDropConstraint(Relation rel, const char *constrName,
/*
* Propagate to children as appropriate. Unlike most other ALTER
* routines, we have to do this one level of recursion at a time; we can't
- * use find_all_inheritors to do it in one pass.
+ * use find_all_inheritors to do it in one pass. Note that if the parent
+ * is a partitioned table, we propagate to children (partitions) despite
+ * is_no_inherit_constraint.
*/
- if (!is_no_inherit_constraint)
+ if (!is_no_inherit_constraint ||
+ relid_is_partitioned(RelationGetRelid(rel)))
children = find_inheritance_children(RelationGetRelid(rel), lockmode);
else
children = NIL;
@@ -8070,6 +8183,11 @@ ATPrepAlterColumnType(List **wqueue,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot alter column type of typed table")));
+ if (relid_is_partition(RelationGetRelid(rel)) && !recursing)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot alter column type of a partition")));
+
/* lookup the attribute so we can check inheritance status */
tuple = SearchSysCacheAttName(RelationGetRelid(rel), colName);
if (!HeapTupleIsValid(tuple))
@@ -8100,11 +8218,13 @@ ATPrepAlterColumnType(List **wqueue,
if (!is_expr)
ereport(ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
- errmsg("cannot alter column named in partition key")));
+ errmsg("cannot alter column named in%s partition key",
+ recursing ? " downstream" : "")));
else
ereport(ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
- errmsg("cannot alter column referenced in partition key expression")));
+ errmsg("cannot alter column referenced in%s partition key expression",
+ recursing ? " downstream" : "")));
}
/* Look up the target type */
@@ -10237,6 +10357,11 @@ ATPrepAddInherit(Relation child_rel)
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot change inheritance of typed table")));
+ if (relid_is_partition(RelationGetRelid(child_rel)))
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot change inheritance of a partition")));
+
if (child_rel->rd_rel->relkind == RELKIND_PARTITIONED_REL)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
@@ -10293,6 +10418,11 @@ ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode)
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot inherit from partitioned table \"%s\"", parent->relname)));
+ if (relid_is_partition(RelationGetRelid(parent_rel)))
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot inherit from a partition")));
+
/*
* Prevent circularity by seeing if proposed parent inherits from child.
* (In particular, this disallows making a rel inherit from itself.)
@@ -10679,6 +10809,11 @@ ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
ObjectAddress address;
Relation parent_rel;
+ if (relid_is_partition(RelationGetRelid(rel)))
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot change inheritance of a partition")));
+
/*
* AccessShareLock on the parent is probably enough, seeing that DROP
* TABLE doesn't lock parent tables at all. We need some lock since we'll
@@ -12568,6 +12703,12 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
*/
ATSimplePermissions(attachRel, ATT_TABLE);
+ if (relid_is_partition(RelationGetRelid(attachRel)))
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("\"%s\" is already a partition",
+ RelationGetRelationName(attachRel))));
+
if (attachRel->rd_rel->reloftype)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
@@ -12601,6 +12742,12 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
errmsg("cannot attach a temporary relation of another session as partition ")));
/* If parent has OIDs, attachRel must have OIDs */
+ if (attachRel->rd_rel->reloftype)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot attach a typed table as partition")));
+
+ /* If parent has OIDs then child must have OIDs */
if (rel->rd_rel->relhasoids && !attachRel->rd_rel->relhasoids)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
@@ -12643,6 +12790,30 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
/* OK to create inheritance. Rest of the checks performed there */
CreateInheritance(attachRel, rel);
+ /*
+ * Set up to have the rows in table to be checked for violation of the
+ * partition bound spec in phase 3 scan.
+ */
+ if (!cmd->skip_validate)
+ {
+ AlteredTableInfo *tab;
+ PartitionCheck *check;
+ Expr *my_expr,
+ *parent_expr = (Expr *) RelationGetPartitionCheckExpr(rel);
+
+ tab = ATGetQueueEntry(wqueue, attachRel);
+ check = (PartitionCheck *) palloc0(sizeof(PartitionCheck));
+ my_expr = get_check_expr_from_partbound(attachRel, rel, cmd->bound);
+ check->expr = parent_expr ? makeBoolExpr(AND_EXPR,
+ list_make2(parent_expr, my_expr), -1)
+ : my_expr;
+ tab->partitionCheck = check;
+ }
+
+ /* Store bound into the catalog */
+ StorePartitionBound(RelationGetRelid(attachRel), RelationGetRelid(rel),
+ cmd->bound);
+
ObjectAddressSet(address, RelationRelationId, RelationGetRelid(attachRel));
/* keep our lock until commit */
@@ -12667,6 +12838,15 @@ ATExecDetachPartition(Relation rel, RangeVar *name)
/* All inheritance related checks are performed within the function */
RemoveInheritance(partRel, rel);
+ /* Remove the pg_partition entry */
+ RemovePartitionEntryByRelId(RelationGetRelid(partRel));
+
+ /*
+ * Invalidate the relcache so that the partition is no longer included
+ * in our partition descriptor.
+ */
+ CacheInvalidateRelcache(rel);
+
ObjectAddressSet(address, RelationRelationId, RelationGetRelid(partRel));
/* keep our lock until commit */
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 67492a5..23ed0b6 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -42,6 +42,7 @@
#include "access/transam.h"
#include "access/xact.h"
#include "catalog/namespace.h"
+#include "catalog/partition.h"
#include "commands/matview.h"
#include "commands/trigger.h"
#include "executor/execdebug.h"
@@ -1250,6 +1251,8 @@ InitResultRelInfo(ResultRelInfo *resultRelInfo,
resultRelInfo->ri_ConstraintExprs = NULL;
resultRelInfo->ri_junkFilter = NULL;
resultRelInfo->ri_projectReturning = NULL;
+ resultRelInfo->ri_PartitionCheck = (Expr *)
+ RelationGetPartitionCheckExpr(resultRelationDesc);
}
/*
@@ -1683,6 +1686,49 @@ ExecRelCheck(ResultRelInfo *resultRelInfo,
return NULL;
}
+/*
+ * ExecPartitionCheck --- check that tuple meets the partition boundary
+ * specification.
+ *
+ * Note: This is called, *iff* resultRelInfo is the main target table.
+ */
+static bool
+ExecPartitionCheck(ResultRelInfo *resultRelInfo, TupleTableSlot *slot,
+ EState *estate)
+{
+ ExprContext *econtext;
+
+ /*
+ * If first time through, build expression state tree for the partition
+ * check expression. Keep it in the per-query memory context so they'll
+ * survive throughout the query.
+ */
+ if (resultRelInfo->ri_PartitionCheckExpr == NULL)
+ {
+ List *qual = make_ands_implicit(resultRelInfo->ri_PartitionCheck);
+ resultRelInfo->ri_PartitionCheckExpr = (List *)
+ ExecPrepareExpr((Expr *) qual, estate);
+ }
+
+ /*
+ * We will use the EState's per-tuple context for evaluating constraint
+ * expressions (creating it if it's not already there).
+ */
+ econtext = GetPerTupleExprContext(estate);
+
+ /* Arrange for econtext's scan tuple to be the tuple under test */
+ econtext->ecxt_scantuple = slot;
+
+ /*
+ * NOTE: SQL specifies that a NULL result from a constraint expression
+ * is not to be treated as a failure. Therefore, tell ExecQual to
+ * return TRUE for NULL.
+ *
+ * XXX - although, it's unlikely that NULL would result.
+ */
+ return ExecQual(resultRelInfo->ri_PartitionCheckExpr, econtext, true);
+}
+
void
ExecConstraints(ResultRelInfo *resultRelInfo,
TupleTableSlot *slot, EState *estate)
@@ -1694,9 +1740,9 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
Bitmapset *insertedCols;
Bitmapset *updatedCols;
- Assert(constr);
+ Assert(constr || resultRelInfo->ri_PartitionCheck);
- if (constr->has_not_null)
+ if (constr && constr->has_not_null)
{
int natts = tupdesc->natts;
int attrChk;
@@ -1727,7 +1773,7 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
}
}
- if (constr->num_check > 0)
+ if (constr && constr->num_check > 0)
{
const char *failed;
@@ -1751,6 +1797,29 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
errtableconstraint(rel, failed)));
}
}
+
+ if (resultRelInfo->ri_PartitionCheck)
+ {
+ if (!ExecPartitionCheck(resultRelInfo, slot, estate))
+ {
+ char *val_desc;
+
+ insertedCols = GetInsertedColumns(resultRelInfo, estate);
+ updatedCols = GetUpdatedColumns(resultRelInfo, estate);
+ modifiedCols = bms_union(insertedCols, updatedCols);
+ val_desc = ExecBuildSlotValueDescription(RelationGetRelid(rel),
+ slot,
+ tupdesc,
+ modifiedCols,
+ 64);
+ ereport(ERROR,
+ (errcode(ERRCODE_CHECK_VIOLATION),
+ errmsg("new row violates the partition boundary"
+ " specification of \"%s\"",
+ RelationGetRelationName(rel)),
+ val_desc ? errdetail("Failing row contains %s.", val_desc) : 0));
+ }
+ }
}
/*
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 7ad1140..6ecb6be 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -354,7 +354,7 @@ ExecInsert(ModifyTableState *mtstate,
/*
* Check the constraints of the tuple
*/
- if (resultRelationDesc->rd_att->constr)
+ if (resultRelationDesc->rd_att->constr || resultRelInfo->ri_PartitionCheck)
ExecConstraints(resultRelInfo, slot, estate);
if (onconflict != ONCONFLICT_NONE && resultRelInfo->ri_NumIndices > 0)
@@ -907,7 +907,7 @@ lreplace:;
/*
* Check the constraints of the tuple
*/
- if (resultRelationDesc->rd_att->constr)
+ if (resultRelationDesc->rd_att->constr || resultRelInfo->ri_PartitionCheck)
ExecConstraints(resultRelInfo, slot, estate);
/*
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 8059594..45f0064 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -2247,6 +2247,35 @@ _readExtensibleNode(void)
}
/*
+ * _readPartitionListSpec
+ */
+static PartitionListSpec *
+_readPartitionListSpec(void)
+{
+ READ_LOCALS(PartitionListSpec);
+
+ READ_NODE_FIELD(values);
+
+ READ_DONE();
+}
+
+/*
+ * _readPartitionRangeSpec
+ */
+static PartitionRangeSpec *
+_readPartitionRangeSpec(void)
+{
+ READ_LOCALS(PartitionRangeSpec);
+
+ READ_BOOL_FIELD(lowerinc);
+ READ_NODE_FIELD(lower);
+ READ_BOOL_FIELD(upperinc);
+ READ_NODE_FIELD(upper);
+
+ READ_DONE();
+}
+
+/*
* parseNodeString
*
* Given a character string representing a node tree, parseNodeString creates
@@ -2476,6 +2505,10 @@ parseNodeString(void)
return_value = _readAlternativeSubPlan();
else if (MATCH("EXTENSIBLENODE", 14))
return_value = _readExtensibleNode();
+ else if (MATCH("PARTITIONLISTVALUES", 19))
+ return_value = _readPartitionListSpec();
+ else if (MATCH("PARTITIONRANGE", 14))
+ return_value = _readPartitionRangeSpec();
else
{
elog(ERROR, "badly formatted node string \"%.32s\"...", token);
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index 9c11b09..ed3193b 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -27,6 +27,7 @@
#include "catalog/catalog.h"
#include "catalog/dependency.h"
#include "catalog/heap.h"
+#include "catalog/partition.h"
#include "catalog/pg_am.h"
#include "catalog/pg_constraint.h"
#include "foreign/fdwapi.h"
@@ -1124,6 +1125,7 @@ get_relation_constraints(PlannerInfo *root,
Index varno = rel->relid;
Relation relation;
TupleConstr *constr;
+ Node *pexpr;
/*
* We assume the relation has already been safely locked.
@@ -1203,6 +1205,17 @@ get_relation_constraints(PlannerInfo *root,
}
}
+ /* Append partition check constraint, if any */
+ pexpr = RelationGetPartitionCheckExpr(relation);
+ if (pexpr)
+ {
+ /* Fix Vars to have the desired varno */
+ if (varno != 1)
+ ChangeVarNodes(pexpr, 1, varno, 0);
+
+ result = list_concat(result, make_ands_implicit((Expr *) pexpr));
+ }
+
heap_close(relation, NoLock);
return result;
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 7d2fedf..f55129a 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -25,6 +25,7 @@
#include "postgres.h"
#include "access/sysattr.h"
+#include "catalog/partition.h"
#include "catalog/pg_type.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
diff --git a/src/backend/parser/parse_clause.c b/src/backend/parser/parse_clause.c
index 307e383..5124dbf 100644
--- a/src/backend/parser/parse_clause.c
+++ b/src/backend/parser/parse_clause.c
@@ -202,8 +202,8 @@ setTargetTable(ParseState *pstate, RangeVar *relation,
pstate->p_target_rangetblentry = rte;
/*
- * Override specified inheritance option, if relation is a partitioned
- * table and it's a target of INSERT (ie, alsoSource == false).
+ * For UPDATE/DELETE, force inheritance recursion, if partitioned
+ * table.
*/
if (alsoSource)
rte->inh |= relid_is_partitioned(rte->relid);
diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c
index abcc1d6..b3870f6 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -1218,8 +1218,7 @@ addRangeTableEntry(ParseState *pstate,
* The initial default on access checks is always check-for-READ-access,
* which is the right thing for all except target tables.
*
- * Note: we override the specified inheritance option, if relation is a
- * partitioned table.
+ * Note: We force inheritance recursion, if partitioned table.
*/
rte->lateral = false;
rte->inh = inh || relid_is_partitioned(RelationGetRelid(rel));
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index c0a1bb0..d2273c6 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -32,6 +32,7 @@
#include "catalog/dependency.h"
#include "catalog/heap.h"
#include "catalog/index.h"
+#include "catalog/partition.h"
#include "catalog/namespace.h"
#include "catalog/partition.h"
#include "catalog/pg_am.h"
@@ -3105,6 +3106,11 @@ transformPartitionOf(CreateStmtContext *cxt, Node *bound)
if (parentRel->rd_rel->relhasoids)
cxt->hasoids = true;
+ /*
+ * Create ColumnDefs correponding to parent's attributes as "inherited"
+ * attributes. Also, set is_for_partition whose function is similar to
+ * is_from_type for MergeAttributes()'s purposes.
+ */
tupdesc = RelationGetDescr(parentRel);
for (i = 0; i < tupdesc->natts; i++)
{
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 49f6fad..bf59cc3 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -8159,6 +8159,84 @@ get_rule_expr(Node *node, deparse_context *context,
}
break;
+ case T_PartitionListSpec:
+ {
+ PartitionListSpec *list_spec = (PartitionListSpec *) node;
+ ListCell *cell;
+ char *sep;
+
+ appendStringInfoString(buf, "FOR VALUES");
+
+ appendStringInfoString(buf, " IN (");
+ sep = "";
+ foreach (cell, list_spec->values)
+ {
+ Const *val = lfirst(cell);
+
+ appendStringInfoString(buf, sep);
+ get_const_expr(val, context, -1);
+ sep = ", ";
+ }
+
+ appendStringInfoString(buf, ")");
+ }
+ break;
+
+ case T_PartitionRangeSpec:
+ {
+ PartitionRangeSpec *range_spec = (PartitionRangeSpec *) node;
+ ListCell *cell;
+ char *sep;
+
+ appendStringInfoString(buf, "FOR VALUES");
+
+ appendStringInfoString(buf, " START");
+ if (!range_spec->lower)
+ appendStringInfoString(buf, " UNBOUNDED");
+ else
+ {
+ appendStringInfoString(buf, " (");
+
+ sep = "";
+ foreach (cell, range_spec->lower)
+ {
+ Const *val = lfirst(cell);
+
+ appendStringInfoString(buf, sep);
+ get_const_expr(val, context, -1);
+ sep = ", ";
+ }
+ appendStringInfoString(buf, ")");
+
+ if (!range_spec->lowerinc)
+ appendStringInfoString(buf, " EXCLUSIVE");
+ }
+
+ appendStringInfoString(buf, " END");
+
+ if (!range_spec->upper)
+ appendStringInfoString(buf, " UNBOUNDED");
+ else
+ {
+ appendStringInfoString(buf, " (");
+
+ sep = "";
+ foreach (cell, range_spec->upper)
+ {
+ Const *val = lfirst(cell);
+
+ appendStringInfoString(buf, sep);
+ get_const_expr(val, context, -1);
+ sep = ", ";
+ }
+ appendStringInfoString(buf, ")");
+
+ if (range_spec->upperinc)
+ appendStringInfoString(buf, " INCLUSIVE");
+ }
+ }
+ break;
+
case T_List:
{
char *sep;
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 2841745..05c4f87 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -50,6 +50,7 @@
#include "catalog/pg_database.h"
#include "catalog/pg_namespace.h"
#include "catalog/pg_opclass.h"
+#include "catalog/partition.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_rewrite.h"
#include "catalog/pg_shseclabel.h"
@@ -2055,6 +2056,10 @@ RelationDestroyRelation(Relation relation, bool remember_tupdesc)
if (relation->rd_rsdesc)
MemoryContextDelete(relation->rd_rsdesc->rscxt);
FreePartitionKey(relation->rd_partkey);
+ if (relation->rd_pdcxt)
+ MemoryContextDelete(relation->rd_pdcxt);
+ if (relation->rd_partcheck)
+ pfree(relation->rd_partcheck);
if (relation->rd_fdwroutine)
pfree(relation->rd_fdwroutine);
pfree(relation);
@@ -4984,6 +4989,8 @@ load_relcache_init_file(bool shared)
rel->trigdesc = NULL;
rel->rd_rsdesc = NULL;
rel->rd_partkey = NULL;
+ rel->rd_partdesc = NULL;
+ rel->rd_partcheck = NULL;
rel->rd_indexprs = NIL;
rel->rd_indpred = NIL;
rel->rd_exclops = NULL;
diff --git a/src/backend/utils/cache/syscache.c b/src/backend/utils/cache/syscache.c
index a6563fe..4a5783a 100644
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -49,6 +49,7 @@
#include "catalog/pg_operator.h"
#include "catalog/pg_opfamily.h"
#include "catalog/pg_partitioned.h"
+#include "catalog/pg_partition.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_range.h"
#include "catalog/pg_rewrite.h"
@@ -569,6 +570,17 @@ static const struct cachedesc cacheinfo[] = {
},
8
},
+ {PartitionRelationId, /* PARTRELID */
+ PartitionRelidIndexId,
+ 1,
+ {
+ Anum_pg_partition_partrelid,
+ 0,
+ 0,
+ 0
+ },
+ 128
+ },
{PartitionedRelationId, /* PARTEDRELID */
PartitionedRelidIndexId,
1,
diff --git a/src/bin/pg_dump/common.c b/src/bin/pg_dump/common.c
index 1cbb987..c8e56bd 100644
--- a/src/bin/pg_dump/common.c
+++ b/src/bin/pg_dump/common.c
@@ -68,6 +68,8 @@ static int numextmembers;
static void flagInhTables(TableInfo *tbinfo, int numTables,
InhInfo *inhinfo, int numInherits);
+static void flagPartitions(TableInfo *tblinfo, int numTables,
+ PartInfo *partinfo, int numPartitions);
static void flagInhAttrs(DumpOptions *dopt, TableInfo *tblinfo, int numTables);
static DumpableObject **buildIndexArray(void *objArray, int numObjs,
Size objSize);
@@ -75,6 +77,8 @@ static int DOCatalogIdCompare(const void *p1, const void *p2);
static int ExtensionMemberIdCompare(const void *p1, const void *p2);
static void findParentsByOid(TableInfo *self,
InhInfo *inhinfo, int numInherits);
+static void findPartitionParentByOid(TableInfo *self, PartInfo *partinfo,
+ int numPartitions);
static int strInArray(const char *pattern, char **arr, int arr_size);
@@ -93,8 +97,10 @@ getSchemaData(Archive *fout, int *numTablesPtr)
NamespaceInfo *nspinfo;
ExtensionInfo *extinfo;
InhInfo *inhinfo;
+ PartInfo *partinfo;
int numAggregates;
int numInherits;
+ int numPartitions;
int numRules;
int numProcLangs;
int numCasts;
@@ -232,6 +238,10 @@ getSchemaData(Archive *fout, int *numTablesPtr)
inhinfo = getInherits(fout, &numInherits);
if (g_verbose)
+ write_msg(NULL, "reading partition information\n");
+ partinfo = getPartitions(fout, &numPartitions);
+
+ if (g_verbose)
write_msg(NULL, "reading event triggers\n");
getEventTriggers(fout, &numEventTriggers);
@@ -245,6 +255,11 @@ getSchemaData(Archive *fout, int *numTablesPtr)
write_msg(NULL, "finding inheritance relationships\n");
flagInhTables(tblinfo, numTables, inhinfo, numInherits);
+ /* Link tables to partition parents, mark parents as interesting */
+ if (g_verbose)
+ write_msg(NULL, "finding partition relationships\n");
+ flagPartitions(tblinfo, numTables, partinfo, numPartitions);
+
if (g_verbose)
write_msg(NULL, "reading column info for interesting tables\n");
getTableAttrs(fout, tblinfo, numTables);
@@ -319,6 +334,43 @@ flagInhTables(TableInfo *tblinfo, int numTables,
}
}
+/* flagPartitions -
+ * Fill in parent link fields of every target table that is partition,
+ * and mark parents of partitions as interesting
+ *
+ * modifies tblinfo
+ */
+static void
+flagPartitions(TableInfo *tblinfo, int numTables,
+ PartInfo *partinfo, int numPartitions)
+{
+ int i;
+
+ for (i = 0; i < numTables; i++)
+ {
+ /* Some kinds are never partitions */
+ if (tblinfo[i].relkind == RELKIND_SEQUENCE ||
+ tblinfo[i].relkind == RELKIND_VIEW ||
+ tblinfo[i].relkind == RELKIND_MATVIEW)
+ continue;
+
+ /* Don't bother computing anything for non-target tables, either */
+ if (!tblinfo[i].dobj.dump)
+ continue;
+
+ /* Find the parent TableInfo and save */
+ findPartitionParentByOid(&tblinfo[i], partinfo, numPartitions);
+
+ /* Mark the parent as interesting for getTableAttrs */
+ if (tblinfo[i].partitionOf)
+ {
+ tblinfo[i].partitionOf->interesting = true;
+ addObjectDependency(&tblinfo[i].dobj,
+ tblinfo[i].partitionOf->dobj.dumpId);
+ }
+ }
+}
+
/* flagInhAttrs -
* for each dumpable table in tblinfo, flag its inherited attributes
*
@@ -920,6 +972,40 @@ findParentsByOid(TableInfo *self,
}
/*
+ * findPartitionParentByOid
+ * find a partition's parent in tblinfo[]
+ */
+static void
+findPartitionParentByOid(TableInfo *self, PartInfo *partinfo,
+ int numPartitions)
+{
+ Oid oid = self->dobj.catId.oid;
+ int i;
+
+ for (i = 0; i < numPartitions; i++)
+ {
+ if (partinfo[i].partrelid == oid)
+ {
+ TableInfo *parent;
+
+ parent = findTableByOid(partinfo[i].partparent);
+ if (parent == NULL)
+ {
+ write_msg(NULL, "failed sanity check, parent OID %u of table \"%s\" (OID %u) not found\n",
+ partinfo[i].partparent,
+ self->dobj.name,
+ oid);
+ exit_nicely(1);
+ }
+ self->partitionOf = parent;
+
+ /* While we're at it, also save the partdef */
+ self->partitiondef = partinfo[i].partdef;
+ }
+ }
+}
+
+/*
* parseOidArray
* parse a string of numbers delimited by spaces into a character array
*
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 4c95aca..7337145 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -5986,6 +5986,63 @@ getInherits(Archive *fout, int *numInherits)
}
/*
+ * getPartitions
+ * read all the partition inheritance and partition bound information
+ * from the system catalogs return them in the PartInfo* structure
+ *
+ * numPartitions is set to the number of pairs read in
+ */
+PartInfo *
+getPartitions(Archive *fout, int *numPartitions)
+{
+ PGresult *res;
+ int ntups;
+ int i;
+ PQExpBuffer query = createPQExpBuffer();
+ PartInfo *partinfo;
+
+ int i_partrelid;
+ int i_partparent;
+ int i_partbound;
+
+ /* Make sure we are in proper schema */
+ selectSourceSchema(fout, "pg_catalog");
+
+ /* find all the inheritance information */
+
+ appendPQExpBufferStr(query,
+ "SELECT partrelid, inhparent AS partparent, "
+ " pg_get_expr(partbound, partrelid) AS partbound "
+ "FROM pg_partition, pg_inherits "
+ "WHERE partrelid = inhrelid");
+
+ res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
+
+ ntups = PQntuples(res);
+
+ *numPartitions = ntups;
+
+ partinfo = (PartInfo *) pg_malloc(ntups * sizeof(PartInfo));
+
+ i_partrelid = PQfnumber(res, "partrelid");
+ i_partparent = PQfnumber(res, "partparent");
+ i_partbound = PQfnumber(res, "partbound");
+
+ for (i = 0; i < ntups; i++)
+ {
+ partinfo[i].partrelid = atooid(PQgetvalue(res, i, i_partrelid));
+ partinfo[i].partparent = atooid(PQgetvalue(res, i, i_partparent));
+ partinfo[i].partdef = pg_strdup(PQgetvalue(res, i, i_partbound));
+ }
+
+ PQclear(res);
+
+ destroyPQExpBuffer(query);
+
+ return partinfo;
+}
+
+/*
* getIndexes
* get information about every index on a dumpable table
*
@@ -15133,6 +15190,17 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
if (tbinfo->reloftype && !dopt->binary_upgrade)
appendPQExpBuffer(q, " OF %s", tbinfo->reloftype);
+ if (tbinfo->partitionOf && !dopt->binary_upgrade)
+ {
+ TableInfo *parentRel = tbinfo->partitionOf;
+
+ appendPQExpBuffer(q, " PARTITION OF ");
+ if (parentRel->dobj.namespace != tbinfo->dobj.namespace)
+ appendPQExpBuffer(q, "%s.",
+ fmtId(parentRel->dobj.namespace->dobj.name));
+ appendPQExpBufferStr(q, fmtId(parentRel->dobj.name));
+ }
+
if (tbinfo->relkind != RELKIND_MATVIEW)
{
/* Dump the attributes */
@@ -15161,8 +15229,11 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
(!tbinfo->inhNotNull[j] ||
dopt->binary_upgrade));
- /* Skip column if fully defined by reloftype */
- if (tbinfo->reloftype &&
+ /*
+ * Skip column if fully defined by reloftype or the
+ * partition parent.
+ */
+ if ((tbinfo->reloftype || tbinfo->partitionOf) &&
!has_default && !has_notnull && !dopt->binary_upgrade)
continue;
@@ -15191,7 +15262,8 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
}
/* Attribute type */
- if (tbinfo->reloftype && !dopt->binary_upgrade)
+ if ((tbinfo->reloftype || tbinfo->partitionOf) &&
+ !dopt->binary_upgrade)
{
appendPQExpBufferStr(q, " WITH OPTIONS");
}
@@ -15256,15 +15328,22 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
if (actual_atts)
appendPQExpBufferStr(q, "\n)");
- else if (!(tbinfo->reloftype && !dopt->binary_upgrade))
+ else if (!((tbinfo->reloftype || tbinfo->partitionOf) &&
+ !dopt->binary_upgrade))
{
/*
* We must have a parenthesized attribute list, even though
- * empty, when not using the OF TYPE syntax.
+ * empty, when not using the OF TYPE or PARTITION OF syntax.
*/
appendPQExpBufferStr(q, " (\n)");
}
+ if (tbinfo->partitiondef && !dopt->binary_upgrade)
+ {
+ appendPQExpBufferStr(q, "\n");
+ appendPQExpBufferStr(q, tbinfo->partitiondef);
+ }
+
if (numParents > 0 && !dopt->binary_upgrade)
{
appendPQExpBufferStr(q, "\nINHERITS (");
@@ -15433,6 +15512,15 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
tbinfo->reloftype);
}
+ if (tbinfo->partitionOf)
+ {
+ appendPQExpBufferStr(q, "\n-- For binary upgrade, set up partitions this way.\n");
+ appendPQExpBuffer(q, "ALTER TABLE ONLY %s ATTACH PARTITION %s %s;\n",
+ fmtId(tbinfo->partitionOf->dobj.name),
+ tbinfo->dobj.name,
+ tbinfo->partitiondef);
+ }
+
appendPQExpBufferStr(q, "\n-- For binary upgrade, set heap's relfrozenxid and relminmxid\n");
appendPQExpBuffer(q, "UPDATE pg_catalog.pg_class\n"
"SET relfrozenxid = '%u', relminmxid = '%u'\n"
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 3094425..e3309b6 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -293,6 +293,8 @@ typedef struct _tableInfo
struct _tableDataInfo *dataObj; /* TableDataInfo, if dumping its data */
int numTriggers; /* number of triggers for table */
struct _triggerInfo *triggers; /* array of TriggerInfo structs */
+ struct _tableInfo *partitionOf; /* TableInfo for the partition parent */
+ char *partitiondef; /* partition key definition */
} TableInfo;
typedef struct _attrDefInfo
@@ -433,6 +435,15 @@ typedef struct _inhInfo
Oid inhparent; /* OID of its parent */
} InhInfo;
+/* PartInfo isn't a DumpableObject, just temporary state */
+typedef struct _partInfo
+{
+ Oid partrelid; /* OID of a partition */
+ Oid partparent; /* OID of its parent */
+ char *partdef; /* partition bound definition */
+} PartInfo;
+
+
typedef struct _prsInfo
{
DumpableObject dobj;
@@ -599,6 +610,7 @@ extern ConvInfo *getConversions(Archive *fout, int *numConversions);
extern TableInfo *getTables(Archive *fout, int *numTables);
extern void getOwnedSeqs(Archive *fout, TableInfo tblinfo[], int numTables);
extern InhInfo *getInherits(Archive *fout, int *numInherits);
+extern PartInfo *getPartitions(Archive *fout, int *numPartitions);
extern void getIndexes(Archive *fout, TableInfo tblinfo[], int numTables);
extern void getConstraints(Archive *fout, TableInfo tblinfo[], int numTables);
extern RuleInfo *getRules(Archive *fout, int *numRules);
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 78a1fdb..6bbfc9a 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -1694,6 +1694,34 @@ describeOneTableDetails(const char *schemaname,
}
/* Make footers */
+ if (pset.sversion >= 90600)
+ {
+ /* Get the partition information */
+ PGresult *result;
+ char *parent_name;
+ char *partdef;
+
+ printfPQExpBuffer(&buf,
+ "SELECT inhparent::pg_catalog.regclass, pg_get_expr(partbound, partrelid)"
+ " FROM pg_catalog.pg_partition p"
+ " JOIN pg_catalog.pg_inherits i"
+ " ON p.partrelid = i.inhrelid"
+ " WHERE p.partrelid = '%s';", oid);
+ result = PSQLexec(buf.data);
+ if (!result)
+ goto error_return;
+
+ if (PQntuples(result) > 0)
+ {
+ parent_name = PQgetvalue(result, 0, 0);
+ partdef = PQgetvalue(result, 0, 1);
+ printfPQExpBuffer(&tmpbuf, _("Partition Of: %s %s"), parent_name,
+ partdef);
+ printTableAddFooter(&cont, tmpbuf.data);
+ PQclear(result);
+ }
+ }
+
if (pset.sversion >= 90600 && tableinfo.relkind == 'P')
{
/* Get the partition key information */
@@ -2455,8 +2483,12 @@ describeOneTableDetails(const char *schemaname,
PQclear(result);
}
- /* print inherited tables */
- printfPQExpBuffer(&buf, "SELECT c.oid::pg_catalog.regclass FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i WHERE c.oid=i.inhparent AND i.inhrelid = '%s' ORDER BY inhseqno;", oid);
+ /* print inherited tables (exclude, if parent is a partitioned table) */
+ printfPQExpBuffer(&buf,
+ "SELECT c.oid::pg_catalog.regclass"
+ " FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i"
+ " WHERE c.oid=i.inhparent AND i.inhrelid = '%s'"
+ " AND c.relkind != 'P' ORDER BY inhseqno;", oid);
result = PSQLexec(buf.data);
if (!result)
@@ -2485,9 +2517,15 @@ describeOneTableDetails(const char *schemaname,
PQclear(result);
}
- /* print child tables */
+ /* print child tables (exclude, if parent is a partitioned table) */
if (pset.sversion >= 80300)
- printfPQExpBuffer(&buf, "SELECT c.oid::pg_catalog.regclass FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i WHERE c.oid=i.inhrelid AND i.inhparent = '%s' ORDER BY c.oid::pg_catalog.regclass::pg_catalog.text;", oid);
+ printfPQExpBuffer(&buf,
+ "SELECT c.oid::pg_catalog.regclass"
+ " FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i"
+ " WHERE c.oid=i.inhrelid AND"
+ " i.inhparent = '%s' AND"
+ " EXISTS (SELECT 1 FROM pg_class c WHERE c.oid = '%s' AND c.relkind != 'P')"
+ " ORDER BY c.oid::pg_catalog.regclass::pg_catalog.text;", oid, oid);
else
printfPQExpBuffer(&buf, "SELECT c.oid::pg_catalog.regclass FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i WHERE c.oid=i.inhrelid AND i.inhparent = '%s' ORDER BY c.relname;", oid);
diff --git a/src/include/catalog/indexing.h b/src/include/catalog/indexing.h
index cadb741..73786c0 100644
--- a/src/include/catalog/indexing.h
+++ b/src/include/catalog/indexing.h
@@ -322,6 +322,9 @@ DECLARE_UNIQUE_INDEX(pg_replication_origin_roname_index, 6002, on pg_replication
DECLARE_UNIQUE_INDEX(pg_partitioned_partedrelid_index, 3351, on pg_partitioned using btree(partedrelid oid_ops));
#define PartitionedRelidIndexId 3351
+DECLARE_UNIQUE_INDEX(pg_partition_partrelid_index, 3354, on pg_partition using btree(partrelid oid_ops));
+#define PartitionRelidIndexId 3354
+
/* last step of initialization script: build the indexes declared above */
BUILD_INDICES
diff --git a/src/include/catalog/partition.h b/src/include/catalog/partition.h
index 31dbe17..6204ad2 100644
--- a/src/include/catalog/partition.h
+++ b/src/include/catalog/partition.h
@@ -42,6 +42,53 @@ typedef struct PartitionKeyData
typedef struct PartitionKeyData *PartitionKey;
+/* Internal representation of a list partition bound */
+typedef struct PartitionListBound
+{
+ bool contains_null;
+ int nvalues; /* number of values in the following array */
+ Datum *values; /* values contained in the list */
+} PartitionListBound;
+
+/* Internal representation of a range partition bound */
+typedef struct
+{
+ Datum *val; /* composite bound value, if any */
+ bool infinite; /* bound is +/- infinity */
+ bool inclusive; /* bound is inclusive (vs exclusive) */
+ bool lower; /* this is the lower (vs upper) bound */
+} PartitionRangeBound;
+
+typedef struct PartitionDescData *PartitionDesc;
+
+/*
+ * Partition tree node
+ *
+ * pkinfo PartitionKey executor state
+ * pdesc info about immediate partitions (see
+ PartitionDescData)
+ * index index within 0..(parent_pdesc->nparts - 1)
+ * num_leaf_partitions number of leaf partitions in the partition
+ * tree rooted at this node
+ * offset index (0-based) of the first leaf partition
+ * in the partition tree rooted at this node
+ * downlink link to the leftmost child node
+ * next link to the right sibling node on a given level
+ */
+typedef struct PartitionDescNodeData
+{
+ PartitionDesc pdesc;
+ Oid relid;
+ int index;
+ int offset;
+ int num_leaf_partitions;
+
+ struct PartitionDescNodeData *downlink;
+ struct PartitionDescNodeData *next;
+} PartitionDescNodeData;
+
+typedef struct PartitionDescNodeData *PartitionDescNode;
+
extern void StorePartitionKey(Relation rel,
char strategy,
int16 partnatts,
@@ -53,4 +100,14 @@ extern void RelationBuildPartitionKey(Relation relation);
extern void FreePartitionKey(PartitionKey key);
extern bool relid_is_partitioned(Oid relid);
+extern void StorePartitionBound(Oid relid, Oid parentId, Node *bound);
+extern void RemovePartitionEntryByRelId(Oid relid);
+extern bool relid_is_partition(Oid relid);
+extern Oid get_partition_parent(Oid relid);
+extern void RelationDropPartitions(Oid relid);
+extern Node *RelationGetPartitionCheckExpr(Relation rel);
+extern Expr *get_check_expr_from_partbound(Relation rel, Relation parent,
+ Node *bound);
+extern List *get_leaf_partition_oids(PartitionDescNode pdnode);
+extern PartitionDescNode RelationGetPartitionDescNode(Relation rel);
#endif /* PARTITION_H */
diff --git a/src/include/catalog/pg_partition.h b/src/include/catalog/pg_partition.h
new file mode 100644
index 0000000..d31a6b7
--- /dev/null
+++ b/src/include/catalog/pg_partition.h
@@ -0,0 +1,54 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_partition.h
+ * definition of the system "partition" relation (pg_partition)
+ * along with the relation's initial contents.
+ *
+ *
+ * Portions Copyright (c) 1996-2015, PostgreSQL Global Development Group
+ *
+ * $PostgreSQL: pgsql/src/include/catalog/pg_partition.h $
+ *
+ * NOTES
+ * the genbki.sh script reads this file and generates .bki
+ * information from the DATA() statements.
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_PARTITION_H
+#define PG_PARTITION_H
+
+#include "catalog/genbki.h"
+
+/* ----------------
+ * pg_partitioned_rel definition. cpp turns this into
+ * typedef struct FormData_pg_partitioned_rel
+ * ----------------
+ */
+#define PartitionRelationId 3353
+
+CATALOG(pg_partition,3353) BKI_WITHOUT_OIDS
+{
+ Oid partrelid; /* partition relation OID */
+
+#ifdef CATALOG_VARLEN /* variable-length fields start here */
+ pg_node_tree partbound; /* node representation of partition bound */
+#endif
+} FormData_pg_partition;
+
+/* ----------------
+ * Form_pg_partition corresponds to a pointer to a tuple with
+ * the format of pg_partition relation.
+ * ----------------
+ */
+typedef FormData_pg_partition *Form_pg_partition;
+
+/* ----------------
+ * compiler constants for pg_partition
+ * ----------------
+ */
+#define Natts_pg_partition 2
+#define Anum_pg_partition_partrelid 1
+#define Anum_pg_partition_partbound 2
+
+#endif /* PG_PARTITION_H */
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index ee4e189..74288e2 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -319,6 +319,8 @@ typedef struct JunkFilter
* projectReturning for computing a RETURNING list
* onConflictSetProj for computing ON CONFLICT DO UPDATE SET
* onConflictSetWhere list of ON CONFLICT DO UPDATE exprs (qual)
+ * PartitionCheck partition check expression
+ * PartitionCheckExpr partition check expression state
* ----------------
*/
typedef struct ResultRelInfo
@@ -343,6 +345,8 @@ typedef struct ResultRelInfo
ProjectionInfo *ri_projectReturning;
ProjectionInfo *ri_onConflictSetProj;
List *ri_onConflictSetWhere;
+ Expr *ri_PartitionCheck;
+ List *ri_PartitionCheckExpr;
} ResultRelInfo;
/* ----------------
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index cf91ef8..c3177d7 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -91,6 +91,10 @@ typedef struct RelationData
struct RowSecurityDesc *rd_rsdesc; /* row security policies, or NULL */
struct PartitionKeyData *rd_partkey; /* partition key, or NULL */
+ MemoryContext rd_pdcxt; /* private context for partdesc */
+ struct PartitionDescData *rd_partdesc; /* partitions, or NULL */
+ Node *rd_partcheck; /* partition CHECK constraint expression
+ * tree */
/* data managed by RelationGetIndexList: */
List *rd_indexlist; /* list of OIDs of indexes on relation */
diff --git a/src/include/utils/syscache.h b/src/include/utils/syscache.h
index e727842..4e493e0 100644
--- a/src/include/utils/syscache.h
+++ b/src/include/utils/syscache.h
@@ -72,6 +72,7 @@ enum SysCacheIdentifier
OPEROID,
OPFAMILYAMNAMENSP,
OPFAMILYOID,
+ PARTRELID,
PARTEDRELID,
PROCNAMEARGSNSP,
PROCOID,
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index b1e473d..57bde9d 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -118,6 +118,7 @@ pg_namespace|t
pg_opclass|t
pg_operator|t
pg_opfamily|t
+pg_partition|t
pg_partitioned|t
pg_pltemplate|t
pg_policy|t
--
1.7.1
[text/x-diff] 0005-Introduce-tuple-routing-for-partitioned-tables-v3.patch (27.8K, ../../57183572.8020900@lab.ntt.co.jp/6-0005-Introduce-tuple-routing-for-partitioned-tables-v3.patch)
download | inline diff:
From 441cfbaa87276ff0a5956ed334cce0ab4eb0e6bd Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Fri, 18 Mar 2016 19:48:49 +0900
Subject: [PATCH 5/5] Introduce tuple routing for partitioned tables.
A tuple inserted into a partitioned table using insert or copy is now
routed to the appropriate leaf partition, recursively.
State required for the same is built in advance by BeginCopy() and
ExecInitModifyTable(). It consists of N ResultRelInfos and pointers
to TupleConversionMap structs, where N is the number of leaf partitions
in the multi-level partition tree. One needs only to get a pointer to the
root PartitionDescNode node for the tree. A tuple conversion map is only
allocated if the tuple descriptor of a leaf partition differs from that
of the root table. Partition check expression is not initialized for
leaf partition result relations, as they are redundant in this case.
Whether or not to build one is now determined by new parameter
load_partition_check of InitResultRelInfo().
CopyFrom() or ExecInsert() passes the root node (set up initially, as
just mentioned) along with the tuple slot to get_partition_for_tuple()
which traverses the tree to determine the appropriate leaf partition for
the tuple. It returns the index (0..N) of the leaf partition. Caller
then switches the result relation (ResultRelInfo) appropriately and
performs the tuple conversion, if required, before following up with
the row triggers, constraints, actual heap and index insertion.
* Following requires further consideration:
Both CopyFrom() and ExecInsert() switch to the partition before invoking
BR triggers and back after AR trigger are processed. That ignores parent's
triggers completely. Note that those triggers are not required to be present
in each partition. OTOH, constraints are fine (both check and not null),
since parent's constraints are present in partitions.
---
src/backend/catalog/partition.c | 318 ++++++++++++++++++++++++++++++++
src/backend/commands/copy.c | 97 +++++++++-
src/backend/commands/tablecmds.c | 1 +
src/backend/executor/execMain.c | 44 +++++-
src/backend/executor/nodeModifyTable.c | 79 ++++++++
src/backend/parser/analyze.c | 8 +
src/include/catalog/partition.h | 24 +++
src/include/executor/executor.h | 6 +
src/include/nodes/execnodes.h | 10 +
9 files changed, 582 insertions(+), 5 deletions(-)
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index 46bbb9e..abec2b8 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -131,6 +131,21 @@ static Node *generate_partition_check_expr(Relation rel);
static PartitionDescNode GetPartitionDescNodeRecurse(Relation rel, int offset);
static int get_leaf_partition_count(PartitionDescNode pdnode);
+static PartitionKeyExecInfo *BuildPartitionKeyExecInfo(Relation rel);
+static void FormPartitionKeyDatum(PartitionKeyExecInfo *pkinfo,
+ TupleTableSlot *slot,
+ EState *estate,
+ Datum *values,
+ bool *isnull);
+static int list_partition_for_tuple(PartitionKey key, PartitionDesc pdesc,
+ Datum value, bool isnull);
+static int range_partition_for_tuple(PartitionKey key, PartitionDesc pdesc,
+ Datum *values);
+static int range_partition_bsearch(PartitionKey key, PartitionDesc pdesc,
+ Datum *values);
+static bool rightof(PartitionKey key, Datum *values, PartitionRangeBound *bound);
+static bool leftof(PartitionKey key, Datum *values, PartitionRangeBound *bound);
+
/*
* StorePartitionKey
* Store the partition keys of rel into pg_partitioned catalog
@@ -1863,6 +1878,7 @@ GetPartitionDescNodeRecurse(Relation rel, int offset)
/* First build our own node */
root = (PartitionDescNode) palloc0(sizeof(PartitionDescNodeData));
+ root->pkinfo = BuildPartitionKeyExecInfo(rel);
root->pdesc = pdesc;
root->relid = RelationGetRelid(rel);
root->offset = offset;
@@ -1980,3 +1996,305 @@ get_leaf_partition_oids(PartitionDescNode pdnode)
return result;
}
+
+/* ----------------
+ * BuildPartitionKeyExecInfo
+ * Construct a list of PartitionKeyExecInfo records for an open
+ * relation
+ *
+ * PartitionKeyExecInfo stores the information about the partition key
+ * that's needed when inserting tuples into a partitioned table; especially,
+ * partition key expression state if there are any expression columns in
+ * the partition key. Normally we build a PartitionKeyExecInfo for a
+ * partitioned table just once per command, and then use it for (potentially)
+ * many tuples.
+ * ----------------
+ */
+static PartitionKeyExecInfo *
+BuildPartitionKeyExecInfo(Relation rel)
+{
+ PartitionKeyExecInfo *pkinfo;
+
+ pkinfo = (PartitionKeyExecInfo *) palloc0(sizeof(PartitionKeyExecInfo));
+ pkinfo->pi_Key = CopyPartitionKey(rel->rd_partkey);
+ pkinfo->pi_ExpressionState = NIL;
+
+ return pkinfo;
+}
+
+/*
+ * get_partition_for_tuple
+ * Recursively finds the "leaf" partition for tuple (slot)
+ *
+ * Returns -1 if no partition is found and sets *failed_at to the OID of
+ * the partitioned table whose partition was not found.
+ */
+int
+get_partition_for_tuple(PartitionDescNode pdnode,
+ TupleTableSlot *slot,
+ EState *estate,
+ Oid *failed_at)
+{
+ PartitionKeyExecInfo *pkinfo = pdnode->pkinfo;
+ PartitionDescNode node;
+ Datum values[PARTITION_MAX_KEYS];
+ bool isnull[PARTITION_MAX_KEYS];
+ int i;
+ int index;
+
+ if (pdnode->pdesc->nparts == 0)
+ return -1;
+
+ /* Extract partition key from tuple */
+ Assert(GetPerTupleExprContext(estate)->ecxt_scantuple == slot);
+ FormPartitionKeyDatum(pkinfo, slot, estate, values, isnull);
+
+ /* Disallow nulls, if range partition key */
+ for (i = 0; i < pkinfo->pi_Key->partnatts; i++)
+ if (isnull[i] && pkinfo->pi_Key->strategy == PARTITION_STRAT_RANGE)
+ ereport(ERROR,
+ (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+ errmsg("range partition key contains null")));
+
+ switch (pkinfo->pi_Key->strategy)
+ {
+ case PARTITION_STRAT_LIST:
+ index = list_partition_for_tuple(pkinfo->pi_Key, pdnode->pdesc,
+ values[0], isnull[0]);
+ break;
+
+ case PARTITION_STRAT_RANGE:
+ index = range_partition_for_tuple(pkinfo->pi_Key, pdnode->pdesc,
+ values);
+ break;
+ }
+
+ /* Don't recurse, if no partition exists for the key at this level... */
+ if (index < 0)
+ {
+ *failed_at = pdnode->relid;
+ return index;
+ }
+
+ /* ... or, if the index'th partition is a leaf partition. */
+ if (!relid_is_partitioned(pdnode->pdesc->oids[index]))
+ {
+ PartitionDescNode prev;
+
+ /* Patiently determine own leaf partition index */
+ prev = node = pdnode->downlink;
+ if (node && node->index < index)
+ {
+ while (node)
+ {
+ if (node->index > index)
+ {
+ node = prev;
+ break;
+ }
+
+ prev = node;
+ node = node->next;
+ }
+
+ if (!node)
+ node = prev;
+
+ /*
+ * Account for siblings on left that are partition trees and those
+ * that are leaf partitions themsleves.
+ */
+ return node->offset + node->num_leaf_partitions +
+ (index - node->index - 1);
+ }
+ else
+ /* Only leaf partitions in pdnode->offset..index */
+ return pdnode->offset + index;
+
+ }
+
+ /* Recurse, by locating the index'th partition's node */
+ node = pdnode->downlink;
+ while (node->next != NULL && node->index != index)
+ node = node->next;
+ Assert (node != NULL);
+
+ return get_partition_for_tuple(node, slot, estate, failed_at);
+}
+
+/*
+ * FormPartitionKeyDatum
+ * Construct values[] and isnull[] arrays for partition key columns
+ */
+static void
+FormPartitionKeyDatum(PartitionKeyExecInfo *pkinfo,
+ TupleTableSlot *slot,
+ EState *estate,
+ Datum *values,
+ bool *isnull)
+{
+ ListCell *partexpr_item;
+ int i;
+
+ if (pkinfo->pi_Key->partexprs != NIL && pkinfo->pi_ExpressionState == NIL)
+ {
+ /* First time through, set up expression evaluation state */
+ pkinfo->pi_ExpressionState = (List *)
+ ExecPrepareExpr((Expr *) pkinfo->pi_Key->partexprs,
+ estate);
+ /* Check caller has set up context correctly */
+ Assert(GetPerTupleExprContext(estate)->ecxt_scantuple == slot);
+ }
+
+ partexpr_item = list_head(pkinfo->pi_ExpressionState);
+ for (i = 0; i < pkinfo->pi_Key->partnatts; i++)
+ {
+ AttrNumber keycol = pkinfo->pi_Key->partattrs[i];
+ Datum pkDatum;
+ bool isNull;
+
+ if (keycol != 0)
+ {
+ /* Plain column; get the value directly from the heap tuple */
+ pkDatum = slot_getattr(slot, keycol, &isNull);
+ }
+ else
+ {
+ /* Expression; need to evaluate it */
+ if (partexpr_item == NULL)
+ elog(ERROR, "wrong number of partition key expressions");
+ pkDatum = ExecEvalExprSwitchContext((ExprState *) lfirst(partexpr_item),
+ GetPerTupleExprContext(estate),
+ &isNull,
+ NULL);
+ partexpr_item = lnext(partexpr_item);
+ }
+ values[i] = pkDatum;
+ isnull[i] = isNull;
+ }
+
+ if (partexpr_item != NULL)
+ elog(ERROR, "wrong number of partition key expressions");
+}
+
+/*
+ * list_partition_for_tuple
+ * Find the list partition for a tuple
+ *
+ * Returns -1 if none found.
+ */
+static int
+list_partition_for_tuple(PartitionKey key, PartitionDesc pdesc,
+ Datum value, bool isnull)
+{
+ int i;
+
+ Assert(pdesc->nparts > 0);
+
+ for (i = 0; i < pdesc->nparts; i++)
+ {
+ int j;
+
+ if (isnull)
+ {
+ if (pdesc->lists[i]->contains_null)
+ return i;
+
+ continue;
+ }
+
+ for (j = 0; j < pdesc->lists[i]->nvalues; j++)
+ {
+ int32 cmpval;
+
+ cmpval = DatumGetInt32(FunctionCall2Coll(&key->partsupfunc[0],
+ key->tcinfo->typcoll[0],
+ pdesc->lists[i]->values[j],
+ value));
+ if (!cmpval)
+ return i;
+ }
+ }
+
+ return -1;
+}
+
+/*
+ * range_partition_for_tuple
+ * Search the range partition for a range key ('values')
+ *
+ * Returns -1 if none found.
+ */
+static int
+range_partition_for_tuple(PartitionKey key, PartitionDesc pdesc, Datum *values)
+{
+ Assert(pdesc->nparts > 0);
+
+ return range_partition_bsearch(key, pdesc, values);
+}
+
+/*
+ * range_partition_bsearch
+ * Workhorse of range_partition_for_tuple
+ */
+static int
+range_partition_bsearch(PartitionKey key, PartitionDesc pdesc,
+ Datum *values)
+{
+ int low, high;
+
+ /* Good ol' bsearch */
+ low = 0;
+ high = pdesc->nparts - 1;
+ while (low <= high)
+ {
+ int idx = (low + high) / 2;
+
+ if (pdesc->rangeuppers[idx]->infinite)
+ {
+ if (rightof(key, values, pdesc->rangelowers[idx]))
+ return idx;
+
+ break;
+ }
+ else if (leftof(key, values, pdesc->rangeuppers[idx]))
+ {
+ if (pdesc->rangelowers[idx]->infinite)
+ return idx;
+
+ if (rightof(key, values, pdesc->rangelowers[idx]))
+ return idx;
+
+ high = idx - 1;
+ continue;
+ }
+
+ low = idx + 1;
+ }
+
+ return -1;
+}
+
+/* Does range key lie to the right of partition bound */
+static bool
+rightof(PartitionKey key, Datum *values, PartitionRangeBound *bound)
+{
+ int32 cmpval = compare_range_keys(key, values, bound->val);
+
+ if (!cmpval)
+ return bound->lower ? bound->inclusive : !bound->inclusive;
+
+ return cmpval > 0;
+}
+
+/* Does range key lie to the left of partition bound */
+static bool
+leftof(PartitionKey key, Datum *values, PartitionRangeBound *bound)
+{
+ int32 cmpval = compare_range_keys(key, values, bound->val);
+
+ if (!cmpval)
+ return !bound->lower ? bound->inclusive : !bound->inclusive;
+
+ return cmpval < 0;
+}
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 14e81de..9c9b401 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -161,6 +161,10 @@ typedef struct CopyStateData
ExprState **defexprs; /* array of default att expressions */
bool volatile_defexprs; /* is any of defexprs volatile? */
List *range_table;
+ PartitionDescNode pdnode; /* partition descriptor node tree */
+ ResultRelInfo *partitions;
+ TupleConversionMap **partition_tupconv_maps;
+ int num_partitions;
/*
* These variables are used to reduce overhead in textual COPY FROM.
@@ -1364,6 +1368,50 @@ BeginCopy(bool is_from,
(errcode(ERRCODE_UNDEFINED_COLUMN),
errmsg("table \"%s\" does not have OIDs",
RelationGetRelationName(cstate->rel))));
+
+ /* Initialize state for CopyFrom tuple routing */
+ if (is_from && rel->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+ {
+ List *leaf_part_oids;
+ ListCell *cell;
+ int i;
+ int num_leaf_parts;
+ ResultRelInfo *leaf_rel_rri;
+
+ cstate->pdnode = RelationGetPartitionDescNode(rel);
+ leaf_part_oids = get_leaf_partition_oids(cstate->pdnode);
+ num_leaf_parts = list_length(leaf_part_oids);
+
+ cstate->num_partitions = num_leaf_parts;
+ cstate->partitions = (ResultRelInfo *)
+ palloc0(num_leaf_parts * sizeof(ResultRelInfo));
+ cstate->partition_tupconv_maps = (TupleConversionMap **)
+ palloc0(num_leaf_parts * sizeof(TupleConversionMap *));
+
+ leaf_rel_rri = cstate->partitions;
+ i = 0;
+ foreach(cell, leaf_part_oids)
+ {
+ Relation leaf_rel;
+
+ leaf_rel = heap_open(lfirst_oid(cell), RowExclusiveLock);
+ InitResultRelInfo(leaf_rel_rri,
+ leaf_rel,
+ 1, /* dummy */
+ false, /* no need for partition check */
+ 0);
+
+ if (!equalTupleDescs(tupDesc, RelationGetDescr(leaf_rel)))
+ cstate->partition_tupconv_maps[i] =
+ convert_tuples_by_name(tupDesc,
+ RelationGetDescr(leaf_rel),
+ gettext_noop("could not convert row type"));
+
+ heap_close(leaf_rel, NoLock); /* keep the lock */
+ leaf_rel_rri++;
+ i++;
+ }
+ }
}
else
{
@@ -2216,6 +2264,7 @@ CopyFrom(CopyState cstate)
Datum *values;
bool *nulls;
ResultRelInfo *resultRelInfo;
+ ResultRelInfo *saved_resultRelInfo = NULL;
EState *estate = CreateExecutorState(); /* for ExecConstraints() */
ExprContext *econtext;
TupleTableSlot *myslot;
@@ -2236,7 +2285,8 @@ CopyFrom(CopyState cstate)
Assert(cstate->rel);
- if (cstate->rel->rd_rel->relkind != RELKIND_RELATION)
+ if (cstate->rel->rd_rel->relkind != RELKIND_RELATION &&
+ cstate->rel->rd_rel->relkind != RELKIND_PARTITIONED_REL)
{
if (cstate->rel->rd_rel->relkind == RELKIND_VIEW)
ereport(ERROR,
@@ -2344,6 +2394,7 @@ CopyFrom(CopyState cstate)
InitResultRelInfo(resultRelInfo,
cstate->rel,
1, /* dummy rangetable index */
+ true, /* do load partition check expression */
0);
ExecOpenIndices(resultRelInfo, false);
@@ -2371,6 +2422,7 @@ CopyFrom(CopyState cstate)
if ((resultRelInfo->ri_TrigDesc != NULL &&
(resultRelInfo->ri_TrigDesc->trig_insert_before_row ||
resultRelInfo->ri_TrigDesc->trig_insert_instead_row)) ||
+ cstate->pdnode != NULL ||
cstate->volatile_defexprs)
{
useHeapMultiInsert = false;
@@ -2395,7 +2447,11 @@ CopyFrom(CopyState cstate)
values = (Datum *) palloc(tupDesc->natts * sizeof(Datum));
nulls = (bool *) palloc(tupDesc->natts * sizeof(bool));
- bistate = GetBulkInsertState();
+ if (useHeapMultiInsert)
+ bistate = GetBulkInsertState();
+ else
+ bistate = NULL;
+
econtext = GetPerTupleExprContext(estate);
/* Set up callback to identify error line number */
@@ -2447,6 +2503,31 @@ CopyFrom(CopyState cstate)
slot = myslot;
ExecStoreTuple(tuple, slot, InvalidBuffer, false);
+ /* Determine the partition */
+ saved_resultRelInfo = resultRelInfo;
+ if (cstate->pdnode)
+ {
+ int i_leaf_partition;
+ TupleConversionMap *map;
+
+ econtext->ecxt_scantuple = slot;
+ i_leaf_partition = ExecFindPartition(resultRelInfo,
+ cstate->pdnode,
+ slot,
+ estate);
+ Assert(i_leaf_partition >= 0 &&
+ i_leaf_partition < cstate->num_partitions);
+
+ resultRelInfo = cstate->partitions + i_leaf_partition;
+ estate->es_result_relation_info = resultRelInfo;
+
+ map = cstate->partition_tupconv_maps[i_leaf_partition];
+ if (map)
+ tuple = do_convert_tuple(tuple, map);
+
+ tuple->t_tableOid = RelationGetRelid(resultRelInfo->ri_RelationDesc);
+ }
+
skip_tuple = false;
/* BEFORE ROW INSERT Triggers */
@@ -2497,7 +2578,8 @@ CopyFrom(CopyState cstate)
List *recheckIndexes = NIL;
/* OK, store the tuple and create index entries for it */
- heap_insert(cstate->rel, tuple, mycid, hi_options, bistate);
+ heap_insert(resultRelInfo->ri_RelationDesc,
+ tuple, mycid, hi_options, bistate);
if (resultRelInfo->ri_NumIndices > 0)
recheckIndexes = ExecInsertIndexTuples(slot, &(tuple->t_self),
@@ -2517,6 +2599,12 @@ CopyFrom(CopyState cstate)
* tuples inserted by an INSERT command.
*/
processed++;
+
+ if (saved_resultRelInfo)
+ {
+ resultRelInfo = saved_resultRelInfo;
+ estate->es_result_relation_info = resultRelInfo;
+ }
}
}
@@ -2530,7 +2618,8 @@ CopyFrom(CopyState cstate)
/* Done, clean up */
error_context_stack = errcallback.previous;
- FreeBulkInsertState(bistate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
MemoryContextSwitchTo(oldcontext);
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index b763a1a..a40bc61 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -1242,6 +1242,7 @@ ExecuteTruncate(TruncateStmt *stmt)
InitResultRelInfo(resultRelInfo,
rel,
0, /* dummy rangetable index */
+ false,
0);
resultRelInfo++;
}
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 23ed0b6..b151fb5 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -826,6 +826,7 @@ InitPlan(QueryDesc *queryDesc, int eflags)
InitResultRelInfo(resultRelInfo,
resultRelation,
resultRelationIndex,
+ true,
estate->es_instrument);
resultRelInfo++;
}
@@ -1214,6 +1215,7 @@ void
InitResultRelInfo(ResultRelInfo *resultRelInfo,
Relation resultRelationDesc,
Index resultRelationIndex,
+ bool load_partition_check,
int instrument_options)
{
MemSet(resultRelInfo, 0, sizeof(ResultRelInfo));
@@ -1251,7 +1253,8 @@ InitResultRelInfo(ResultRelInfo *resultRelInfo,
resultRelInfo->ri_ConstraintExprs = NULL;
resultRelInfo->ri_junkFilter = NULL;
resultRelInfo->ri_projectReturning = NULL;
- resultRelInfo->ri_PartitionCheck = (Expr *)
+ if (load_partition_check)
+ resultRelInfo->ri_PartitionCheck = (Expr *)
RelationGetPartitionCheckExpr(resultRelationDesc);
}
@@ -1315,6 +1318,7 @@ ExecGetTriggerResultRel(EState *estate, Oid relid)
InitResultRelInfo(rInfo,
rel,
0, /* dummy rangetable index */
+ true,
estate->es_instrument);
estate->es_trig_target_relations =
lappend(estate->es_trig_target_relations, rInfo);
@@ -2987,3 +2991,41 @@ EvalPlanQualEnd(EPQState *epqstate)
epqstate->planstate = NULL;
epqstate->origslot = NULL;
}
+
+int
+ExecFindPartition(ResultRelInfo *resultRelInfo, PartitionDescNode pdnode,
+ TupleTableSlot *slot, EState *estate)
+{
+ int i_leaf_partition;
+ Oid failed_at;
+
+ i_leaf_partition = get_partition_for_tuple(pdnode, slot, estate,
+ &failed_at);
+
+ if (i_leaf_partition < 0)
+ {
+ Relation rel = resultRelInfo->ri_RelationDesc;
+ char *val_desc;
+ Bitmapset *insertedCols,
+ *updatedCols,
+ *modifiedCols;
+ TupleDesc tupDesc = RelationGetDescr(rel);
+
+ insertedCols = GetInsertedColumns(resultRelInfo, estate);
+ updatedCols = GetUpdatedColumns(resultRelInfo, estate);
+ modifiedCols = bms_union(insertedCols, updatedCols);
+ val_desc = ExecBuildSlotValueDescription(RelationGetRelid(rel),
+ slot,
+ tupDesc,
+ modifiedCols,
+ 64);
+ Assert(OidIsValid(failed_at));
+ ereport(ERROR,
+ (errcode(ERRCODE_CHECK_VIOLATION),
+ errmsg("no partition of relation \"%s\" found for row",
+ get_rel_name(failed_at)),
+ val_desc ? errdetail("Failing row contains %s.", val_desc) : 0));
+ }
+
+ return i_leaf_partition;
+}
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 6ecb6be..18ce333 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -243,6 +243,7 @@ ExecInsert(ModifyTableState *mtstate,
{
HeapTuple tuple;
ResultRelInfo *resultRelInfo;
+ ResultRelInfo *saved_resultRelInfo = NULL;
Relation resultRelationDesc;
Oid newId;
List *recheckIndexes = NIL;
@@ -257,6 +258,31 @@ ExecInsert(ModifyTableState *mtstate,
* get information on the (current) result relation
*/
resultRelInfo = estate->es_result_relation_info;
+
+ saved_resultRelInfo = resultRelInfo;
+
+ if (mtstate->mt_partition_node)
+ {
+ int i_leaf_partition;
+ ExprContext *econtext = GetPerTupleExprContext(estate);
+ TupleConversionMap *map;
+
+ econtext->ecxt_scantuple = slot;
+ i_leaf_partition = ExecFindPartition(resultRelInfo,
+ mtstate->mt_partition_node,
+ slot,
+ estate);
+ Assert(i_leaf_partition >= 0 &&
+ i_leaf_partition < mtstate->mt_num_partitions);
+
+ resultRelInfo = mtstate->mt_partitions + i_leaf_partition;
+ estate->es_result_relation_info = resultRelInfo;
+
+ map = mtstate->mt_partition_tupconv_maps[i_leaf_partition];
+ if (map)
+ tuple = do_convert_tuple(tuple, map);
+ }
+
resultRelationDesc = resultRelInfo->ri_RelationDesc;
/*
@@ -496,6 +522,12 @@ ExecInsert(ModifyTableState *mtstate,
list_free(recheckIndexes);
+ if (saved_resultRelInfo)
+ {
+ resultRelInfo = saved_resultRelInfo;
+ estate->es_result_relation_info = resultRelInfo;
+ }
+
/*
* Check any WITH CHECK OPTION constraints from parent views. We are
* required to do this after testing all constraints and uniqueness
@@ -1550,6 +1582,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
Plan *subplan;
ListCell *l;
int i;
+ Relation rel;
/* check for unsupported flags */
Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));
@@ -1640,6 +1673,52 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
estate->es_result_relation_info = saved_resultRelInfo;
+ /* Build state for INSERT tuple routing */
+ rel = mtstate->resultRelInfo->ri_RelationDesc;
+ if (operation == CMD_INSERT && rel->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+ {
+ int i,
+ num_leaf_parts;
+ List *leaf_part_oids;
+ ListCell *cell;
+ ResultRelInfo *leaf_rel_rri;
+
+ mtstate->mt_partition_node = RelationGetPartitionDescNode(rel);
+ leaf_part_oids = get_leaf_partition_oids(mtstate->mt_partition_node);
+ num_leaf_parts = list_length(leaf_part_oids);
+
+ mtstate->mt_num_partitions = num_leaf_parts;
+ mtstate->mt_partitions = (ResultRelInfo *)
+ palloc0(num_leaf_parts * sizeof(ResultRelInfo));
+ mtstate->mt_partition_tupconv_maps = (TupleConversionMap **)
+ palloc0(num_leaf_parts * sizeof(TupleConversionMap *));
+
+ leaf_rel_rri = mtstate->mt_partitions;
+ i = 0;
+ foreach(cell, leaf_part_oids)
+ {
+ Relation leaf_rel;
+
+ leaf_rel = heap_open(lfirst_oid(cell), RowExclusiveLock);
+ InitResultRelInfo(leaf_rel_rri,
+ leaf_rel,
+ 1, /* dummy */
+ false, /* no need for partition checks */
+ eflags);
+
+ if (!equalTupleDescs(RelationGetDescr(rel),
+ RelationGetDescr(leaf_rel)))
+ mtstate->mt_partition_tupconv_maps[i] =
+ convert_tuples_by_name(RelationGetDescr(rel),
+ RelationGetDescr(leaf_rel),
+ gettext_noop("could not convert row type"));
+
+ heap_close(leaf_rel, NoLock); /* keep the lock */
+ leaf_rel_rri++;
+ i++;
+ }
+ }
+
/*
* Initialize any WITH CHECK OPTION constraints if needed.
*/
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index f55129a..802cd68 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -755,8 +755,16 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
/* Process ON CONFLICT, if any. */
if (stmt->onConflictClause)
+ {
+ /* Bail out if target relation is partitioned table */
+ if (relid_is_partitioned(pstate->p_target_rangetblentry->relid))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("ON CONFLICT clause is not supported with partitioned tables")));
+
qry->onConflict = transformOnConflictClause(pstate,
stmt->onConflictClause);
+ }
/*
* If we have a RETURNING clause, we need to add the target relation to
diff --git a/src/include/catalog/partition.h b/src/include/catalog/partition.h
index 6204ad2..f4840e4 100644
--- a/src/include/catalog/partition.h
+++ b/src/include/catalog/partition.h
@@ -14,6 +14,8 @@
#define PARTITION_H
#include "fmgr.h"
+#include "executor/tuptable.h"
+#include "nodes/execnodes.h"
#include "parser/parse_node.h"
#include "utils/relcache.h"
@@ -62,6 +64,22 @@ typedef struct
typedef struct PartitionDescData *PartitionDesc;
/*
+ * PartitionKeyExecInfo
+ *
+ * This struct holds the information needed to extract partition
+ * column values from a heap tuple.
+ *
+ * Key copy of the rd_partkey of rel
+ * ExpressionState exec state for expressions, or NIL if none
+ */
+typedef struct PartitionKeyExecInfo
+{
+ NodeTag type;
+ PartitionKey pi_Key;
+ List *pi_ExpressionState; /* list of ExprState */
+} PartitionKeyExecInfo;
+
+/*
* Partition tree node
*
* pkinfo PartitionKey executor state
@@ -77,6 +95,7 @@ typedef struct PartitionDescData *PartitionDesc;
*/
typedef struct PartitionDescNodeData
{
+ PartitionKeyExecInfo *pkinfo;
PartitionDesc pdesc;
Oid relid;
int index;
@@ -110,4 +129,9 @@ extern Expr *get_check_expr_from_partbound(Relation rel, Relation parent,
Node *bound);
extern List *get_leaf_partition_oids(PartitionDescNode pdnode);
extern PartitionDescNode RelationGetPartitionDescNode(Relation rel);
+
+extern int get_partition_for_tuple(PartitionDescNode pdnode,
+ TupleTableSlot *slot,
+ EState *estate,
+ Oid *failed_at);
#endif /* PARTITION_H */
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index 44fac27..d4e8b35 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -14,6 +14,7 @@
#ifndef EXECUTOR_H
#define EXECUTOR_H
+#include "catalog/partition.h"
#include "executor/execdesc.h"
#include "nodes/parsenodes.h"
@@ -188,6 +189,7 @@ extern void CheckValidResultRel(Relation resultRel, CmdType operation);
extern void InitResultRelInfo(ResultRelInfo *resultRelInfo,
Relation resultRelationDesc,
Index resultRelationIndex,
+ bool load_partition_check,
int instrument_options);
extern ResultRelInfo *ExecGetTriggerResultRel(EState *estate, Oid relid);
extern bool ExecContextForcesOids(PlanState *planstate, bool *hasoids);
@@ -211,6 +213,10 @@ extern void EvalPlanQualSetPlan(EPQState *epqstate,
extern void EvalPlanQualSetTuple(EPQState *epqstate, Index rti,
HeapTuple tuple);
extern HeapTuple EvalPlanQualGetTuple(EPQState *epqstate, Index rti);
+extern int ExecFindPartition(ResultRelInfo *resultRelInfo,
+ PartitionDescNode pdnode,
+ TupleTableSlot *slot,
+ EState *estate);
#define EvalPlanQualSetSlot(epqstate, slot) ((epqstate)->origslot = (slot))
extern void EvalPlanQualFetchRowMarks(EPQState *epqstate);
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 74288e2..f2f535e 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -16,6 +16,7 @@
#include "access/genam.h"
#include "access/heapam.h"
+#include "access/tupconvert.h"
#include "executor/instrument.h"
#include "lib/pairingheap.h"
#include "nodes/params.h"
@@ -1140,6 +1141,15 @@ typedef struct ModifyTableState
* tlist */
TupleTableSlot *mt_conflproj; /* CONFLICT ... SET ... projection
* target */
+ struct PartitionDescNodeData *mt_partition_node;
+ /* Partition descriptor node tree */
+ ResultRelInfo *mt_partitions; /* Per leaf partition target
+ * relations */
+ TupleConversionMap **mt_partition_tupconv_maps;
+ /* Per leaf partition
+ * tuple conversion map */
+ int mt_num_partitions; /* Number of leaf partition target
+ * relations in the above array */
} ModifyTableState;
/* ----------------
--
1.7.1
^ permalink raw reply [nested|flat] 161+ messages in thread
* Re: Declarative partitioning
@ 2016-04-26 08:46 Erik Rijkers <er@xs4all.nl>
parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
3 siblings, 1 reply; 161+ messages in thread
From: Erik Rijkers @ 2016-04-26 08:46 UTC (permalink / raw)
To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; +Cc: Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; Robert Haas <robertmhaas@gmail.com>; Alexander Korotkov <a.korotkov@postgrespro.ru>; Amit Langote <amitlangote09@gmail.com>; Corey Huinker <corey.huinker@gmail.com>; pgsql-hackers; pgsql-hackers-owner@postgresql.org
On 2016-04-15 04:35, Amit Langote wrote:
A quick test with:
> 0001-Add-syntax-to-specify-partition-key-v3.patch
> 0002-Infrastructure-for-creation-of-partitioned-tables-v3.patch
> 0003-Add-syntax-to-create-partitions-v3.patch
> 0004-Infrastructure-for-partition-metadata-storage-and-ma-v3.patch
> 0005-Introduce-tuple-routing-for-partitioned-tables-v3.patch
patches apply, build and make check ok.
There is somwthing wrong with indexes on child tables (and only with
higher rowcounts).
Surely the below code should give 6 rows; it actually does return 6 rows
without the indexes.
With indexes it returns 0 rows.
(but when doing the same test with low rowcounts, things are OK.)
thanks,
Erik Rijkers
(Linux Centos 6.6)
---------------------------------------
create table inh(a int, b int) partition by range ((a+b));
create table inh_1 partition of inh for values start ( 0) end (
10000);
create table inh_2 partition of inh for values start ( 10000) end (
20000);
create table inh_3 partition of inh for values start ( 20000) end (
100000);
create index inh_1_a_idx on inh_1 (a);
create index inh_2_a_idx on inh_2 (a);
create index inh_3_a_idx on inh_3 (a);
insert into inh select i, i as j from generate_series(1, 10000) as f(i);
analyze inh_1;
analyze inh_2;
analyze inh_3;
select 'inh' , count(*) from inh
union all select 'inh_1', count(*) from inh_1
union all select 'inh_2', count(*) from inh_2
union all select 'inh_3', count(*) from inh_3
;
explain analyze select * from inh where a between 10110 and 10115;
---------------------------------------
# output :
create table inh(a int, b int) partition by range ((a+b));
create table inh_1 partition of inh for values start ( 0) end (
10000); create index inh_1_a_idx on inh_1 (a);
create table inh_2 partition of inh for values start ( 10000) end (
20000); create index inh_2_a_idx on inh_2 (a);
create table inh_3 partition of inh for values start ( 20000) end (
100000); create index inh_3_a_idx on inh_3 (a);
insert into inh select i, i as j from generate_series(1, 10000) as f(i);
analyze inh_1;
analyze inh_2;
analyze inh_3;
select 'inh' , count(*) from inh
union all select 'inh_1', count(*) from inh_1
union all select 'inh_2', count(*) from inh_2
union all select 'inh_3', count(*) from inh_3
;
?column? | count
----------+-------
inh | 10000
inh_1 | 4999
inh_2 | 5000
inh_3 | 1
(4 rows)
explain analyze select * from inh where a between 10110 and 10115;
QUERY PLAN
-------------------------------------------------------------------------------------------------------------------------
Append (cost=0.00..17.37 rows=4 width=8) (actual time=0.023..0.023
rows=0 loops=1)
-> Seq Scan on inh (cost=0.00..0.00 rows=1 width=8) (actual
time=0.004..0.004 rows=0 loops=1)
Filter: ((a >= 10110) AND (a <= 10115))
-> Index Scan using inh_1_a_idx on inh_1 (cost=0.16..8.18 rows=1
width=8) (actual time=0.007..0.007 rows=0 loops=1)
Index Cond: ((a >= 10110) AND (a <= 10115))
-> Index Scan using inh_2_a_idx on inh_2 (cost=0.16..8.18 rows=1
width=8) (actual time=0.002..0.002 rows=0 loops=1)
Index Cond: ((a >= 10110) AND (a <= 10115))
-> Seq Scan on inh_3 (cost=0.00..1.01 rows=1 width=8) (actual
time=0.008..0.008 rows=0 loops=1)
Filter: ((a >= 10110) AND (a <= 10115))
Rows Removed by Filter: 1
Planning time: 0.858 ms
Execution time: 0.093 ms
(12 rows)
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 161+ messages in thread
* Re: Declarative partitioning
@ 2016-05-09 11:21 Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
1 sibling, 1 reply; 161+ messages in thread
From: Ashutosh Bapat @ 2016-05-09 11:21 UTC (permalink / raw)
To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; +Cc: Amit Langote <amitlangote09@gmail.com>; Ildar Musin <i.musin@postgrespro.ru>; Alexander Korotkov <a.korotkov@postgrespro.ru>; Robert Haas <robertmhaas@gmail.com>; Corey Huinker <corey.huinker@gmail.com>; pgsql-hackers
Hi Amit,
I am trying multi-column/expression partitions.
create table t1_multi_col (a int, b int) partition by range (a, b);
create table t1_mc_p1 partition of t1_multi_col for values start (1, 200)
end (100, 300);
create table t1_mc_p2 partition of t1_multi_col for values start (200, 1)
end (300, 100);
insert into t1_multi_col values (1, 250);
insert into t1_multi_col values (250, 1);
insert into t1_multi_col values (100, 100);
select tableoid::regclass, * from t1_multi_col;
tableoid | a | b
----------+-----+-----
t1_mc_p1 | 1 | 250
t1_mc_p1 | 100 | 100
t1_mc_p2 | 250 | 1
The row (100, 100) landed in t1_mc_p1 which has partition bounds as (1,
200) and (100, 300) which should not accept a row with b = 100. It looks
like the binary search got confused with the reversed order of ranges
(should that be allowed?)
Symantec of multiple columns for ranges (may be list as well) looks
confusing. The current scheme doesn't allow overlapping range for one of
the partitioning keys even if the combined range is non-overlapping.
create table t1_multi_col (a int, b int) partition by range (a, b);
create table t1_mc_p1 partition of t1_multi_col for values start (1, 100)
end (100, 200);
create table t1_mc_p2 partition of t1_multi_col for values start (1, 200)
end (100, 300);
ERROR: new partition's range overlaps with that of partition "t1_mc_p1" of
"t1_multi_col"
HINT: Please specify a range that does not overlap with any existing
partition's range.
create table t1_mc_p2 partition of t1_multi_col for values start (1, 300)
end (100, 400);
ERROR: new partition's range overlaps with that of partition "t1_mc_p1" of
"t1_multi_col"
HINT: Please specify a range that does not overlap with any existing
partition's range.
That should be better realised using subpartitioning on b. The question is,
if one column's value is enough to identify partition (since they can not
contain overlapping values for that column), why do we need mutliple
columns/expressions as partition keys? IIUC, all the other column does is
to disallow certain range of values for that column, which can better be
done by a CHECK constraint. It looks like Oracle looks at combined range
and not just one column.
On Thu, Apr 21, 2016 at 7:35 AM, Amit Langote <Langote_Amit_f8@lab.ntt.co.jp
> wrote:
>
> Hi Ildar,
>
> On 2016/04/21 1:06, Amit Langote wrote:
> > On Wed, Apr 20, 2016 at 11:46 PM, Ildar Musin <i.musin@postgrespro.ru>
> wrote:
> >> Crash occurs in get_check_expr_from_partbound(). It seems that function
> is
> >> not yet expecting an expression key and designed to handle only simple
> >> attributes keys. Backtrace:
> >
> > You're right, silly mistake. :-(
> >
> > Will fix
>
> Attached updated version fixes this. I'll take time to send the next
> version but I'd very much appreciate it if you keep reporting anything
> that doesn't look/work right like you did so far.
>
> Thanks,
> Amit
>
--
Best Wishes,
Ashutosh Bapat
EnterpriseDB Corporation
The Postgres Database Company
^ permalink raw reply [nested|flat] 161+ messages in thread
* Re: Declarative partitioning
@ 2016-05-10 10:40 Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
parent: Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
0 siblings, 1 reply; 161+ messages in thread
From: Amit Langote @ 2016-05-10 10:40 UTC (permalink / raw)
To: Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; +Cc: Amit Langote <amitlangote09@gmail.com>; Ildar Musin <i.musin@postgrespro.ru>; Alexander Korotkov <a.korotkov@postgrespro.ru>; Robert Haas <robertmhaas@gmail.com>; Corey Huinker <corey.huinker@gmail.com>; pgsql-hackers
Hi Ashutosh,
On 2016/05/09 20:21, Ashutosh Bapat wrote:
> Hi Amit,
> I am trying multi-column/expression partitions.
Thanks for the tests.
> create table t1_multi_col (a int, b int) partition by range (a, b);
> create table t1_mc_p1 partition of t1_multi_col for values start (1, 200)
> end (100, 300);
> create table t1_mc_p2 partition of t1_multi_col for values start (200, 1)
> end (300, 100);
> insert into t1_multi_col values (1, 250);
> insert into t1_multi_col values (250, 1);
> insert into t1_multi_col values (100, 100);
> select tableoid::regclass, * from t1_multi_col;
> tableoid | a | b
> ----------+-----+-----
> t1_mc_p1 | 1 | 250
> t1_mc_p1 | 100 | 100
> t1_mc_p2 | 250 | 1
> The row (100, 100) landed in t1_mc_p1 which has partition bounds as (1,
> 200) and (100, 300) which should not accept a row with b = 100. It looks
> like the binary search got confused with the reversed order of ranges
> (should that be allowed?)
It's useful to think of multi-column key as defining ranges of composite
values (tuples) instead of thinking in terms of ranges of values of
individual columns. That's how a row's partition key is compared against
individual partition bounds until a suitable partition is found (with
binary search that is), which uses record comparison logic as shown below:
postgres=# select (1, 200) <= (100, 100) AND (100, 100) < (100, 300);
?column?
----------
t
(1 row)
Which means the row (100, 100) belongs in the partition with the start
bound (1, 200) and the end bound (100, 300). Just like in composite value
case, comparison stops at some leading column that returns != 0 result.
So, in comparison (1, 200) <= (100, 100), the second column plays no role.
> Symantec of multiple columns for ranges (may be list as well) looks
Note that list partition key does not support multiple columns.
> confusing. The current scheme doesn't allow overlapping range for one of
> the partitioning keys even if the combined range is non-overlapping.
> create table t1_multi_col (a int, b int) partition by range (a, b);
> create table t1_mc_p1 partition of t1_multi_col for values start (1, 100)
> end (100, 200);
> create table t1_mc_p2 partition of t1_multi_col for values start (1, 200)
> end (100, 300);
> ERROR: new partition's range overlaps with that of partition "t1_mc_p1" of
> "t1_multi_col"
> HINT: Please specify a range that does not overlap with any existing
> partition's range.
> create table t1_mc_p2 partition of t1_multi_col for values start (1, 300)
> end (100, 400);
> ERROR: new partition's range overlaps with that of partition "t1_mc_p1" of
> "t1_multi_col"
> HINT: Please specify a range that does not overlap with any existing
> partition's range.
Ranges [ (1, 100), (100, 200) ) and [ (1, 200), (100, 300) ) do overlap:
postgres=# select (1, 100) <= (1, 200) AND (1, 200) < (100, 200);
?column?
----------
t
(1 row)
That is, (1, 200) is both the start element of the 2nd partition's range
and is contained in the first partition's range as illustrated above.
> That should be better realised using subpartitioning on b. The question is,
> if one column's value is enough to identify partition (since they can not
> contain overlapping values for that column), why do we need mutliple
> columns/expressions as partition keys? IIUC, all the other column does is
> to disallow certain range of values for that column, which can better be
> done by a CHECK constraint. It looks like Oracle looks at combined range
> and not just one column.
A more familiar example I have seen around the web illustrating
multi-column range partitioning is for something like (year, month, day)
triple. Consider the following example:
create table parted(year int, month int, day int)
partition by range (year, month, day);
create table part201605week1 partition of parted
for values start (2016, 5, 1) end (2016, 5, 8);
create table part201605week2 partition of parted
for values start (2016, 5, 8) end (2016, 5, 15);
create table part201605week3 partition of parted
for values start (2016, 5, 15) end (2016, 5, 22);
create table part201605week4 partition of parted
for values start (2016, 5, 22) end (2016, 5, 29);
create table part201605week5 partition of parted
for values start (2016, 5, 29) end (2016, 5, 31) inclusive;
create table part201606week1 partition of parted
for values start (2016, 6, 1) end (2016, 6, 8);
create table part201606week2 partition of parted
for values start (2016, 6, 8) end (2016, 6, 15);
create table part201606week3 partition of parted
for values start (2016, 6, 15) end (2016, 6, 22);
create table part201606week4 partition of parted
for values start (2016, 6, 2) end (2016, 6, 29);
create table part201606week4 partition of parted
for values start (2016, 6, 22) end (2016, 6, 29);
create table part201606week5 partition of parted
for values start (2016, 6, 29) end (2016, 6, 30) inclusive;
explain (costs off) select * from parted where day between 4 and 10;
QUERY PLAN
----------------------------------------------
Append
-> Seq Scan on parted
Filter: ((day >= 4) AND (day <= 10))
-> Seq Scan on part201605week1
Filter: ((day >= 4) AND (day <= 10))
-> Seq Scan on part201605week2
Filter: ((day >= 4) AND (day <= 10))
-> Seq Scan on part201606week1
Filter: ((day >= 4) AND (day <= 10))
-> Seq Scan on part201606week2
Filter: ((day >= 4) AND (day <= 10))
(11 rows)
explain (costs off) select * from parted where day between 4 and 10 and
month = 5;
QUERY PLAN
--------------------------------------------------------------
Append
-> Seq Scan on parted
Filter: ((day >= 4) AND (day <= 10) AND (month = 5))
-> Seq Scan on part201605week1
Filter: ((day >= 4) AND (day <= 10) AND (month = 5))
-> Seq Scan on part201605week2
Filter: ((day >= 4) AND (day <= 10) AND (month = 5))
(7 rows)
explain (costs off) select * from parted where month = 6;
QUERY PLAN
-----------------------------------
Append
-> Seq Scan on parted
Filter: (month = 6)
-> Seq Scan on part201606week1
Filter: (month = 6)
-> Seq Scan on part201606week2
Filter: (month = 6)
-> Seq Scan on part201606week3
Filter: (month = 6)
-> Seq Scan on part201606week4
Filter: (month = 6)
-> Seq Scan on part201606week5
Filter: (month = 6)
(13 rows)
Thanks,
Amit
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 161+ messages in thread
* Re: Declarative partitioning
@ 2016-05-11 02:26 Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
parent: Erik Rijkers <er@xs4all.nl>
0 siblings, 0 replies; 161+ messages in thread
From: Amit Langote @ 2016-05-11 02:26 UTC (permalink / raw)
To: Erik Rijkers <er@xs4all.nl>; +Cc: Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; Robert Haas <robertmhaas@gmail.com>; Alexander Korotkov <a.korotkov@postgrespro.ru>; Amit Langote <amitlangote09@gmail.com>; Corey Huinker <corey.huinker@gmail.com>; pgsql-hackers; pgsql-hackers-owner@postgresql.org
Hi Erik,
On 2016/04/26 17:46, Erik Rijkers wrote:
> On 2016-04-15 04:35, Amit Langote wrote:
>
> A quick test with:
>
>> 0001-Add-syntax-to-specify-partition-key-v3.patch
>> 0002-Infrastructure-for-creation-of-partitioned-tables-v3.patch
>> 0003-Add-syntax-to-create-partitions-v3.patch
>> 0004-Infrastructure-for-partition-metadata-storage-and-ma-v3.patch
>> 0005-Introduce-tuple-routing-for-partitioned-tables-v3.patch
>
> patches apply, build and make check ok.
Thanks for testing!
> There is somwthing wrong with indexes on child tables (and only with
> higher rowcounts).
There was an oversight in patch 0005 that caused partition indexes to not
be opened and tuples inserted into. Attached new version should have
fixed it.
> Surely the below code should give 6 rows; it actually does return 6 rows
> without the indexes.
> With indexes it returns 0 rows.
>
> (but when doing the same test with low rowcounts, things are OK.)
...
> ---------------------------------------
> create table inh(a int, b int) partition by range ((a+b));
> create table inh_1 partition of inh for values start ( 0) end ( 10000);
> create table inh_2 partition of inh for values start ( 10000) end ( 20000);
> create table inh_3 partition of inh for values start ( 20000) end ( 100000);
>
> create index inh_1_a_idx on inh_1 (a);
> create index inh_2_a_idx on inh_2 (a);
> create index inh_3_a_idx on inh_3 (a);
>
> insert into inh select i, i as j from generate_series(1, 10000) as f(i);
>
> analyze inh_1;
> analyze inh_2;
> analyze inh_3;
>
> select 'inh' , count(*) from inh
> union all select 'inh_1', count(*) from inh_1
> union all select 'inh_2', count(*) from inh_2
> union all select 'inh_3', count(*) from inh_3
> ;
>
> explain analyze select * from inh where a between 10110 and 10115;
Hmm, this last query should return 0 rows because:
select max(a) from inh;
max
=------
10000
(1 row)
Did you by any chance mean to write the following:
explain analyze select * from inh where a + b between 10110 and 10115;
In which case:
explain analyze select * from inh where a + b between 10110 and 10115;
QUERY PLAN
=-------------------------------------------------------------------------------------------------------
Append (cost=0.00..123.00 rows=26 width=8) (actual time=0.119..6.407
rows=3 loops=1)
-> Seq Scan on inh (cost=0.00..0.00 rows=1 width=8) (actual
time=0.015..0.015 rows=0 loops=1)
Filter: (((a + b) >= 10110) AND ((a + b) <= 10115))
-> Seq Scan on inh_2 (cost=0.00..123.00 rows=25 width=8) (actual
time=0.076..6.198 rows=3 loops=1)
Filter: (((a + b) >= 10110) AND ((a + b) <= 10115))
Rows Removed by Filter: 4997
Planning time: 0.521 ms
Execution time: 6.572 ms
(8 rows)
select * from inh where a + b between 10110 and 10115;
a | b
=-----+------
5055 | 5055
5056 | 5056
5057 | 5057
(3 rows)
Now that doesn't use index for the obvious reason (mismatched key). So,
let's try one which will:
explain analyze select * from inh where a = 4567;
QUERY PLAN
=------------------------------------------------------------------------------------------------------------------------
Append (cost=0.00..17.61 rows=4 width=8) (actual time=0.189..0.293
rows=1 loops=1)
-> Seq Scan on inh (cost=0.00..0.00 rows=1 width=8) (actual
time=0.016..0.016 rows=0 loops=1)
Filter: (a = 4567)
-> Index Scan using inh_1_a_idx on inh_1 (cost=0.28..8.30 rows=1
width=8) (actual time=0.043..0.056 rows=1 loops=1)
Index Cond: (a = 4567)
-> Index Scan using inh_2_a_idx on inh_2 (cost=0.28..8.30 rows=1
width=8) (actual time=0.024..0.024 rows=0 loops=1)
Index Cond: (a = 4567)
-> Seq Scan on inh_3 (cost=0.00..1.01 rows=1 width=8) (actual
time=0.029..0.029 rows=0 loops=1)
Filter: (a = 4567)
Rows Removed by Filter: 1
Planning time: 0.589 ms
Execution time: 0.433 ms
select * from inh where a = 4567;
a | b
=-----+------
4567 | 4567
(1 row)
No pruning occurs this time for the obvious reason (mismatched key).
Does that help clarify?
Thanks,
Amit
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Attachments:
[text/x-diff] 0001-Add-syntax-to-specify-partition-key-v4.patch (41.5K, ../../5732984F.6090200@lab.ntt.co.jp/2-0001-Add-syntax-to-specify-partition-key-v4.patch)
download | inline diff:
From fced921285371d5783d3a2fd8b9fa45f553115b7 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Mon, 22 Feb 2016 14:06:33 +0900
Subject: [PATCH 1/6] Add syntax to specify partition key.
This adds the following clause to CREATE TABLE:
PARTITION BY method (col [USING opclass] [, ...])
where 'method' is either RANGE or LIST. 'col' could be either a simple
column reference or a parenthesized expression. 'opclass' is name
of btree operator class to use with the column. If method is LIST,
only one column is allowed, whereas, for RANGE method up to 32 columns
can be specified. If this clause is specified, the following constraints
viz. PARTITION KEY, UNIQUE, FOREIGN KEY, EXCLUSION are not allowed
because they cannot be supported on partitioned tables.
This adds 2 new parser nodes viz. PartitionElem and PartitionBy. The former
is similar to IndexElem and the latter consists of details of partition
key such as the method and list of PartitionElems. The new member partby
of CreateStmt is a pointer to the PartitionBy struct created when PARTITION
BY clause is present.
---
doc/src/sgml/ref/create_table.sgml | 35 +++++
src/backend/commands/indexcmds.c | 4 +-
src/backend/commands/tablecmds.c | 215 ++++++++++++++++++++++++++++
src/backend/nodes/copyfuncs.c | 33 +++++
src/backend/nodes/equalfuncs.c | 28 ++++
src/backend/nodes/outfuncs.c | 27 ++++
src/backend/parser/gram.y | 110 ++++++++++++---
src/backend/parser/parse_agg.c | 11 ++
src/backend/parser/parse_expr.c | 5 +
src/backend/parser/parse_utilcmd.c | 63 ++++++++
src/include/commands/defrem.h | 2 +
src/include/nodes/nodes.h | 2 +
src/include/nodes/parsenodes.h | 35 +++++
src/include/parser/kwlist.h | 1 +
src/include/parser/parse_node.h | 3 +-
src/include/pg_config_manual.h | 5 +
src/test/regress/expected/create_table.out | 79 ++++++++++
src/test/regress/sql/create_table.sql | 68 +++++++++
18 files changed, 704 insertions(+), 22 deletions(-)
diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml
index d1807ed..70a7579 100644
--- a/doc/src/sgml/ref/create_table.sgml
+++ b/doc/src/sgml/ref/create_table.sgml
@@ -28,6 +28,7 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
[, ... ]
] )
[ INHERITS ( <replaceable>parent_table</replaceable> [, ... ] ) ]
+[ PARTITION BY {RANGE | LIST} ( { <replaceable class="parameter">column_name</replaceable> | ( <replaceable class="parameter">expression</replaceable> ) } [ <replaceable class="parameter">opclass</replaceable> ] [, ...] )
[ WITH ( <replaceable class="PARAMETER">storage_parameter</replaceable> [= <replaceable class="PARAMETER">value</replaceable>] [, ... ] ) | WITH OIDS | WITHOUT OIDS ]
[ ON COMMIT { PRESERVE ROWS | DELETE ROWS | DROP } ]
[ TABLESPACE <replaceable class="PARAMETER">tablespace_name</replaceable> ]
@@ -38,6 +39,7 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
| <replaceable>table_constraint</replaceable> }
[, ... ]
) ]
+[ PARTITION BY {RANGE | LIST} ( { <replaceable class="parameter">column_name</replaceable> | ( <replaceable class="parameter">expression</replaceable> ) } [ <replaceable class="parameter">opclass</replaceable> ] [, ...] )
[ WITH ( <replaceable class="PARAMETER">storage_parameter</replaceable> [= <replaceable class="PARAMETER">value</replaceable>] [, ... ] ) | WITH OIDS | WITHOUT OIDS ]
[ ON COMMIT { PRESERVE ROWS | DELETE ROWS | DROP } ]
[ TABLESPACE <replaceable class="PARAMETER">tablespace_name</replaceable> ]
@@ -314,6 +316,39 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
</varlistentry>
<varlistentry>
+ <term><literal>PARTITION BY {RANGE | LIST} ( { <replaceable class="parameter">column_name</replaceable> | ( <replaceable class="parameter">expression</replaceable> ) } [ <replaceable class="parameter">opclass</replaceable> ] [, ...] ) </literal></term>
+ <listitem>
+ <para>
+ The optional <literal>PARTITION BY</> clause specifies a method of
+ partitioning the table and the corresponding partition key. Table
+ thus created is called <firstterm>partitioned</firstterm> table. Key
+ consists of an ordered list of column names and/or expressions when
+ using the <literal>RANGE</> method, whereas only a single column or
+ expression can be specified when using the <literal>LIST</> method.
+ The type of a key column or an expresion must have an associated
+ btree operator class or one must be specified along with the column
+ or the expression.
+ </para>
+
+ <para>
+ A partitioned table is divided into sub-tables (called partitions), which
+ in turn, are created using separate <literal>CREATE TABLE</> commands.
+ The table itself is empty. A data row inserted into the table is mapped
+ to and stored in one of the partitions (if one exists) based on the
+ values of columns and/or expressions in the partition key and partition
+ rules associated with the partitions.
+ </para>
+
+ <para>
+ Currently, there are following limitations on definition of partitioned
+ tables: one cannot specify any UNIQUE, PRIMARY KEY, EXCLUDE and/or
+ FOREIGN KEY constraints.
+ </para>
+
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><literal>LIKE <replaceable>source_table</replaceable> [ <replaceable>like_option</replaceable> ... ]</literal></term>
<listitem>
<para>
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index d14d540..4ef6b80 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -69,8 +69,6 @@ static void ComputeIndexAttrs(IndexInfo *indexInfo,
char *accessMethodName, Oid accessMethodId,
bool amcanorder,
bool isconstraint);
-static Oid GetIndexOpClass(List *opclass, Oid attrType,
- char *accessMethodName, Oid accessMethodId);
static char *ChooseIndexName(const char *tabname, Oid namespaceId,
List *colnames, List *exclusionOpNames,
bool primary, bool isconstraint);
@@ -1256,7 +1254,7 @@ ComputeIndexAttrs(IndexInfo *indexInfo,
/*
* Resolve possibly-defaulted operator class specification
*/
-static Oid
+Oid
GetIndexOpClass(List *opclass, Oid attrType,
char *accessMethodName, Oid accessMethodId)
{
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 86e9814..d03147d 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -433,6 +433,11 @@ static void RangeVarCallbackForDropRelation(const RangeVar *rel, Oid relOid,
Oid oldRelOid, void *arg);
static void RangeVarCallbackForAlterRelation(const RangeVar *rv, Oid relid,
Oid oldrelid, void *arg);
+static PartitionBy *transformPartitionBy(Relation rel, PartitionBy *partitionby);
+static void ComputePartitionAttrs(Oid relid, List *partParams,
+ AttrNumber *partattrs,
+ List **partexprs,
+ Oid *partoplass);
/* ----------------------------------------------------------------
@@ -710,6 +715,21 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
AddRelationNewConstraints(rel, rawDefaults, stmt->constraints,
true, true, false);
+ /* Process partition key, if any */
+ if (stmt->partby)
+ {
+ PartitionBy *partby;
+ int16 partnatts;
+ AttrNumber partattrs[PARTITION_MAX_KEYS];
+ Oid partopclass[PARTITION_MAX_KEYS];
+ List *partexprs = NIL;
+
+ partnatts = list_length(stmt->partby->partParams);
+ partby = transformPartitionBy(rel, stmt->partby);
+ ComputePartitionAttrs(relationId, stmt->partby->partParams,
+ partattrs, &partexprs, partopclass);
+ }
+
ObjectAddressSet(address, RelationRelationId, relationId);
/*
@@ -12059,3 +12079,198 @@ RangeVarCallbackForAlterRelation(const RangeVar *rv, Oid relid, Oid oldrelid,
ReleaseSysCache(tuple);
}
+
+/*
+ * transformPartitionBy
+ * Transform any expressions present in the partition key
+ */
+static PartitionBy *
+transformPartitionBy(Relation rel, PartitionBy *partitionby)
+{
+ PartitionBy *partby;
+ ParseState *pstate;
+ RangeTblEntry *rte;
+ ListCell *l;
+
+ partby = (PartitionBy *) makeNode(PartitionBy);
+
+ partby->strategy = partitionby->strategy;
+ partby->location = partitionby->location;
+ partby->partParams = NIL;
+
+ /*
+ * Create a dummy ParseState and insert the target relation as its sole
+ * rangetable entry. We need a ParseState for transformExpr.
+ */
+ pstate = make_parsestate(NULL);
+ rte = addRangeTableEntryForRelation(pstate, rel, NULL, false, true);
+ addRTEtoQuery(pstate, rte, true, true, true);
+
+ /* take care of any partition expressions */
+ foreach(l, partitionby->partParams)
+ {
+ ListCell *column;
+ PartitionElem *pelem = (PartitionElem *) lfirst(l);
+
+ /* Check for PARTITION BY ... ON (foo, foo) */
+ foreach(column, partby->partParams)
+ {
+ PartitionElem *pparam = (PartitionElem *) lfirst(column);
+
+ if (pelem->name && pparam->name &&
+ !strcmp(pelem->name, pparam->name))
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_COLUMN),
+ errmsg("column \"%s\" appears twice in partition key", pelem->name),
+ parser_errposition(pstate, pelem->location)));
+ }
+
+ if (pelem->expr)
+ {
+ /* Now do parse transformation of the expression */
+ pelem->expr = transformExpr(pstate, pelem->expr,
+ EXPR_KIND_PARTITION_KEY);
+
+ /* we have to fix its collations too */
+ assign_expr_collations(pstate, pelem->expr);
+
+ /*
+ * transformExpr() should have already rejected subqueries,
+ * aggregates, and window functions, based on the EXPR_KIND_ for
+ * a partition key expression.
+ *
+ * Also reject expressions returning sets; this is for consistency
+ * DefineRelation() will make more checks.
+ */
+ if (expression_returns_set(pelem->expr))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("partition key expression cannot return a set"),
+ parser_errposition(pstate, pelem->location)));
+ }
+
+ partby->partParams = lappend(partby->partParams, pelem);
+ }
+
+ return partby;
+}
+
+/*
+ * ComputePartitionAttrs
+ * Compute per-partition-column information from partParams
+ */
+static void
+ComputePartitionAttrs(Oid relid, List *partParams, AttrNumber *partattrs,
+ List **partexprs, Oid *partopclass)
+{
+ int attn;
+ ListCell *lc;
+
+ attn = 0;
+ foreach(lc, partParams)
+ {
+ PartitionElem *pelem = (PartitionElem *) lfirst(lc);
+ Oid atttype;
+ Oid opclassOid;
+
+ if (pelem->name != NULL)
+ {
+ HeapTuple atttuple;
+ Form_pg_attribute attform;
+
+ atttuple = SearchSysCacheAttName(relid, pelem->name);
+ if (!HeapTupleIsValid(atttuple))
+ {
+ /* difference in error message spellings is historical */
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_COLUMN),
+ errmsg("column \"%s\" named in partition key does not exist",
+ pelem->name)));
+ }
+ attform = (Form_pg_attribute) GETSTRUCT(atttuple);
+
+ if (attform->attnum <= 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_COLUMN),
+ errmsg("cannot use system column \"%s\" in partition key",
+ pelem->name)));
+
+ partattrs[attn] = attform->attnum;
+ atttype = attform->atttypid;
+ ReleaseSysCache(atttuple);
+ }
+ else
+ {
+ /* Partition key expression */
+ Node *expr = pelem->expr;
+
+ Assert(expr != NULL);
+ atttype = exprType(expr);
+
+ if (IsA(expr, CollateExpr))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("cannot use COLLATE in partition key expression")));
+
+ if (IsA(expr, Var) &&
+ ((Var *) expr)->varattno != InvalidAttrNumber)
+ {
+ /*
+ * User wrote "(column)" or "(column COLLATE something)".
+ * Treat it like simple attribute anyway.
+ */
+ partattrs[attn] = ((Var *) expr)->varattno;
+ }
+ else
+ {
+ partattrs[attn] = 0; /* marks expression */
+ *partexprs = lappend(*partexprs, expr);
+
+ /*
+ * transformExpr() should have already rejected subqueries,
+ * aggregates, and window functions, based on the EXPR_KIND_
+ * for a partition key expression.
+ */
+
+ /*
+ * An expression using mutable functions is probably wrong even
+ * even to use in a partition key
+ */
+ expr = (Node *) expression_planner((Expr *) expr);
+
+ if (IsA(expr, Const))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("cannot use a constant expression as partition key")));
+
+ if (contain_mutable_functions(expr))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("functions in partition key expression must be marked IMMUTABLE")));
+ }
+ }
+
+ /*
+ * Identify the opclass to use. At the moment, we use "btree" operators
+ * that seems enough for list and range partitioning.
+ */
+ if (!pelem->opclass)
+ {
+ opclassOid = GetDefaultOpClass(atttype, BTREE_AM_OID);
+
+ if (!OidIsValid(opclassOid))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("data type %s has no default btree operator class",
+ format_type_be(atttype)),
+ errhint("You must specify an existing btree operator class or define one for the type.")));
+ }
+ else
+ opclassOid = GetIndexOpClass(pelem->opclass,
+ atttype,
+ "btree",
+ BTREE_AM_OID);
+
+ partopclass[attn++] = opclassOid;
+ }
+}
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 20e38f0..968a3f4 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3001,6 +3001,7 @@ CopyCreateStmtFields(const CreateStmt *from, CreateStmt *newnode)
COPY_NODE_FIELD(relation);
COPY_NODE_FIELD(tableElts);
COPY_NODE_FIELD(inhRelations);
+ COPY_NODE_FIELD(partby);
COPY_NODE_FIELD(ofTypename);
COPY_NODE_FIELD(constraints);
COPY_NODE_FIELD(options);
@@ -4156,6 +4157,32 @@ _copyAlterPolicyStmt(const AlterPolicyStmt *from)
return newnode;
}
+static PartitionBy *
+_copyPartitionBy(const PartitionBy *from)
+{
+
+ PartitionBy *newnode = makeNode(PartitionBy);
+
+ COPY_SCALAR_FIELD(strategy);
+ COPY_NODE_FIELD(partParams);
+ COPY_LOCATION_FIELD(location);
+
+ return newnode;
+}
+
+static PartitionElem *
+_copyPartitionElem(const PartitionElem *from)
+{
+ PartitionElem *newnode = makeNode(PartitionElem);
+
+ COPY_STRING_FIELD(name);
+ COPY_NODE_FIELD(expr);
+ COPY_NODE_FIELD(opclass);
+ COPY_LOCATION_FIELD(location);
+
+ return newnode;
+}
+
/* ****************************************************************
* pg_list.h copy functions
* ****************************************************************
@@ -5049,6 +5076,12 @@ copyObject(const void *from)
case T_RoleSpec:
retval = _copyRoleSpec(from);
break;
+ case T_PartitionBy:
+ retval = _copyPartitionBy(from);
+ break;
+ case T_PartitionElem:
+ retval = _copyPartitionElem(from);
+ break;
default:
elog(ERROR, "unrecognized node type: %d", (int) nodeTag(from));
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index c5ccc42..56325fe 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1156,6 +1156,7 @@ _equalCreateStmt(const CreateStmt *a, const CreateStmt *b)
COMPARE_NODE_FIELD(relation);
COMPARE_NODE_FIELD(tableElts);
COMPARE_NODE_FIELD(inhRelations);
+ COMPARE_NODE_FIELD(partby);
COMPARE_NODE_FIELD(ofTypename);
COMPARE_NODE_FIELD(constraints);
COMPARE_NODE_FIELD(options);
@@ -2619,6 +2620,27 @@ _equalRoleSpec(const RoleSpec *a, const RoleSpec *b)
return true;
}
+static bool
+_equalPartitionBy(const PartitionBy *a, const PartitionBy *b)
+{
+ COMPARE_SCALAR_FIELD(strategy);
+ COMPARE_NODE_FIELD(partParams);
+ COMPARE_LOCATION_FIELD(location);
+
+ return true;
+}
+
+static bool
+_equalPartitionElem(const PartitionElem *a, const PartitionElem *b)
+{
+ COMPARE_STRING_FIELD(name);
+ COMPARE_NODE_FIELD(expr);
+ COMPARE_NODE_FIELD(opclass);
+ COMPARE_LOCATION_FIELD(location);
+
+ return true;
+}
+
/*
* Stuff from pg_list.h
*/
@@ -3369,6 +3391,12 @@ equal(const void *a, const void *b)
case T_RoleSpec:
retval = _equalRoleSpec(a, b);
break;
+ case T_PartitionBy:
+ retval = _equalPartitionBy(a, b);
+ break;
+ case T_PartitionElem:
+ retval = _equalPartitionElem(a, b);
+ break;
default:
elog(ERROR, "unrecognized node type: %d",
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index c2f0e0f..5623471 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2369,6 +2369,7 @@ _outCreateStmtInfo(StringInfo str, const CreateStmt *node)
WRITE_NODE_FIELD(relation);
WRITE_NODE_FIELD(tableElts);
WRITE_NODE_FIELD(inhRelations);
+ WRITE_NODE_FIELD(partby);
WRITE_NODE_FIELD(ofTypename);
WRITE_NODE_FIELD(constraints);
WRITE_NODE_FIELD(options);
@@ -3217,6 +3218,26 @@ _outConstraint(StringInfo str, const Constraint *node)
}
}
+static void
+_outPartitionBy(StringInfo str, const PartitionBy *node)
+{
+ WRITE_NODE_TYPE("PARTITIONBY");
+
+ WRITE_CHAR_FIELD(strategy);
+ WRITE_NODE_FIELD(partParams);
+ WRITE_LOCATION_FIELD(location);
+}
+
+static void
+_outPartitionElem(StringInfo str, const PartitionElem *node)
+{
+ WRITE_NODE_TYPE("PARTITIONELEM");
+
+ WRITE_STRING_FIELD(name);
+ WRITE_NODE_FIELD(expr);
+ WRITE_NODE_FIELD(opclass);
+ WRITE_LOCATION_FIELD(location);
+}
/*
* outNode -
@@ -3796,6 +3817,12 @@ outNode(StringInfo str, const void *obj)
case T_XmlSerialize:
_outXmlSerialize(str, obj);
break;
+ case T_PartitionBy:
+ _outPartitionBy(str, obj);
+ break;
+ case T_PartitionElem:
+ _outPartitionElem(str, obj);
+ break;
default:
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 18ec5f0..0607439 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -227,6 +227,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
struct ImportQual *importqual;
InsertStmt *istmt;
VariableSetStmt *vsetstmt;
+ PartitionElem *partelem;
+ PartitionBy *partby;
}
%type <node> stmt schema_stmt
@@ -539,6 +541,9 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
opt_frame_clause frame_extent frame_bound
%type <str> opt_existing_window_name
%type <boolean> opt_if_not_exists
+%type <partby> PartitionBy OptPartitionBy
+%type <partelem> part_elem
+%type <list> part_params
/*
* Non-keyword token types. These are hard-wired into the "flex" lexer.
@@ -603,7 +608,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
KEY
LABEL LANGUAGE LARGE_P LAST_P LATERAL_P
- LEADING LEAKPROOF LEAST LEFT LEVEL LIKE LIMIT LISTEN LOAD LOCAL
+ LEADING LEAKPROOF LEAST LEFT LEVEL LIKE LIMIT LIST LISTEN LOAD LOCAL
LOCALTIME LOCALTIMESTAMP LOCATION LOCK_P LOCKED LOGGED
MAPPING MATCH MATERIALIZED MAXVALUE METHOD MINUTE_P MINVALUE MODE MONTH_P MOVE
@@ -2808,69 +2813,75 @@ copy_generic_opt_arg_list_item:
*****************************************************************************/
CreateStmt: CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
- OptInherit OptWith OnCommitOption OptTableSpace
+ OptInherit OptPartitionBy OptWith OnCommitOption OptTableSpace
{
CreateStmt *n = makeNode(CreateStmt);
$4->relpersistence = $2;
n->relation = $4;
n->tableElts = $6;
n->inhRelations = $8;
+ n->partby = $9;
n->ofTypename = NULL;
n->constraints = NIL;
- n->options = $9;
- n->oncommit = $10;
- n->tablespacename = $11;
+ n->options = $10;
+ n->oncommit = $11;
+ n->tablespacename = $12;
n->if_not_exists = false;
$$ = (Node *)n;
}
| CREATE OptTemp TABLE IF_P NOT EXISTS qualified_name '('
- OptTableElementList ')' OptInherit OptWith OnCommitOption
- OptTableSpace
+ OptTableElementList ')' OptInherit OptPartitionBy OptWith
+ OnCommitOption OptTableSpace
{
CreateStmt *n = makeNode(CreateStmt);
$7->relpersistence = $2;
n->relation = $7;
n->tableElts = $9;
n->inhRelations = $11;
+ n->partby = $12;
n->ofTypename = NULL;
n->constraints = NIL;
- n->options = $12;
- n->oncommit = $13;
- n->tablespacename = $14;
+ n->options = $13;
+ n->oncommit = $14;
+ n->tablespacename = $15;
n->if_not_exists = true;
$$ = (Node *)n;
}
| CREATE OptTemp TABLE qualified_name OF any_name
- OptTypedTableElementList OptWith OnCommitOption OptTableSpace
+ OptTypedTableElementList OptPartitionBy OptWith OnCommitOption
+ OptTableSpace
{
CreateStmt *n = makeNode(CreateStmt);
$4->relpersistence = $2;
n->relation = $4;
n->tableElts = $7;
n->inhRelations = NIL;
+ n->partby = $8;
n->ofTypename = makeTypeNameFromNameList($6);
n->ofTypename->location = @6;
n->constraints = NIL;
- n->options = $8;
- n->oncommit = $9;
- n->tablespacename = $10;
+ n->options = $9;
+ n->oncommit = $10;
+ n->tablespacename = $11;
n->if_not_exists = false;
$$ = (Node *)n;
}
| CREATE OptTemp TABLE IF_P NOT EXISTS qualified_name OF any_name
- OptTypedTableElementList OptWith OnCommitOption OptTableSpace
+ OptTypedTableElementList OptPartitionBy OptWith OnCommitOption
+ OptTableSpace
{
CreateStmt *n = makeNode(CreateStmt);
$7->relpersistence = $2;
n->relation = $7;
n->tableElts = $10;
n->inhRelations = NIL;
+ n->partby = $11;
n->ofTypename = makeTypeNameFromNameList($9);
n->ofTypename->location = @9;
n->constraints = NIL;
- n->options = $11;
- n->oncommit = $12;
- n->tablespacename = $13;
+ n->options = $12;
+ n->oncommit = $13;
+ n->tablespacename = $14;
n->if_not_exists = true;
$$ = (Node *)n;
}
@@ -3415,6 +3426,68 @@ OptInherit: INHERITS '(' qualified_name_list ')' { $$ = $3; }
| /*EMPTY*/ { $$ = NIL; }
;
+/* Optional partition key definition */
+OptPartitionBy: PartitionBy { $$ = $1; }
+ | /*EMPTY*/ { $$ = NULL; }
+ ;
+
+PartitionBy: PARTITION BY RANGE '(' part_params ')'
+ {
+ PartitionBy *n = makeNode(PartitionBy);
+
+ n->strategy = PARTITION_STRAT_RANGE;
+ n->partParams = $5;
+ n->location = @1;
+
+ $$ = n;
+ }
+ | PARTITION BY LIST '(' part_params ')'
+ {
+ PartitionBy *n = makeNode(PartitionBy);
+
+ n->strategy = PARTITION_STRAT_LIST;
+ n->partParams = $5;
+ n->location = @1;
+
+ $$ = n;
+ }
+ ;
+
+part_params: part_elem { $$ = list_make1($1); }
+ | part_params ',' part_elem { $$ = lappend($1, $3); }
+ ;
+
+part_elem: ColId opt_class
+ {
+ PartitionElem *n = makeNode(PartitionElem);
+
+ n->name = $1;
+ n->expr = NULL;
+ n->opclass = $2;
+ n->location = @1;
+ $$ = n;
+ }
+ | func_expr_windowless opt_class
+ {
+ PartitionElem *n = makeNode(PartitionElem);
+
+ n->name = NULL;
+ n->expr = $1;
+ n->opclass = $2;
+ n->location = @1;
+ $$ = n;
+ }
+ | '(' a_expr ')' opt_class
+ {
+ PartitionElem *n = makeNode(PartitionElem);
+
+ n->name = NULL;
+ n->expr = $2;
+ n->opclass = $4;
+ n->location = @1;
+ $$ = n;
+ }
+ ;
/* WITH (options) is preferred, WITH OIDS and WITHOUT OIDS are legacy forms */
OptWith:
WITH reloptions { $$ = $2; }
@@ -13839,6 +13912,7 @@ unreserved_keyword:
| LAST_P
| LEAKPROOF
| LEVEL
+ | LIST
| LISTEN
| LOAD
| LOCAL
diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c
index 91bfe66..b56fd4f 100644
--- a/src/backend/parser/parse_agg.c
+++ b/src/backend/parser/parse_agg.c
@@ -486,6 +486,14 @@ check_agglevels_and_constraints(ParseState *pstate, Node *expr)
err = _("grouping operations are not allowed in trigger WHEN conditions");
break;
+ case EXPR_KIND_PARTITION_KEY:
+ if (isAgg)
+ err = _("aggregate functions are not allowed in partition key expressions");
+ else
+ err = _("grouping operations are not allowed in partition key expressions");
+
+ break;
+
/*
* There is intentionally no default: case here, so that the
@@ -843,6 +851,9 @@ transformWindowFuncCall(ParseState *pstate, WindowFunc *wfunc,
case EXPR_KIND_TRIGGER_WHEN:
err = _("window functions are not allowed in trigger WHEN conditions");
break;
+ case EXPR_KIND_PARTITION_KEY:
+ err = _("window functions are not allowed in partition key expressions");
+ break;
/*
* There is intentionally no default: case here, so that the
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 8b28516..b53b7f1 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -1723,6 +1723,9 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
case EXPR_KIND_TRIGGER_WHEN:
err = _("cannot use subquery in trigger WHEN condition");
break;
+ case EXPR_KIND_PARTITION_KEY:
+ err = _("cannot use subquery in partition key expressions");
+ break;
/*
* There is intentionally no default: case here, so that the
@@ -3249,6 +3252,8 @@ ParseExprKindName(ParseExprKind exprKind)
return "EXECUTE";
case EXPR_KIND_TRIGGER_WHEN:
return "WHEN";
+ case EXPR_KIND_PARTITION_KEY:
+ return "partition key expression";
/*
* There is intentionally no default: case here, so that the
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 6528494..e267f74 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -87,6 +87,7 @@ typedef struct
List *alist; /* "after list" of things to do after creating
* the table */
IndexStmt *pkey; /* PRIMARY KEY index, if any */
+ bool ispartitioned; /* true if table is partitioned */
} CreateStmtContext;
/* State shared by transformCreateSchemaStmt and its subroutines */
@@ -229,6 +230,7 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
cxt.blist = NIL;
cxt.alist = NIL;
cxt.pkey = NULL;
+ cxt.ispartitioned = stmt->partby != NULL;
/*
* Notice that we allow OIDs here only for plain tables, even though
@@ -247,6 +249,24 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
if (stmt->ofTypename)
transformOfType(&cxt, stmt->ofTypename);
+ if (stmt->partby)
+ {
+ int partnatts = list_length(stmt->partby->partParams);
+
+ if (partnatts > PARTITION_MAX_KEYS)
+ ereport(ERROR,
+ (errcode(ERRCODE_TOO_MANY_COLUMNS),
+ errmsg("cannot use more than %d columns in partition key",
+ PARTITION_MAX_KEYS)));
+
+ if (stmt->partby->strategy == PARTITION_STRAT_LIST &&
+ partnatts > 1)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("cannot use more than one column in partition key"),
+ errdetail("Only one column allowed with list partitioning.")));
+ }
+
/*
* Run through each primary element in the table creation clause. Separate
* column defs from constraints, and do preliminary analysis. We have to
@@ -582,6 +602,12 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
errmsg("primary key constraints are not supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
+ if (cxt->ispartitioned)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("primary key constraints are not supported on partitioned tables"),
+ parser_errposition(cxt->pstate,
+ constraint->location)));
/* FALL THRU */
case CONSTR_UNIQUE:
@@ -591,6 +617,12 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
errmsg("unique constraints are not supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
+ if (cxt->ispartitioned)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("unique constraints are not supported on partitioned tables"),
+ parser_errposition(cxt->pstate,
+ constraint->location)));
if (constraint->keys == NIL)
constraint->keys = list_make1(makeString(column->colname));
cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
@@ -608,6 +640,12 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
errmsg("foreign key constraints are not supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
+ if (cxt->ispartitioned)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("foreign key constraints are not supported on partitioned tables"),
+ parser_errposition(cxt->pstate,
+ constraint->location)));
/*
* Fill in the current attribute's name and throw it into the
@@ -673,6 +711,12 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
errmsg("primary key constraints are not supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
+ if (cxt->ispartitioned)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("primary key constraints are not supported on partitioned tables"),
+ parser_errposition(cxt->pstate,
+ constraint->location)));
cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
break;
@@ -683,6 +727,12 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
errmsg("unique constraints are not supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
+ if (cxt->ispartitioned)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("unique constraints are not supported on partitioned tables"),
+ parser_errposition(cxt->pstate,
+ constraint->location)));
cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
break;
@@ -693,6 +743,12 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
errmsg("exclusion constraints are not supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
+ if (cxt->ispartitioned)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("exclusion constraints are not supported on partitioned tables"),
+ parser_errposition(cxt->pstate,
+ constraint->location)));
cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
break;
@@ -707,6 +763,12 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
errmsg("foreign key constraints are not supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
+ if (cxt->ispartitioned)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("foreign key constraints are not supported on partitioned tables"),
+ parser_errposition(cxt->pstate,
+ constraint->location)));
cxt->fkconstraints = lappend(cxt->fkconstraints, constraint);
break;
@@ -2515,6 +2577,7 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
cxt.blist = NIL;
cxt.alist = NIL;
cxt.pkey = NULL;
+ cxt.ispartitioned = false;
/*
* The only subtypes that currently require parse transformation handling
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index b064eb4..28024f8 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -42,6 +42,8 @@ extern bool CheckIndexCompatible(Oid oldId,
List *attributeList,
List *exclusionOpNames);
extern Oid GetDefaultOpClass(Oid type_id, Oid am_id);
+extern Oid GetIndexOpClass(List *opclass, Oid attrType,
+ char *accessMethodName, Oid accessMethodId);
/* commands/functioncmds.c */
extern ObjectAddress CreateFunction(CreateFunctionStmt *stmt, const char *queryString);
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index ea65f2e..9e582a5 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -452,6 +452,8 @@ typedef enum NodeTag
T_OnConflictClause,
T_CommonTableExpr,
T_RoleSpec,
+ T_PartitionElem,
+ T_PartitionBy,
/*
* TAGS FOR REPLICATION GRAMMAR PARSE NODES (replnodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 714cf15..4f697b8 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -696,6 +696,40 @@ typedef struct XmlSerialize
int location; /* token location, or -1 if unknown */
} XmlSerialize;
+/* Partitioning related definitions */
+
+/*
+ * PartitionElem - a partition key column
+ *
+ * 'name' Name of the table column included in the key
+ * 'expr' Expression node tree of expressional key column
+ * 'opclass' Operator class name associated with the column
+ */
+typedef struct PartitionElem
+{
+ NodeTag type;
+ char *name; /* name of column to partition on, or NULL */
+ Node *expr; /* expression to partition on, or NULL */
+ List *opclass; /* name of desired opclass; NIL = default */
+ int location; /* token location, or -1 if unknown */
+} PartitionElem;
+
+/*
+ * PartitionBy - partition key definition including the strategy
+ *
+ * 'strategy' partition strategy to use (one of the below defined)
+ * 'partParams' List of PartitionElems, one for each key column
+ */
+#define PARTITION_STRAT_LIST 'l'
+#define PARTITION_STRAT_RANGE 'r'
+
+typedef struct PartitionBy
+{
+ NodeTag type;
+ char strategy;
+ List *partParams;
+ int location; /* token location, or -1 if unknown */
+} PartitionBy;
/****************************************************************************
* Nodes for a Query tree
@@ -1750,6 +1784,7 @@ typedef struct CreateStmt
List *tableElts; /* column definitions (list of ColumnDef) */
List *inhRelations; /* relations to inherit from (list of
* inhRelation) */
+ PartitionBy *partby; /* PARTITION BY clause */
TypeName *ofTypename; /* OF typename */
List *constraints; /* constraints (list of Constraint nodes) */
List *options; /* options from WITH clause */
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 17ffef5..40da67a 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -227,6 +227,7 @@ PG_KEYWORD("left", LEFT, TYPE_FUNC_NAME_KEYWORD)
PG_KEYWORD("level", LEVEL, UNRESERVED_KEYWORD)
PG_KEYWORD("like", LIKE, TYPE_FUNC_NAME_KEYWORD)
PG_KEYWORD("limit", LIMIT, RESERVED_KEYWORD)
+PG_KEYWORD("list", LIST, UNRESERVED_KEYWORD)
PG_KEYWORD("listen", LISTEN, UNRESERVED_KEYWORD)
PG_KEYWORD("load", LOAD, UNRESERVED_KEYWORD)
PG_KEYWORD("local", LOCAL, UNRESERVED_KEYWORD)
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index e3e359c..a13c6fb 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -64,7 +64,8 @@ typedef enum ParseExprKind
EXPR_KIND_ALTER_COL_TRANSFORM, /* transform expr in ALTER COLUMN TYPE */
EXPR_KIND_EXECUTE_PARAMETER, /* parameter value in EXECUTE */
EXPR_KIND_TRIGGER_WHEN, /* WHEN condition in CREATE TRIGGER */
- EXPR_KIND_POLICY /* USING or WITH CHECK expr in policy */
+ EXPR_KIND_POLICY, /* USING or WITH CHECK expr in policy */
+ EXPR_KIND_PARTITION_KEY /* partition key expression */
} ParseExprKind;
diff --git a/src/include/pg_config_manual.h b/src/include/pg_config_manual.h
index 8d5a378..850251b 100644
--- a/src/include/pg_config_manual.h
+++ b/src/include/pg_config_manual.h
@@ -46,6 +46,11 @@
#define INDEX_MAX_KEYS 32
/*
+ * Maximum number of columns in a partition key
+ */
+#define PARTITION_MAX_KEYS 32
+
+/*
* Set the upper and lower bounds of sequence values.
*/
#define SEQ_MAXVALUE PG_INT64_MAX
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 41ceb87..aaae9f9 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -253,3 +253,82 @@ DROP TABLE as_select1;
-- check that the oid column is added before the primary key is checked
CREATE TABLE oid_pk (f1 INT, PRIMARY KEY(oid)) WITH OIDS;
DROP TABLE oid_pk;
+--
+-- partitioned tables
+--
+-- cannot use system columns in partition key
+CREATE TABLE fail_oid_pk (
+ a int
+) PARTITION BY RANGE (oid) WITH OIDS; -- fail
+ERROR: cannot use system column "oid" in partition key
+-- cannot use more than 1 column as partition key for list partitioned table
+CREATE TABLE two_col_tab (
+ a1 int,
+ a2 int
+) PARTITION BY LIST (a1, a2); -- fail
+ERROR: cannot use more than one column in partition key
+DETAIL: Only one column allowed with list partitioning.
+-- cannot use more than 32 columns as partition key for range partitioned table
+CREATE TABLE wide_tab (
+ a01 int, a02 int, a03 int, a04 int, a05 int, a06 int, a07 int, a08 int,
+ a09 int, a10 int, a11 int, a12 int, a13 int, a14 int, a15 int, a16 int,
+ a17 int, a18 int, a19 int, a20 int, a21 int, a22 int, a23 int, a24 int,
+ a25 int, a26 int, a27 int, a28 int, a29 int, a30 int, a31 int, a32 int,
+ a33 int
+) PARTITION BY RANGE (a01, a02, a03, a04, a05, a06, a07, a08,
+ a09, a10, a11, a12, a13, a14, a15, a16,
+ a17, a18, a19, a20, a21, a22, a23, a24,
+ a25, a26, a27, a28, a29, a30, a31, a32, a33); -- fail
+ERROR: cannot use more than 32 columns in partition key
+-- Partitioning requires to use columns with data type having a suitable
+-- btree operator class
+CREATE TABLE point_tab (
+ a point
+) PARTITION BY LIST (a); -- fail
+ERROR: data type point has no default btree operator class
+HINT: You must specify an existing btree operator class or define one for the type.
+CREATE TABLE point_tab (
+ a point
+) PARTITION BY LIST (a USING point_ops); -- fail
+ERROR: operator class "point_ops" does not exist for access method "btree"
+CREATE TABLE point_tab (
+ a point
+) PARTITION BY RANGE (a); -- fail
+ERROR: data type point has no default btree operator class
+HINT: You must specify an existing btree operator class or define one for the type.
+CREATE TABLE point_tab (
+ a point
+) PARTITION BY RANGE (a USING point_ops); -- fail
+ERROR: operator class "point_ops" does not exist for access method "btree"
+-- fail to create a partitioned table with PRIMARY KEY constraint
+CREATE TABLE pk_fail (
+ a int PRIMARY KEY
+) PARTITION BY RANGE (a);
+ERROR: primary key constraints are not supported on partitioned tables
+LINE 2: a int PRIMARY KEY
+ ^
+-- fail to create a partitioned table with foreign key constraint
+CREATE TABLE pk_rel(
+ a int PRIMARY KEY
+);
+CREATE TABLE fk_fail (
+ a int REFERENCES pk_rel(a)
+) PARTITION BY RANGE (a);
+ERROR: foreign key constraints are not supported on partitioned tables
+LINE 2: a int REFERENCES pk_rel(a)
+ ^
+DROP TABLE pk_rel;
+-- fail to create a partitioned table with prohibited expressions in key
+CREATE TABLE fail_const_key (
+ a int
+) PARTITION BY RANGE (('a'));
+ERROR: cannot use a constant expression as partition key
+CREATE TABLE fail_agg_in_key (
+ a int
+) PARTITION BY RANGE ((avg(a)));
+ERROR: aggregate functions are not allowed in partition key expressions
+CREATE TABLE fail_window_fun_in_key (
+ a int,
+ b int
+) PARTITION BY RANGE ((avg(a) OVER (PARTITION BY b)));
+ERROR: window functions are not allowed in partition key expressions
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 78bdc8b..df47656 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -269,3 +269,71 @@ DROP TABLE as_select1;
-- check that the oid column is added before the primary key is checked
CREATE TABLE oid_pk (f1 INT, PRIMARY KEY(oid)) WITH OIDS;
DROP TABLE oid_pk;
+
+--
+-- partitioned tables
+--
+
+-- cannot use system columns in partition key
+CREATE TABLE fail_oid_pk (
+ a int
+) PARTITION BY RANGE (oid) WITH OIDS; -- fail
+
+-- cannot use more than 1 column as partition key for list partitioned table
+CREATE TABLE two_col_tab (
+ a1 int,
+ a2 int
+) PARTITION BY LIST (a1, a2); -- fail
+
+-- cannot use more than 32 columns as partition key for range partitioned table
+CREATE TABLE wide_tab (
+ a01 int, a02 int, a03 int, a04 int, a05 int, a06 int, a07 int, a08 int,
+ a09 int, a10 int, a11 int, a12 int, a13 int, a14 int, a15 int, a16 int,
+ a17 int, a18 int, a19 int, a20 int, a21 int, a22 int, a23 int, a24 int,
+ a25 int, a26 int, a27 int, a28 int, a29 int, a30 int, a31 int, a32 int,
+ a33 int
+) PARTITION BY RANGE (a01, a02, a03, a04, a05, a06, a07, a08,
+ a09, a10, a11, a12, a13, a14, a15, a16,
+ a17, a18, a19, a20, a21, a22, a23, a24,
+ a25, a26, a27, a28, a29, a30, a31, a32, a33); -- fail
+
+-- Partitioning requires to use columns with data type having a suitable
+-- btree operator class
+CREATE TABLE point_tab (
+ a point
+) PARTITION BY LIST (a); -- fail
+CREATE TABLE point_tab (
+ a point
+) PARTITION BY LIST (a USING point_ops); -- fail
+CREATE TABLE point_tab (
+ a point
+) PARTITION BY RANGE (a); -- fail
+CREATE TABLE point_tab (
+ a point
+) PARTITION BY RANGE (a USING point_ops); -- fail
+
+-- fail to create a partitioned table with PRIMARY KEY constraint
+CREATE TABLE pk_fail (
+ a int PRIMARY KEY
+) PARTITION BY RANGE (a);
+
+-- fail to create a partitioned table with foreign key constraint
+CREATE TABLE pk_rel(
+ a int PRIMARY KEY
+);
+CREATE TABLE fk_fail (
+ a int REFERENCES pk_rel(a)
+) PARTITION BY RANGE (a);
+DROP TABLE pk_rel;
+
+-- fail to create a partitioned table with prohibited expressions in key
+CREATE TABLE fail_const_key (
+ a int
+) PARTITION BY RANGE (('a'));
+CREATE TABLE fail_agg_in_key (
+ a int
+) PARTITION BY RANGE ((avg(a)));
+CREATE TABLE fail_window_fun_in_key (
+ a int,
+ b int
+) PARTITION BY RANGE ((avg(a) OVER (PARTITION BY b)));
--
1.7.1
[text/x-diff] 0002-Infrastructure-for-creation-of-partitioned-tables-v4.patch (85.6K, ../../5732984F.6090200@lab.ntt.co.jp/3-0002-Infrastructure-for-creation-of-partitioned-tables-v4.patch)
download | inline diff:
From 225857d212832e48c72b0153e05c6c036dd140c1 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Mon, 22 Feb 2016 17:38:55 +0900
Subject: [PATCH 2/6] Infrastructure for creation of partitioned tables.
First, this adds a new system catalog pg_partitioned with:
partedrelid oid
partstrat char
partnatts int16
partattrs int2vector
partclass oidvector
partexprs pg_node_tree
There is a unique index on partedrelid and associated syscache PARTEDRELID.
Second, a new relkind RELKIND_PARTITIONED_REL is added to pg_class. Tables
created with CREATE TABLE PARTITION BY are of this relkind. Some special
properties:
* Has a pg_partitioned entry
* Access to them unconditionally overrides specified inheritance option
* Dropping or altering type of columns that are part of the partition key
is disallowed
* Changing inheritance (ALTER TABLE table INHERIT parent) and linking to
a type (ALTER TABLE table OF type) is disallowed
Partition key is represented using struct PartitionKey and built during the
first relcache build of a partitioned relation. It is stored in rd_partkey
using the cache context.
---
doc/src/sgml/catalogs.sgml | 99 ++++++
src/backend/access/common/reloptions.c | 2 +
src/backend/catalog/Makefile | 4 +-
src/backend/catalog/aclchk.c | 2 +
src/backend/catalog/heap.c | 19 +-
src/backend/catalog/objectaddress.c | 10 +-
src/backend/catalog/partition.c | 451 ++++++++++++++++++++++++++++
src/backend/commands/copy.c | 6 +
src/backend/commands/indexcmds.c | 3 +-
src/backend/commands/lockcmds.c | 2 +-
src/backend/commands/policy.c | 2 +-
src/backend/commands/seclabel.c | 1 +
src/backend/commands/sequence.c | 1 +
src/backend/commands/tablecmds.c | 168 ++++++++++-
src/backend/commands/trigger.c | 7 +-
src/backend/commands/vacuum.c | 11 +-
src/backend/executor/execMain.c | 1 +
src/backend/executor/nodeModifyTable.c | 1 +
src/backend/parser/parse_agg.c | 6 +-
src/backend/parser/parse_clause.c | 8 +
src/backend/parser/parse_expr.c | 2 +-
src/backend/parser/parse_relation.c | 6 +-
src/backend/parser/parse_utilcmd.c | 4 +-
src/backend/rewrite/rewriteDefine.c | 1 +
src/backend/rewrite/rewriteHandler.c | 1 +
src/backend/tcop/utility.c | 5 +-
src/backend/utils/adt/ruleutils.c | 147 +++++++++
src/backend/utils/cache/relcache.c | 14 +-
src/backend/utils/cache/syscache.c | 12 +
src/bin/pg_dump/pg_dump.c | 34 ++-
src/bin/pg_dump/pg_dump.h | 1 +
src/bin/psql/describe.c | 61 +++-
src/bin/psql/tab-complete.c | 6 +-
src/include/catalog/indexing.h | 3 +
src/include/catalog/partition.h | 55 ++++
src/include/catalog/pg_class.h | 1 +
src/include/catalog/pg_partitioned.h | 67 ++++
src/include/catalog/pg_proc.h | 2 +
src/include/commands/defrem.h | 4 +-
src/include/utils/builtins.h | 1 +
src/include/utils/rel.h | 2 +
src/include/utils/syscache.h | 1 +
src/test/regress/expected/create_table.out | 30 ++-
src/test/regress/expected/sanity_check.out | 1 +
src/test/regress/sql/create_table.sql | 21 ++
45 files changed, 1226 insertions(+), 60 deletions(-)
create mode 100644 src/backend/catalog/partition.c
create mode 100644 src/include/catalog/partition.h
create mode 100644 src/include/catalog/pg_partitioned.h
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index eac6671..652f779 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -224,6 +224,11 @@
</row>
<row>
+ <entry><link linkend="catalog-pg-partitioned"><structname>pg_partitioned</structname></link></entry>
+ <entry>information about partitioned tables, including the partition key</entry>
+ </row>
+
+ <row>
<entry><link linkend="catalog-pg-policy"><structname>pg_policy</structname></link></entry>
<entry>row-security policies</entry>
</row>
@@ -4672,6 +4677,100 @@
</sect1>
+ <sect1 id="catalog-pg-partitioned">
+ <title><structname>pg_partitioned</structname></title>
+
+ <indexterm zone="catalog-pg-partitioned">
+ <primary>pg_partitioned</primary>
+ </indexterm>
+
+ <para>
+ The catalog <structname>pg_partitioned</structname> stores information
+ about the partition key of partitioned tables.
+ </para>
+
+ <table>
+ <title><structname>pg_partitioned</> Columns</title>
+
+ <tgroup cols="4">
+ <thead>
+ <row>
+ <entry>Name</entry>
+ <entry>Type</entry>
+ <entry>References</entry>
+ <entry>Description</entry>
+ </row>
+ </thead>
+
+ <tbody>
+
+ <row>
+ <entry><structfield>partedrelid</structfield></entry>
+ <entry><type>oid</type></entry>
+ <entry><literal><link linkend="catalog-pg-class"><structname>pg_class</structname></link>.oid</literal></entry>
+ <entry>The OID of the <structname>pg_class</> entry for this partitioned table</entry>
+ </row>
+
+ <row>
+ <entry><structfield>partstrat</structfield></entry>
+ <entry><type>char</type></entry>
+ <entry></entry>
+ <entry>
+ Partitioning strategy (or method); <literal>l</> = list partitioned table,
+ <literal>r</> = range partitioned table
+ </entry>
+ </row>
+
+ <row>
+ <entry><structfield>partnatts</structfield></entry>
+ <entry><type>int2</type></entry>
+ <entry></entry>
+ <entry>The number of columns in partition key</entry>
+ </row>
+
+ <row>
+ <entry><structfield>partattrs</structfield></entry>
+ <entry><type>int2vector</type></entry>
+ <entry><literal><link linkend="catalog-pg-attribute"><structname>pg_attribute</structname></link>.attnum</literal></entry>
+ <entry>
+ This is an array of <structfield>partnatts</structfield> values that
+ indicate which table columns are used as partition key. For example,
+ a value of <literal>1 3</literal> would mean that the first and the
+ third table columns make up the partition key. A zero in this array
+ indicates that the corresponding partition key column is an expression
+ over the table columns, rather than a simple column reference.
+ </entry>
+ </row>
+
+ <row>
+ <entry><structfield>partclass</structfield></entry>
+ <entry><type>oidvector</type></entry>
+ <entry><literal><link linkend="catalog-pg-opclass"><structname>pg_opclass</structname></link>.oid</literal></entry>
+ <entry>
+ For each column in the partition key, this contains the OID of
+ the operator class to use. See
+ <link linkend="catalog-pg-opclass"><structname>pg_opclass</structname></link> for details.
+ </entry>
+ </row>
+
+ <row>
+ <entry><structfield>partexprs</structfield></entry>
+ <entry><type>pg_node_tree</type></entry>
+ <entry></entry>
+ <entry>
+ Expression trees (in <function>nodeToString()</function>
+ representation) for partition key columns that are not simple column
+ references. This is a list with one element for each zero
+ entry in <structfield>partkey</>. Null if all partition key columns
+ are simple references.
+ </entry>
+ </row>
+
+ </tbody>
+ </tgroup>
+ </table>
+ </sect1>
+
<sect1 id="catalog-pg-policy">
<title><structname>pg_policy</structname></title>
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 7448c7f..5a6777a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -925,6 +925,7 @@ extractRelOptions(HeapTuple tuple, TupleDesc tupdesc,
case RELKIND_RELATION:
case RELKIND_TOASTVALUE:
case RELKIND_MATVIEW:
+ case RELKIND_PARTITIONED_REL:
options = heap_reloptions(classForm->relkind, datum, false);
break;
case RELKIND_VIEW:
@@ -1376,6 +1377,7 @@ heap_reloptions(char relkind, Datum reloptions, bool validate)
return (bytea *) rdopts;
case RELKIND_RELATION:
case RELKIND_MATVIEW:
+ case RELKIND_PARTITIONED_REL:
return default_reloptions(reloptions, validate, RELOPT_KIND_HEAP);
default:
/* other relkinds are not supported */
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index 1ce7610..4dc6300 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -11,7 +11,7 @@ top_builddir = ../../..
include $(top_builddir)/src/Makefile.global
OBJS = catalog.o dependency.o heap.o index.o indexing.o namespace.o aclchk.o \
- objectaccess.o objectaddress.o pg_aggregate.o pg_collation.o \
+ objectaccess.o objectaddress.o partition.o pg_aggregate.o pg_collation.o \
pg_constraint.o pg_conversion.o \
pg_depend.o pg_enum.o pg_inherits.o pg_largeobject.o pg_namespace.o \
pg_operator.o pg_proc.o pg_range.o pg_db_role_setting.o pg_shdepend.o \
@@ -41,7 +41,7 @@ POSTGRES_BKI_SRCS = $(addprefix $(top_srcdir)/src/include/catalog/,\
pg_foreign_data_wrapper.h pg_foreign_server.h pg_user_mapping.h \
pg_foreign_table.h pg_policy.h pg_replication_origin.h \
pg_default_acl.h pg_init_privs.h pg_seclabel.h pg_shseclabel.h \
- pg_collation.h pg_range.h pg_transform.h \
+ pg_collation.h pg_range.h pg_transform.h pg_partitioned.h\
toasting.h indexing.h \
)
diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
index d074e85..1818506 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -762,6 +762,8 @@ objectsInSchemaToOids(GrantObjectType objtype, List *nspnames)
case ACL_OBJECT_RELATION:
objs = getRelationsInNamespace(namespaceId, RELKIND_RELATION);
objects = list_concat(objects, objs);
+ objs = getRelationsInNamespace(namespaceId, RELKIND_PARTITIONED_REL);
+ objects = list_concat(objects, objs);
objs = getRelationsInNamespace(namespaceId, RELKIND_VIEW);
objects = list_concat(objects, objs);
objs = getRelationsInNamespace(namespaceId, RELKIND_MATVIEW);
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index e997b57..932a48b 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -41,6 +41,7 @@
#include "catalog/heap.h"
#include "catalog/index.h"
#include "catalog/objectaccess.h"
+#include "catalog/partition.h"
#include "catalog/pg_attrdef.h"
#include "catalog/pg_collation.h"
#include "catalog/pg_constraint.h"
@@ -1101,9 +1102,10 @@ heap_create_with_catalog(const char *relname,
{
/* Use binary-upgrade override for pg_class.oid/relfilenode? */
if (IsBinaryUpgrade &&
- (relkind == RELKIND_RELATION || relkind == RELKIND_SEQUENCE ||
- relkind == RELKIND_VIEW || relkind == RELKIND_MATVIEW ||
- relkind == RELKIND_COMPOSITE_TYPE || relkind == RELKIND_FOREIGN_TABLE))
+ (relkind == RELKIND_RELATION || relkind == RELKIND_PARTITIONED_REL ||
+ relkind == RELKIND_SEQUENCE || relkind == RELKIND_VIEW ||
+ relkind == RELKIND_MATVIEW || relkind == RELKIND_COMPOSITE_TYPE ||
+ relkind == RELKIND_FOREIGN_TABLE))
{
if (!OidIsValid(binary_upgrade_next_heap_pg_class_oid))
ereport(ERROR,
@@ -1134,6 +1136,7 @@ heap_create_with_catalog(const char *relname,
switch (relkind)
{
case RELKIND_RELATION:
+ case RELKIND_PARTITIONED_REL:
case RELKIND_VIEW:
case RELKIND_MATVIEW:
case RELKIND_FOREIGN_TABLE:
@@ -1178,6 +1181,7 @@ heap_create_with_catalog(const char *relname,
* such is an implementation detail: toast tables, sequences and indexes.
*/
if (IsUnderPostmaster && (relkind == RELKIND_RELATION ||
+ relkind == RELKIND_PARTITIONED_REL ||
relkind == RELKIND_VIEW ||
relkind == RELKIND_MATVIEW ||
relkind == RELKIND_FOREIGN_TABLE ||
@@ -1353,7 +1357,8 @@ heap_create_with_catalog(const char *relname,
if (relpersistence == RELPERSISTENCE_UNLOGGED)
{
Assert(relkind == RELKIND_RELATION || relkind == RELKIND_MATVIEW ||
- relkind == RELKIND_TOASTVALUE);
+ relkind == RELKIND_TOASTVALUE || relkind == RELKIND_PARTITIONED_REL);
+
heap_create_init_fork(new_rel_desc);
}
@@ -1800,6 +1805,12 @@ heap_drop_with_catalog(Oid relid)
}
/*
+ * If a partitioned table, delete the pg_partitioned tuples.
+ */
+ if (rel->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+ RemovePartitionKeyByRelId(relid);
+
+ /*
* Schedule unlinking of the relation's physical files at commit.
*/
if (rel->rd_rel->relkind != RELKIND_VIEW &&
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index 1324461..215375e 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -1204,7 +1204,8 @@ get_relation_by_qualified_name(ObjectType objtype, List *objname,
RelationGetRelationName(relation))));
break;
case OBJECT_TABLE:
- if (relation->rd_rel->relkind != RELKIND_RELATION)
+ if (relation->rd_rel->relkind != RELKIND_RELATION &&
+ relation->rd_rel->relkind != RELKIND_PARTITIONED_REL)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not a table",
@@ -3252,6 +3253,10 @@ getRelationDescription(StringInfo buffer, Oid relid)
appendStringInfo(buffer, _("table %s"),
relname);
break;
+ case RELKIND_PARTITIONED_REL:
+ appendStringInfo(buffer, _("partitioned table %s"),
+ relname);
+ break;
case RELKIND_INDEX:
appendStringInfo(buffer, _("index %s"),
relname);
@@ -3708,6 +3713,9 @@ getRelationTypeDescription(StringInfo buffer, Oid relid, int32 objectSubId)
case RELKIND_RELATION:
appendStringInfoString(buffer, "table");
break;
+ case RELKIND_PARTITIONED_REL:
+ appendStringInfoString(buffer, "partitioned table");
+ break;
case RELKIND_INDEX:
appendStringInfoString(buffer, "index");
break;
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
new file mode 100644
index 0000000..5e238dc
--- /dev/null
+++ b/src/backend/catalog/partition.c
@@ -0,0 +1,451 @@
+/*-------------------------------------------------------------------------
+ *
+ * partition.c
+ * Partitioning related utility functions.
+ *
+ * Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ * src/backend/utils/misc/partition.c
+ *
+ *-------------------------------------------------------------------------
+*/
+
+#include "postgres.h"
+
+#include "access/heapam.h"
+#include "access/htup_details.h"
+#include "access/nbtree.h"
+#include "catalog/dependency.h"
+#include "catalog/indexing.h"
+#include "catalog/objectaddress.h"
+#include "catalog/partition.h"
+#include "catalog/pg_collation.h"
+#include "catalog/pg_opclass.h"
+#include "catalog/pg_partitioned.h"
+#include "catalog/pg_type.h"
+#include "executor/executor.h"
+#include "miscadmin.h"
+#include "nodes/nodeFuncs.h"
+#include "optimizer/clauses.h"
+#include "optimizer/planmain.h"
+#include "storage/lmgr.h"
+#include "utils/array.h"
+#include "utils/builtins.h"
+#include "utils/datum.h"
+#include "utils/memutils.h"
+#include "utils/fmgroids.h"
+#include "utils/inval.h"
+#include "utils/lsyscache.h"
+#include "utils/syscache.h"
+
+static PartitionKey CopyPartitionKey(PartitionKey fromkey);
+static KeyTypeCollInfo *copy_key_type_coll_info(int nkeycols,
+ KeyTypeCollInfo *tcinfo);
+static void free_key_type_coll_info(KeyTypeCollInfo *tcinfo);
+
+/*
+ * StorePartitionKey
+ * Store the partition keys of rel into pg_partitioned catalog
+ */
+void
+StorePartitionKey(Relation rel,
+ char strategy,
+ int16 partnatts,
+ AttrNumber *partattrs,
+ List *partexprs,
+ Oid *partopclass)
+{
+ int i;
+ int2vector *partattrs_vec;
+ oidvector *partopclass_vec;
+ Datum partexprsDatum;
+ Relation pg_partitioned;
+ HeapTuple tuple;
+ Datum values[Natts_pg_partitioned];
+ bool nulls[Natts_pg_partitioned];
+ ObjectAddress myself;
+ ObjectAddress referenced;
+
+ Assert(rel->rd_rel->relkind == RELKIND_PARTITIONED_REL);
+
+ tuple = SearchSysCache1(PARTEDRELID,
+ ObjectIdGetDatum(RelationGetRelid(rel)));
+ /* Cannot already exist */
+ Assert(!HeapTupleIsValid(tuple));
+
+ /*
+ * Copy the partition key, opclass info into arrays (should we
+ * make the caller pass them like this to start with?)
+ */
+ partattrs_vec = buildint2vector(partattrs, partnatts);
+ partopclass_vec = buildoidvector(partopclass, partnatts);
+
+ /* Convert the partition key expressions (if any) to a text datum */
+ if (partexprs)
+ {
+ char *exprsString;
+
+ exprsString = nodeToString(partexprs);
+ partexprsDatum = CStringGetTextDatum(exprsString);
+ pfree(exprsString);
+ }
+ else
+ partexprsDatum = (Datum) 0;
+
+ pg_partitioned = heap_open(PartitionedRelationId, RowExclusiveLock);
+
+ MemSet(nulls, false, sizeof(nulls));
+
+ /* Only this can ever be NULL */
+ if (!partexprsDatum)
+ nulls[Anum_pg_partitioned_partexprs - 1] = true;
+
+ values[Anum_pg_partitioned_partedrelid - 1] = ObjectIdGetDatum(RelationGetRelid(rel));
+ values[Anum_pg_partitioned_partstrat - 1] = CharGetDatum(strategy);
+ values[Anum_pg_partitioned_partnatts - 1] = Int16GetDatum(partnatts);
+ values[Anum_pg_partitioned_partattrs - 1] = PointerGetDatum(partattrs_vec);
+ values[Anum_pg_partitioned_partclass - 1] = PointerGetDatum(partopclass_vec);
+ values[Anum_pg_partitioned_partexprs - 1] = partexprsDatum;
+
+ tuple = heap_form_tuple(RelationGetDescr(pg_partitioned), values, nulls);
+
+ simple_heap_insert(pg_partitioned, tuple);
+
+ /* Update the indexes on pg_partitioned */
+ CatalogUpdateIndexes(pg_partitioned, tuple);
+
+ /* Make this relation dependent on a few things: */
+ myself.classId = RelationRelationId;
+ myself.objectId = RelationGetRelid(rel);;
+ myself.objectSubId = 0;
+
+ /* Operator class per key column */
+ for (i = 0; i < partnatts; i++)
+ {
+ referenced.classId = OperatorClassRelationId;
+ referenced.objectId = partopclass[i];
+ referenced.objectSubId = 0;
+
+ recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+ }
+
+ /*
+ * Things inside key expressions
+ *
+ * An ugliness: normal dependencies also created on attribute references
+ * as part of the following.
+ */
+ if (partexprs)
+ recordDependencyOnSingleRelExpr(&myself,
+ (Node *) partexprs,
+ RelationGetRelid(rel),
+ DEPENDENCY_NORMAL,
+ DEPENDENCY_NORMAL);
+ /* Tell world about the key */
+ CacheInvalidateRelcache(rel);
+
+ heap_close(pg_partitioned, RowExclusiveLock);
+ heap_freetuple(tuple);
+}
+
+/*
+ * RemovePartitionKeyByRelId
+ * Remove pg_partitioned entry for a relation
+ */
+void
+RemovePartitionKeyByRelId(Oid relid)
+{
+ Relation rel;
+ HeapTuple tuple;
+
+ rel = heap_open(PartitionedRelationId, RowExclusiveLock);
+
+ tuple = SearchSysCache1(PARTEDRELID, ObjectIdGetDatum(relid));
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "cache lookup failed for partition key of relation %u", relid);
+
+ simple_heap_delete(rel, &tuple->t_self);
+
+ /* Update the indexes on pg_partitioned */
+ CatalogUpdateIndexes(rel, tuple);
+
+ ReleaseSysCache(tuple);
+ heap_close(rel, RowExclusiveLock);
+}
+
+/*
+ * RelationBuildPartitionKey
+ * Build and attach to relcache partition key data of relation
+ *
+ * Note that the partition key data attached to a relcache entry must be
+ * stored CacheMemoryContext to ensure it survives as long as the relcache
+ * entry. But we should be running in a less long-lived working context.
+ * To avoid leaking cache memory if this routine fails partway through,
+ * we build in working memory and then copy the completed structure into
+ * cache memory.
+ */
+void
+RelationBuildPartitionKey(Relation relation)
+{
+ Form_pg_partitioned form;
+ Relation catalog;
+ HeapTuple tuple;
+ bool isnull;
+ int i;
+ PartitionKey key;
+ int2vector *partattrs;
+ oidvector *opclass;
+ KeyTypeCollInfo *tcinfo;
+ ListCell *partexprs_item;
+ Datum datum;
+ MemoryContext oldcxt;
+
+ tuple = SearchSysCache1(PARTEDRELID,
+ ObjectIdGetDatum(RelationGetRelid(relation)));
+ /*
+ * Happend when relcache is built before the partition key is stored in
+ * system catalog
+ */
+ if (!HeapTupleIsValid(tuple))
+ return;
+
+ form = (Form_pg_partitioned) GETSTRUCT(tuple);
+
+ /* Allocate in the hopefully short-lived working context */
+ key = (PartitionKey) palloc0(sizeof(PartitionKeyData));
+ key->strategy = form->partstrat;
+ key->partnatts = form->partnatts;
+
+ /* Open the catalog for its tuple descriptor */
+ catalog = heap_open(PartitionedRelationId, AccessShareLock);
+ datum = fastgetattr(tuple, Anum_pg_partitioned_partattrs,
+ RelationGetDescr(catalog),
+ &isnull);
+ Assert(!isnull);
+ partattrs = (int2vector *) DatumGetPointer(datum);
+
+ datum = fastgetattr(tuple, Anum_pg_partitioned_partclass,
+ RelationGetDescr(catalog),
+ &isnull);
+ Assert(!isnull);
+ opclass = (oidvector *) DatumGetPointer(datum);
+
+ datum = heap_getattr(tuple,
+ Anum_pg_partitioned_partexprs,
+ RelationGetDescr(catalog),
+ &isnull);
+
+ if (!isnull)
+ {
+ char *exprsString;
+ Node *exprs;
+
+ exprsString = TextDatumGetCString(datum);
+ exprs = stringToNode(exprsString);
+ pfree(exprsString);
+
+ /*
+ * Run the expressions through eval_const_expressions. This is
+ * not just an optimization, but is necessary, because eventually
+ * the planner will be comparing them to similarly-processed qual
+ * clauses, and may fail to detect valid matches without this.
+ * We don't bother with canonicalize_qual, however.
+ */
+ exprs = eval_const_expressions(NULL, (Node *) exprs);
+
+ /* May as well fix opfuncids too */
+ fix_opfuncids((Node *) exprs);
+ key->partexprs = (List *) exprs;
+ }
+
+ key->partattrs = (AttrNumber *) palloc0(key->partnatts * sizeof(AttrNumber));
+ key->partopfamily = (Oid *) palloc0(key->partnatts * sizeof(Oid));
+ key->partsupfunc = (FmgrInfo *) palloc0(key->partnatts * sizeof(FmgrInfo));
+
+ /* Gather type and collation info as well */
+ key->tcinfo = tcinfo = (KeyTypeCollInfo *) palloc0(sizeof(KeyTypeCollInfo));
+ tcinfo->typid = (Oid *) palloc0(key->partnatts * sizeof(Oid));
+ tcinfo->typmod = (int32 *) palloc0(key->partnatts * sizeof(int32));
+ tcinfo->typlen = (int16 *) palloc0(key->partnatts * sizeof(int16));
+ tcinfo->typbyval = (bool *) palloc0(key->partnatts * sizeof(bool));
+ tcinfo->typalign = (char *) palloc0(key->partnatts * sizeof(char));
+ tcinfo->typcoll = (Oid *) palloc0(key->partnatts * sizeof(Oid));
+
+ /*
+ * Copy partattrs. Further, determine and store the opfamily and
+ * btree support function per attribute.
+ */
+ partexprs_item = list_head(key->partexprs);
+ for (i = 0; i < key->partnatts; i++)
+ {
+ HeapTuple tuple;
+ AttrNumber attno;
+ Form_pg_opclass form;
+ Oid funcid;
+
+ key->partattrs[i] = attno = partattrs->values[i];
+
+ /* Collect type information */
+ if (attno != InvalidAttrNumber)
+ {
+ tcinfo->typid[i] = relation->rd_att->attrs[attno - 1]->atttypid;
+ tcinfo->typmod[i] = relation->rd_att->attrs[attno - 1]->atttypmod;
+ tcinfo->typcoll[i] = relation->rd_att->attrs[attno - 1]->attcollation;
+ }
+ else
+ {
+ tcinfo->typid[i] = exprType(lfirst(partexprs_item));
+ tcinfo->typmod[i] = exprTypmod(lfirst(partexprs_item));
+ tcinfo->typcoll[i] = exprCollation(lfirst(partexprs_item));
+ partexprs_item = lnext(partexprs_item);
+ }
+ get_typlenbyvalalign(tcinfo->typid[i],
+ &tcinfo->typlen[i],
+ &tcinfo->typbyval[i],
+ &tcinfo->typalign[i]);
+
+ tuple = SearchSysCache1(CLAOID, ObjectIdGetDatum(opclass->values[i]));
+
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "cache lookup failed for opclass %u", opclass->values[i]);
+
+ form = (Form_pg_opclass) GETSTRUCT(tuple);
+ key->partopfamily[i] = form->opcfamily;
+
+ /*
+ * Btree support function is good to cover the list and range
+ * partitionig related needs.
+ */
+ funcid = get_opfamily_proc(form->opcfamily,
+ form->opcintype, form->opcintype,
+ BTORDER_PROC);
+
+ fmgr_info(funcid, &key->partsupfunc[i]);
+ ReleaseSysCache(tuple);
+ }
+
+ ReleaseSysCache(tuple);
+ heap_close(catalog, AccessShareLock);
+
+ /* Success --- now copy to the cache memory */
+ oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
+ relation->rd_partkey = CopyPartitionKey(key);
+ MemoryContextSwitchTo(oldcxt);
+
+ FreePartitionKey(key);
+}
+
+/*
+ * CopyPartitionKey
+ *
+ * The copy is allocated in the current memory context.
+ */
+static PartitionKey
+CopyPartitionKey(PartitionKey fromkey)
+{
+ PartitionKey newkey;
+
+ newkey = (PartitionKey) palloc0(sizeof(PartitionKeyData));
+
+ newkey->strategy = fromkey->strategy;
+ newkey->partnatts = fromkey->partnatts;
+
+ newkey->partattrs = (AttrNumber *)
+ palloc(newkey->partnatts * sizeof(AttrNumber));
+ memcpy(newkey->partattrs, fromkey->partattrs,
+ newkey->partnatts * sizeof(AttrNumber));
+
+ newkey->partopfamily = (Oid *) palloc(newkey->partnatts * sizeof(Oid));
+ memcpy(newkey->partopfamily, fromkey->partopfamily,
+ newkey->partnatts * sizeof(Oid));
+
+ newkey->partsupfunc = (FmgrInfo *)
+ palloc(newkey->partnatts * sizeof(FmgrInfo));
+ memcpy(newkey->partsupfunc, fromkey->partsupfunc,
+ newkey->partnatts * sizeof(FmgrInfo));
+
+ newkey->partexprs = copyObject(fromkey->partexprs);
+ newkey->tcinfo = copy_key_type_coll_info(newkey->partnatts,
+ fromkey->tcinfo);
+
+ return newkey;
+}
+
+/*
+ * copy_key_type_coll_info
+ *
+ * The copy is allocated in the current memory context.
+ */
+static KeyTypeCollInfo *
+copy_key_type_coll_info(int nkeycols, KeyTypeCollInfo *tcinfo)
+{
+ KeyTypeCollInfo *result = (KeyTypeCollInfo *)
+ palloc0(sizeof(KeyTypeCollInfo));
+
+ result->typid = (Oid *) palloc0(nkeycols * sizeof(Oid));
+ memcpy(result->typid, tcinfo->typid, nkeycols * sizeof(Oid));
+
+ result->typmod = (int32 *) palloc0(nkeycols * sizeof(int32));
+ memcpy(result->typmod, tcinfo->typmod, nkeycols * sizeof(int32));
+
+ result->typlen = (int16 *) palloc0(nkeycols * sizeof(int16));
+ memcpy(result->typlen, tcinfo->typlen, nkeycols * sizeof(int16));
+
+ result->typbyval = (bool *) palloc0(nkeycols * sizeof(bool));
+ memcpy(result->typbyval, tcinfo->typbyval, nkeycols * sizeof(bool));
+
+ result->typalign = (char *) palloc0(nkeycols * sizeof(bool));
+ memcpy(result->typalign, tcinfo->typalign, nkeycols * sizeof(char));
+
+ result->typcoll = (Oid *) palloc0(nkeycols * sizeof(Oid));
+ memcpy(result->typcoll, tcinfo->typcoll, nkeycols * sizeof(Oid));
+
+ return result;
+}
+
+/*
+ * free_key_type_info
+ */
+static void
+free_key_type_coll_info(KeyTypeCollInfo *tcinfo)
+{
+ Assert(tcinfo != NULL);
+
+ pfree(tcinfo->typid);
+ pfree(tcinfo->typmod);
+ pfree(tcinfo->typlen);
+ pfree(tcinfo->typbyval);
+ pfree(tcinfo->typalign);
+ pfree(tcinfo->typcoll);
+ pfree(tcinfo);
+}
+
+/*
+ * Does relid have a pg_partitioned entry?
+ */
+bool
+relid_is_partitioned(Oid relid)
+{
+ return SearchSysCacheExists1(PARTEDRELID, ObjectIdGetDatum(relid));
+}
+
+/*
+ * FreePartitionKeys
+ */
+void
+FreePartitionKey(PartitionKey key)
+{
+ if (key == NULL)
+ return;
+
+ pfree(key->partattrs);
+ pfree(key->partopfamily);
+ pfree(key->partsupfunc);
+ if (key->partexprs)
+ pfree(key->partexprs);
+ free_key_type_coll_info(key->tcinfo);
+ pfree(key);
+}
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 3201476..403738d 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -1718,6 +1718,12 @@ BeginCopyTo(Relation rel,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot copy from sequence \"%s\"",
RelationGetRelationName(rel))));
+ else if (rel->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot copy from partitioned table \"%s\"",
+ RelationGetRelationName(rel)),
+ errhint("Try the COPY (SELECT ...) TO variant.")));
else
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 4ef6b80..e8d25e8 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -369,7 +369,8 @@ DefineIndex(Oid relationId,
namespaceId = RelationGetNamespace(rel);
if (rel->rd_rel->relkind != RELKIND_RELATION &&
- rel->rd_rel->relkind != RELKIND_MATVIEW)
+ rel->rd_rel->relkind != RELKIND_MATVIEW &&
+ rel->rd_rel->relkind != RELKIND_PARTITIONED_REL)
{
if (rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
diff --git a/src/backend/commands/lockcmds.c b/src/backend/commands/lockcmds.c
index 175d1f3..62671a8 100644
--- a/src/backend/commands/lockcmds.c
+++ b/src/backend/commands/lockcmds.c
@@ -88,7 +88,7 @@ RangeVarCallbackForLockTable(const RangeVar *rv, Oid relid, Oid oldrelid,
* check */
/* Currently, we only allow plain tables to be locked */
- if (relkind != RELKIND_RELATION)
+ if (relkind != RELKIND_RELATION && relkind != RELKIND_PARTITIONED_REL)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not a table",
diff --git a/src/backend/commands/policy.c b/src/backend/commands/policy.c
index 93d15e4..de75da1 100644
--- a/src/backend/commands/policy.c
+++ b/src/backend/commands/policy.c
@@ -88,7 +88,7 @@ RangeVarCallbackForPolicy(const RangeVar *rv, Oid relid, Oid oldrelid,
rv->relname)));
/* Relation type MUST be a table. */
- if (relkind != RELKIND_RELATION)
+ if (relkind != RELKIND_RELATION && relkind != RELKIND_PARTITIONED_REL)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not a table", rv->relname)));
diff --git a/src/backend/commands/seclabel.c b/src/backend/commands/seclabel.c
index 5bd7e12..8bc8638 100644
--- a/src/backend/commands/seclabel.c
+++ b/src/backend/commands/seclabel.c
@@ -107,6 +107,7 @@ ExecSecLabelStmt(SecLabelStmt *stmt)
* are the only relkinds for which pg_dump will dump labels).
*/
if (relation->rd_rel->relkind != RELKIND_RELATION &&
+ relation->rd_rel->relkind != RELKIND_PARTITIONED_REL &&
relation->rd_rel->relkind != RELKIND_VIEW &&
relation->rd_rel->relkind != RELKIND_MATVIEW &&
relation->rd_rel->relkind != RELKIND_COMPOSITE_TYPE &&
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index c98f981..5dcd4cf 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -1467,6 +1467,7 @@ process_owned_by(Relation seqrel, List *owned_by)
/* Must be a regular or foreign table */
if (!(tablerel->rd_rel->relkind == RELKIND_RELATION ||
+ tablerel->rd_rel->relkind == RELKIND_PARTITIONED_REL ||
tablerel->rd_rel->relkind == RELKIND_FOREIGN_TABLE))
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index d03147d..bea7c72 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -29,6 +29,7 @@
#include "catalog/indexing.h"
#include "catalog/namespace.h"
#include "catalog/objectaccess.h"
+#include "catalog/partition.h"
#include "catalog/pg_am.h"
#include "catalog/pg_collation.h"
#include "catalog/pg_constraint.h"
@@ -262,6 +263,12 @@ struct DropRelationCallbackState
bool concurrent;
};
+/* for find_attr_reference_walker */
+typedef struct
+{
+ AttrNumber attnum;
+} find_attr_reference_context;
+
/* Alter table target-type flags for ATSimplePermissions */
#define ATT_TABLE 0x0001
#define ATT_VIEW 0x0002
@@ -438,6 +445,9 @@ static void ComputePartitionAttrs(Oid relid, List *partParams,
AttrNumber *partattrs,
List **partexprs,
Oid *partoplass);
+static bool find_attr_reference_walker(Node *node,
+ find_attr_reference_context *context);
+static bool is_partition_attr(Relation rel, AttrNumber attnum, bool *is_expr);
/* ----------------------------------------------------------------
@@ -601,7 +611,8 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
* affect other relkinds, but it would complicate interpretOidsOption().
*/
localHasOids = interpretOidsOption(stmt->options,
- (relkind == RELKIND_RELATION));
+ (relkind == RELKIND_RELATION ||
+ relkind == RELKIND_PARTITIONED_REL));
descriptor->tdhasoid = (localHasOids || parentOidCount > 0);
/*
@@ -715,7 +726,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
AddRelationNewConstraints(rel, rawDefaults, stmt->constraints,
true, true, false);
- /* Process partition key, if any */
+ /* Process and store partition key, if any */
if (stmt->partby)
{
PartitionBy *partby;
@@ -728,6 +739,8 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
partby = transformPartitionBy(rel, stmt->partby);
ComputePartitionAttrs(relationId, stmt->partby->partParams,
partattrs, &partexprs, partopclass);
+ StorePartitionKey(rel, partby->strategy, partnatts, partattrs,
+ partexprs, partopclass);
}
ObjectAddressSet(address, RelationRelationId, relationId);
@@ -975,7 +988,15 @@ RangeVarCallbackForDropRelation(const RangeVar *rel, Oid relOid, Oid oldRelOid,
return; /* concurrently dropped, so nothing to do */
classform = (Form_pg_class) GETSTRUCT(tuple);
- if (classform->relkind != relkind)
+ /*
+ * Both normal and partitioned tables are dropped using DROP TABLE.
+ * RemoveRelations however never passes RELKIND_PARTITIONED_REL as
+ * relkind for OBJECT_TABLE relations. A mismatch below may have
+ * to do with that. So, check.
+ */
+ if (classform->relkind != relkind &&
+ (relkind == RELKIND_RELATION &&
+ classform->relkind != RELKIND_PARTITIONED_REL))
DropErrorMsgWrongType(rel->relname, classform->relkind, relkind);
/* Allow DROP to either table owner or schema owner */
@@ -1313,7 +1334,8 @@ truncate_check_rel(Relation rel)
AclResult aclresult;
/* Only allow truncate on regular tables */
- if (rel->rd_rel->relkind != RELKIND_RELATION)
+ if (rel->rd_rel->relkind != RELKIND_RELATION &&
+ rel->rd_rel->relkind != RELKIND_PARTITIONED_REL)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not a table",
@@ -1541,8 +1563,16 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
*/
relation = heap_openrv(parent, ShareUpdateExclusiveLock);
+ /* Cannot inherit from partitioned tables */
+ if (relation->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot inherit from partitioned table \"%s\"",
+ parent->relname)));
+
if (relation->rd_rel->relkind != RELKIND_RELATION &&
- relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
+ relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE &&
+ relation->rd_rel->relkind != RELKIND_PARTITIONED_REL)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("inherited relation \"%s\" is not a table or foreign table",
@@ -2182,6 +2212,7 @@ renameatt_check(Oid myrelid, Form_pg_class classform, bool recursing)
* restriction.
*/
if (relkind != RELKIND_RELATION &&
+ relkind != RELKIND_PARTITIONED_REL &&
relkind != RELKIND_VIEW &&
relkind != RELKIND_MATVIEW &&
relkind != RELKIND_COMPOSITE_TYPE &&
@@ -4311,6 +4342,7 @@ ATSimplePermissions(Relation rel, int allowed_targets)
switch (rel->rd_rel->relkind)
{
case RELKIND_RELATION:
+ case RELKIND_PARTITIONED_REL:
actual_target = ATT_TABLE;
break;
case RELKIND_VIEW:
@@ -4547,6 +4579,7 @@ find_composite_type_dependencies(Oid typeOid, Relation origRelation,
att = rel->rd_att->attrs[pg_depend->objsubid - 1];
if (rel->rd_rel->relkind == RELKIND_RELATION ||
+ rel->rd_rel->relkind == RELKIND_PARTITIONED_REL ||
rel->rd_rel->relkind == RELKIND_MATVIEW)
{
if (origTypeName)
@@ -5437,6 +5470,7 @@ ATPrepSetStatistics(Relation rel, const char *colName, Node *newValue, LOCKMODE
* allowSystemTableMods to be turned on.
*/
if (rel->rd_rel->relkind != RELKIND_RELATION &&
+ rel->rd_rel->relkind != RELKIND_PARTITIONED_REL &&
rel->rd_rel->relkind != RELKIND_MATVIEW &&
rel->rd_rel->relkind != RELKIND_INDEX &&
rel->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
@@ -5711,6 +5745,73 @@ ATPrepDropColumn(List **wqueue, Relation rel, bool recurse, bool recursing,
cmd->subtype = AT_DropColumnRecurse;
}
+/* Checks if a Var node is for a given attnum */
+static bool
+find_attr_reference_walker(Node *node, find_attr_reference_context *context)
+{
+ if (node == NULL)
+ return false;
+
+ if (IsA(node, Var))
+ {
+ Var *variable = (Var *) node;
+ AttrNumber attnum = variable->varattno;
+
+ if (attnum == context->attnum)
+ return true;
+ }
+
+ return expression_tree_walker(node, find_attr_reference_walker, context);
+}
+
+/*
+ * Checks if attnum is a partition attribute for rel
+ *
+ * Sets *is_expr if attnum is found to be referenced in some partition key
+ * expression.
+ */
+static bool
+is_partition_attr(Relation rel, AttrNumber attnum, bool *is_expr)
+{
+ PartitionKey key;
+ ListCell *partexpr_item;
+ int i;
+
+ if (rel->rd_rel->relkind != RELKIND_PARTITIONED_REL)
+ {
+ *is_expr = false;
+ return false;
+ }
+
+ key = rel->rd_partkey;
+
+ partexpr_item = list_head(key->partexprs);
+ for (i = 0; i < key->partnatts; i++)
+ {
+ AttrNumber partatt = key->partattrs[i];
+
+ if(partatt != 0)
+ {
+ *is_expr = false;
+ if (attnum == partatt)
+ return true;
+ }
+ else
+ {
+ find_attr_reference_context context;
+
+ *is_expr = true;
+ context.attnum = attnum;
+ if (find_attr_reference_walker(lfirst(partexpr_item), &context))
+ return true;
+
+ partexpr_item = lnext(partexpr_item);
+ }
+ }
+
+ return false;
+}
+
/*
* Return value is the address of the dropped column.
*/
@@ -5725,6 +5826,7 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
AttrNumber attnum;
List *children;
ObjectAddress object;
+ bool is_expr;
/* At top level, permission check was done in ATPrepCmd, else do it */
if (recursing)
@@ -5769,6 +5871,19 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
errmsg("cannot drop inherited column \"%s\"",
colName)));
+ /* Don't drop columns used in partition key */
+ if (is_partition_attr(rel, attnum, &is_expr))
+ {
+ if (!is_expr)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("cannot drop column named in partition key")));
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("cannot drop column referenced in partition key expression")));
+ }
+
ReleaseSysCache(tuple);
/*
@@ -6287,6 +6402,12 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
* Validity checks (permission checks wait till we have the column
* numbers)
*/
+ if (pkrel->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot reference relation \"%s\"", RelationGetRelationName(pkrel)),
+ errdetail("Referencing partitioned tables in foreign key constraints is not supported.")));
+
if (pkrel->rd_rel->relkind != RELKIND_RELATION)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
@@ -7881,6 +8002,7 @@ ATPrepAlterColumnType(List **wqueue,
NewColumnValue *newval;
ParseState *pstate = make_parsestate(NULL);
AclResult aclresult;
+ bool is_expr;
if (rel->rd_rel->reloftype && !recursing)
ereport(ERROR,
@@ -7911,6 +8033,19 @@ ATPrepAlterColumnType(List **wqueue,
errmsg("cannot alter inherited column \"%s\"",
colName)));
+ /* Don't alter columns used in partition key */
+ if (is_partition_attr(rel, attnum, &is_expr))
+ {
+ if (!is_expr)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("cannot alter column named in partition key")));
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("cannot alter column referenced in partition key expression")));
+ }
+
/* Look up the target type */
typenameTypeIdAndMod(NULL, typeName, &targettype, &targettypmod);
@@ -7926,7 +8061,8 @@ ATPrepAlterColumnType(List **wqueue,
list_make1_oid(rel->rd_rel->reltype),
false);
- if (tab->relkind == RELKIND_RELATION)
+ if (tab->relkind == RELKIND_RELATION ||
+ tab->relkind == RELKIND_PARTITIONED_REL)
{
/*
* Set up an expression to transform the old data value to the new
@@ -8953,6 +9089,7 @@ ATExecChangeOwner(Oid relationOid, Oid newOwnerId, bool recursing, LOCKMODE lock
switch (tuple_class->relkind)
{
case RELKIND_RELATION:
+ case RELKIND_PARTITIONED_REL:
case RELKIND_VIEW:
case RELKIND_MATVIEW:
case RELKIND_FOREIGN_TABLE:
@@ -9415,6 +9552,7 @@ ATExecSetRelOptions(Relation rel, List *defList, AlterTableType operation,
switch (rel->rd_rel->relkind)
{
case RELKIND_RELATION:
+ case RELKIND_PARTITIONED_REL:
case RELKIND_TOASTVALUE:
case RELKIND_MATVIEW:
(void) heap_reloptions(rel->rd_rel->relkind, newOptions, true);
@@ -9837,7 +9975,8 @@ AlterTableMoveAll(AlterTableMoveAllStmt *stmt)
/* Only move the object type requested */
if ((stmt->objtype == OBJECT_TABLE &&
- relForm->relkind != RELKIND_RELATION) ||
+ relForm->relkind != RELKIND_RELATION &&
+ relForm->relkind != RELKIND_PARTITIONED_REL) ||
(stmt->objtype == OBJECT_INDEX &&
relForm->relkind != RELKIND_INDEX) ||
(stmt->objtype == OBJECT_MATVIEW &&
@@ -10036,6 +10175,11 @@ ATPrepAddInherit(Relation child_rel)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot change inheritance of typed table")));
+
+ if (child_rel->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot change inheritance of partitioned table")));
}
/*
@@ -10087,6 +10231,12 @@ ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode)
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot inherit to temporary relation of another session")));
+ /* Prevent partitioned tables from becoming inheritance parents */
+ if (parent_rel->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot inherit from partitioned table \"%s\"", parent->relname)));
+
/*
* Check for duplicates in the list of parents, and determine the highest
* inhseqno already present; we'll use the next one for the new parent.
@@ -11465,6 +11615,7 @@ AlterTableNamespaceInternal(Relation rel, Oid oldNspOid, Oid nspOid,
/* Fix other dependent stuff */
if (rel->rd_rel->relkind == RELKIND_RELATION ||
+ rel->rd_rel->relkind == RELKIND_PARTITIONED_REL ||
rel->rd_rel->relkind == RELKIND_MATVIEW)
{
AlterIndexNamespaces(classRel, rel, oldNspOid, nspOid, objsMoved);
@@ -11914,7 +12065,7 @@ RangeVarCallbackOwnsTable(const RangeVar *relation,
if (!relkind)
return;
if (relkind != RELKIND_RELATION && relkind != RELKIND_TOASTVALUE &&
- relkind != RELKIND_MATVIEW)
+ relkind != RELKIND_MATVIEW && relkind != RELKIND_PARTITIONED_REL)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not a table or materialized view", relation->relname)));
@@ -12068,6 +12219,7 @@ RangeVarCallbackForAlterRelation(const RangeVar *rv, Oid relid, Oid oldrelid,
*/
if (IsA(stmt, AlterObjectSchemaStmt) &&
relkind != RELKIND_RELATION &&
+ relkind != RELKIND_PARTITIONED_REL &&
relkind != RELKIND_VIEW &&
relkind != RELKIND_MATVIEW &&
relkind != RELKIND_SEQUENCE &&
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index 6f728ff..c1e1c4d 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -174,7 +174,8 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
* Triggers must be on tables or views, and there are additional
* relation-type-specific restrictions.
*/
- if (rel->rd_rel->relkind == RELKIND_RELATION)
+ if (rel->rd_rel->relkind == RELKIND_RELATION ||
+ rel->rd_rel->relkind == RELKIND_PARTITIONED_REL)
{
/* Tables can't have INSTEAD OF triggers */
if (stmt->timing != TRIGGER_TYPE_BEFORE &&
@@ -1112,6 +1113,7 @@ RemoveTriggerById(Oid trigOid)
rel = heap_open(relid, AccessExclusiveLock);
if (rel->rd_rel->relkind != RELKIND_RELATION &&
+ rel->rd_rel->relkind != RELKIND_PARTITIONED_REL &&
rel->rd_rel->relkind != RELKIND_VIEW &&
rel->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
ereport(ERROR,
@@ -1218,7 +1220,8 @@ RangeVarCallbackForRenameTrigger(const RangeVar *rv, Oid relid, Oid oldrelid,
/* only tables and views can have triggers */
if (form->relkind != RELKIND_RELATION && form->relkind != RELKIND_VIEW &&
- form->relkind != RELKIND_FOREIGN_TABLE)
+ form->relkind != RELKIND_FOREIGN_TABLE &&
+ form->relkind != RELKIND_PARTITIONED_REL)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not a table, view, or foreign table",
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 93361a0..003a30f 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -1301,9 +1301,14 @@ vacuum_rel(Oid relid, RangeVar *relation, int options, VacuumParams *params)
onerel->rd_rel->relkind != RELKIND_MATVIEW &&
onerel->rd_rel->relkind != RELKIND_TOASTVALUE)
{
- ereport(WARNING,
- (errmsg("skipping \"%s\" --- cannot vacuum non-tables or special system tables",
- RelationGetRelationName(onerel))));
+ if (onerel->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+ ereport(WARNING,
+ (errmsg("skipping \"%s\" --- cannot vacuum partitioned tables",
+ RelationGetRelationName(onerel))));
+ else
+ ereport(WARNING,
+ (errmsg("skipping \"%s\" --- cannot vacuum non-tables or special system tables",
+ RelationGetRelationName(onerel))));
relation_close(onerel, lmode);
PopActiveSnapshot();
CommitTransactionCommand();
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index ac02304..67492a5 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -1019,6 +1019,7 @@ CheckValidResultRel(Relation resultRel, CmdType operation)
switch (resultRel->rd_rel->relkind)
{
case RELKIND_RELATION:
+ case RELKIND_PARTITIONED_REL:
/* OK */
break;
case RELKIND_SEQUENCE:
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index e62c8aa..7ad1140 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1871,6 +1871,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
relkind = resultRelInfo->ri_RelationDesc->rd_rel->relkind;
if (relkind == RELKIND_RELATION ||
+ relkind == RELKIND_PARTITIONED_REL ||
relkind == RELKIND_MATVIEW)
{
j->jf_junkAttNo = ExecFindJunkAttribute(j, "ctid");
diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c
index b56fd4f..77ffd84 100644
--- a/src/backend/parser/parse_agg.c
+++ b/src/backend/parser/parse_agg.c
@@ -488,9 +488,9 @@ check_agglevels_and_constraints(ParseState *pstate, Node *expr)
break;
case EXPR_KIND_PARTITION_KEY:
if (isAgg)
- err = _("aggregate functions are not allowed in partition key expressions");
+ err = _("aggregate functions are not allowed in partition key expression");
else
- err = _("grouping operations are not allowed in partition key expressions");
+ err = _("grouping operations are not allowed in partition key expression");
break;
@@ -852,7 +852,7 @@ transformWindowFuncCall(ParseState *pstate, WindowFunc *wfunc,
err = _("window functions are not allowed in trigger WHEN conditions");
break;
case EXPR_KIND_PARTITION_KEY:
- err = _("window functions are not allowed in partition key expressions");
+ err = _("window functions are not allowed in partition key expression");
break;
/*
diff --git a/src/backend/parser/parse_clause.c b/src/backend/parser/parse_clause.c
index 751de4b..307e383 100644
--- a/src/backend/parser/parse_clause.c
+++ b/src/backend/parser/parse_clause.c
@@ -21,6 +21,7 @@
#include "access/tsmapi.h"
#include "catalog/catalog.h"
#include "catalog/heap.h"
+#include "catalog/partition.h"
#include "catalog/pg_am.h"
#include "catalog/pg_constraint_fn.h"
#include "catalog/pg_type.h"
@@ -200,6 +201,13 @@ setTargetTable(ParseState *pstate, RangeVar *relation,
relation->alias, inh, false);
pstate->p_target_rangetblentry = rte;
+ /*
+ * Override specified inheritance option, if relation is a partitioned
+ * table and it's a target of INSERT (ie, alsoSource == false).
+ */
+ if (alsoSource)
+ rte->inh |= relid_is_partitioned(rte->relid);
+
/* assume new rte is at end */
rtindex = list_length(pstate->p_rtable);
Assert(rte == rt_fetch(rtindex, pstate->p_rtable));
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index b53b7f1..147c6da 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -1724,7 +1724,7 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
err = _("cannot use subquery in trigger WHEN condition");
break;
case EXPR_KIND_PARTITION_KEY:
- err = _("cannot use subquery in partition key expressions");
+ err = _("cannot use subquery in partition key expression");
break;
/*
diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c
index 81332b5..abcc1d6 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -20,6 +20,7 @@
#include "access/sysattr.h"
#include "catalog/heap.h"
#include "catalog/namespace.h"
+#include "catalog/partition.h"
#include "catalog/pg_type.h"
#include "funcapi.h"
#include "nodes/makefuncs.h"
@@ -1216,9 +1217,12 @@ addRangeTableEntry(ParseState *pstate,
*
* The initial default on access checks is always check-for-READ-access,
* which is the right thing for all except target tables.
+ *
+ * Note: we override the specified inheritance option, if relation is a
+ * partitioned table.
*/
rte->lateral = false;
- rte->inh = inh;
+ rte->inh = inh || relid_is_partitioned(RelationGetRelid(rel));
rte->inFromCl = inFromCl;
rte->requiredPerms = ACL_SELECT;
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e267f74..465597d 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -33,6 +33,7 @@
#include "catalog/heap.h"
#include "catalog/index.h"
#include "catalog/namespace.h"
+#include "catalog/partition.h"
#include "catalog/pg_am.h"
#include "catalog/pg_collation.h"
#include "catalog/pg_constraint.h"
@@ -821,6 +822,7 @@ transformTableLikeClause(CreateStmtContext *cxt, TableLikeClause *table_like_cla
relation = relation_openrv(table_like_clause->relation, AccessShareLock);
if (relation->rd_rel->relkind != RELKIND_RELATION &&
+ relation->rd_rel->relkind != RELKIND_PARTITIONED_REL &&
relation->rd_rel->relkind != RELKIND_VIEW &&
relation->rd_rel->relkind != RELKIND_MATVIEW &&
relation->rd_rel->relkind != RELKIND_COMPOSITE_TYPE &&
@@ -2577,7 +2579,7 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
cxt.blist = NIL;
cxt.alist = NIL;
cxt.pkey = NULL;
- cxt.ispartitioned = false;
+ cxt.ispartitioned = relid_is_partitioned(relid);
/*
* The only subtypes that currently require parse transformation handling
diff --git a/src/backend/rewrite/rewriteDefine.c b/src/backend/rewrite/rewriteDefine.c
index 96f3f37..db80c90 100644
--- a/src/backend/rewrite/rewriteDefine.c
+++ b/src/backend/rewrite/rewriteDefine.c
@@ -260,6 +260,7 @@ DefineQueryRewrite(char *rulename,
* blocks them for users. Don't mention them in the error message.
*/
if (event_relation->rd_rel->relkind != RELKIND_RELATION &&
+ event_relation->rd_rel->relkind != RELKIND_PARTITIONED_REL &&
event_relation->rd_rel->relkind != RELKIND_MATVIEW &&
event_relation->rd_rel->relkind != RELKIND_VIEW)
ereport(ERROR,
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index a22a11e..f429dd6 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -1222,6 +1222,7 @@ rewriteTargetListUD(Query *parsetree, RangeTblEntry *target_rte,
TargetEntry *tle;
if (target_relation->rd_rel->relkind == RELKIND_RELATION ||
+ target_relation->rd_rel->relkind == RELKIND_PARTITIONED_REL ||
target_relation->rd_rel->relkind == RELKIND_MATVIEW)
{
/*
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index ac50c2a..220cbfc 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -975,10 +975,13 @@ ProcessUtilitySlow(Node *parsetree,
{
Datum toast_options;
static char *validnsps[] = HEAP_RELOPT_NAMESPACES;
+ char relkind = ((CreateStmt *) stmt)->partby != NULL
+ ? RELKIND_PARTITIONED_REL
+ : RELKIND_RELATION;
/* Create the table itself */
address = DefineRelation((CreateStmt *) stmt,
- RELKIND_RELATION,
+ relkind,
InvalidOid, NULL);
EventTriggerCollectSimpleCommand(address,
secondaryObject,
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 6dfa6b9..167c856 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -33,6 +33,7 @@
#include "catalog/pg_language.h"
#include "catalog/pg_opclass.h"
#include "catalog/pg_operator.h"
+#include "catalog/pg_partitioned.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_trigger.h"
#include "catalog/pg_type.h"
@@ -315,6 +316,7 @@ static char *pg_get_indexdef_worker(Oid indexrelid, int colno,
const Oid *excludeOps,
bool attrsOnly, bool showTblSpc,
int prettyFlags);
+static char *pg_get_partkeydef_worker(Oid relId, int prettyFlags);
static char *pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
int prettyFlags);
static text *pg_get_expr_worker(text *expr, Oid relid, const char *relname,
@@ -1297,6 +1299,151 @@ pg_get_indexdef_worker(Oid indexrelid, int colno,
return buf.data;
}
+/*
+ * pg_get_partkeydef
+ *
+ * Returns the partition key specification, ie, the following:
+ *
+ * PARTITION BY { RANGE | LIST } (column [USING opclass_name] [, ...])
+ */
+Datum
+pg_get_partkeydef(PG_FUNCTION_ARGS)
+{
+ Oid relationId = PG_GETARG_OID(0);
+ int prettyFlags;
+
+ prettyFlags = PRETTYFLAG_INDENT;
+ PG_RETURN_TEXT_P(string_to_text(pg_get_partkeydef_worker(relationId,
+ prettyFlags)));
+}
+
+/*
+ * Internal workhorse to decompile a partition key definition.
+ */
+static char *
+pg_get_partkeydef_worker(Oid relId, int prettyFlags)
+{
+ Form_pg_partitioned form;
+ HeapTuple tuple;
+ oidvector *partclass;
+ List *partexprs;
+ ListCell *partexpr_item;
+ List *context;
+ Datum datum;
+ bool isnull;
+ StringInfoData buf;
+ int keyno;
+ char *str;
+ char *sep;
+
+ tuple = SearchSysCache1(PARTEDRELID, ObjectIdGetDatum(relId));
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "cache lookup failed for partition key of %u", relId);
+
+ form = (Form_pg_partitioned) GETSTRUCT(tuple);
+
+ Assert(form->partedrelid == relId);
+
+ /* Must get partclass, and partexprs the hard way */
+ datum = SysCacheGetAttr(PARTEDRELID, tuple,
+ Anum_pg_partitioned_partclass, &isnull);
+ Assert(!isnull);
+ partclass = (oidvector *) DatumGetPointer(datum);
+
+ /*
+ * Get the partition key expressions, if any. (NOTE: we do not use the
+ * relcache versions of the expressions, because we want to display
+ * non-const-folded expressions.)
+ */
+ if (!heap_attisnull(tuple, Anum_pg_partitioned_partexprs))
+ {
+ Datum exprsDatum;
+ bool isnull;
+ char *exprsString;
+
+ exprsDatum = SysCacheGetAttr(PARTEDRELID, tuple,
+ Anum_pg_partitioned_partexprs, &isnull);
+ Assert(!isnull);
+ exprsString = TextDatumGetCString(exprsDatum);
+ partexprs = (List *) stringToNode(exprsString);
+ pfree(exprsString);
+ }
+ else
+ partexprs = NIL;
+
+ partexpr_item = list_head(partexprs);
+ context = deparse_context_for(get_relation_name(relId), relId);
+
+ /*
+ * Start the partition key definition.
+ */
+ initStringInfo(&buf);
+
+ appendStringInfo(&buf, "PARTITION BY ");
+
+ switch (form->partstrat)
+ {
+ case 'l':
+ appendStringInfo(&buf, "LIST");
+ break;
+ case 'r':
+ appendStringInfo(&buf, "RANGE");
+ break;
+ }
+
+ /*
+ * Report the partition key columns
+ */
+ appendStringInfo(&buf, " (");
+ sep = "";
+ for (keyno = 0; keyno < form->partnatts; keyno++)
+ {
+ AttrNumber attnum = form->partattrs.values[keyno];
+ Oid keycoltype;
+
+ appendStringInfoString(&buf, sep);
+ sep = ", ";
+ if (attnum != 0)
+ {
+ /* Simple partition key column */
+ char *attname;
+
+ attname = get_relid_attribute_name(relId, attnum);
+ appendStringInfoString(&buf, quote_identifier(attname));
+ keycoltype = get_atttype(relId, attnum);
+ }
+ else
+ {
+ /* partition key expression */
+ Node *partkey;
+
+ if (partexpr_item == NULL)
+ elog(ERROR, "too few entries in partexprs list");
+ partkey = (Node *) lfirst(partexpr_item);
+ partexpr_item = lnext(partexpr_item);
+ /* Deparse */
+ str = deparse_expression_pretty(partkey, context, false, false,
+ prettyFlags, 0);
+
+ /* Need parens if it's not a bare function call */
+ if (partkey && IsA(partkey, FuncExpr) &&
+ ((FuncExpr *) partkey)->funcformat == COERCE_EXPLICIT_CALL)
+ appendStringInfoString(&buf, str);
+ else
+ appendStringInfo(&buf, "(%s)", str);
+ keycoltype = exprType(partkey);
+ }
+
+ /* Add the operator class name, if not default */
+ get_opclass_name(partclass->values[keyno], keycoltype, &buf);
+ }
+ appendStringInfoChar(&buf, ')');
+
+ /* Clean up */
+ ReleaseSysCache(tuple);
+
+ return buf.data;
+}
/*
* pg_get_constraintdef
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 79cc3df..e48a1f4 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -40,6 +40,7 @@
#include "catalog/index.h"
#include "catalog/indexing.h"
#include "catalog/namespace.h"
+#include "catalog/partition.h"
#include "catalog/pg_am.h"
#include "catalog/pg_amproc.h"
#include "catalog/pg_attrdef.h"
@@ -431,6 +432,7 @@ RelationParseRelOptions(Relation relation, HeapTuple tuple)
switch (relation->rd_rel->relkind)
{
case RELKIND_RELATION:
+ case RELKIND_PARTITIONED_REL:
case RELKIND_TOASTVALUE:
case RELKIND_INDEX:
case RELKIND_VIEW:
@@ -1049,6 +1051,12 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
else
relation->rd_rsdesc = NULL;
+ /* if it's a partitioned table, initialize key info */
+ if (relation->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+ RelationBuildPartitionKey(relation);
+ else
+ relation->rd_partkey = NULL;
+
/*
* if it's an index, initialize index-related information
*/
@@ -2046,6 +2054,7 @@ RelationDestroyRelation(Relation relation, bool remember_tupdesc)
MemoryContextDelete(relation->rd_rulescxt);
if (relation->rd_rsdesc)
MemoryContextDelete(relation->rd_rsdesc->rscxt);
+ FreePartitionKey(relation->rd_partkey);
if (relation->rd_fdwroutine)
pfree(relation->rd_fdwroutine);
pfree(relation);
@@ -2987,7 +2996,9 @@ RelationBuildLocalRelation(const char *relname,
/* system relations and non-table objects don't have one */
if (!IsSystemNamespace(relnamespace) &&
- (relkind == RELKIND_RELATION || relkind == RELKIND_MATVIEW))
+ (relkind == RELKIND_RELATION ||
+ relkind == RELKIND_PARTITIONED_REL ||
+ relkind == RELKIND_MATVIEW))
rel->rd_rel->relreplident = REPLICA_IDENTITY_DEFAULT;
else
rel->rd_rel->relreplident = REPLICA_IDENTITY_NOTHING;
@@ -4972,6 +4983,7 @@ load_relcache_init_file(bool shared)
rel->rd_rulescxt = NULL;
rel->trigdesc = NULL;
rel->rd_rsdesc = NULL;
+ rel->rd_partkey = NULL;
rel->rd_indexprs = NIL;
rel->rd_indpred = NIL;
rel->rd_exclops = NULL;
diff --git a/src/backend/utils/cache/syscache.c b/src/backend/utils/cache/syscache.c
index 65ffe84..a6563fe 100644
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -48,6 +48,7 @@
#include "catalog/pg_opclass.h"
#include "catalog/pg_operator.h"
#include "catalog/pg_opfamily.h"
+#include "catalog/pg_partitioned.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_range.h"
#include "catalog/pg_rewrite.h"
@@ -568,6 +569,17 @@ static const struct cachedesc cacheinfo[] = {
},
8
},
+ {PartitionedRelationId, /* PARTEDRELID */
+ PartitionedRelidIndexId,
+ 1,
+ {
+ Anum_pg_partitioned_partedrelid,
+ 0,
+ 0,
+ 0
+ },
+ 128
+ },
{ProcedureRelationId, /* PROCNAMEARGSNSP */
ProcedureNameArgsNspIndexId,
3,
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 1267afb..163d013 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -1234,9 +1234,10 @@ expand_table_name_patterns(Archive *fout,
"SELECT c.oid"
"\nFROM pg_catalog.pg_class c"
"\n LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace"
- "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c')\n",
+ "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c')\n",
RELKIND_RELATION, RELKIND_SEQUENCE, RELKIND_VIEW,
- RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+ RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+ RELKIND_PARTITIONED_REL);
processSQLNamePattern(GetConnection(fout), query, cell->val, true,
false, "n.nspname", "c.relname", NULL,
"pg_catalog.pg_table_is_visible(c.oid)");
@@ -2093,6 +2094,9 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo, bool oids)
/* Skip FOREIGN TABLEs (no data to dump) */
if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
return;
+ /* Skip partitioned tables (data in partitions) */
+ if (tbinfo->relkind == RELKIND_PARTITIONED_REL)
+ return;
/* Don't dump data in unlogged tables, if so requested */
if (tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED &&
@@ -5171,6 +5175,7 @@ getTables(Archive *fout, int *numTables)
int i_reloftype;
int i_relpages;
int i_changed_acl;
+ int i_partkeydef;
/* Make sure we are in proper schema */
selectSourceSchema(fout, "pg_catalog");
@@ -5256,6 +5261,7 @@ getTables(Archive *fout, int *numTables)
"OR %s IS NOT NULL"
"))"
"AS changed_acl "
+ "CASE WHEN c.relkind = 'P' THEN pg_catalog.pg_get_partkeydef(c.oid) ELSE NULL END AS partkeydef "
"FROM pg_class c "
"LEFT JOIN pg_depend d ON "
"(c.relkind = '%c' AND "
@@ -5267,7 +5273,7 @@ getTables(Archive *fout, int *numTables)
"(c.oid = pip.objoid AND pip.classoid = "
"(SELECT oid FROM pg_class "
"WHERE relname = 'pg_class') AND pip.objsubid = 0) "
- "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c') "
+ "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c', '%c') "
"ORDER BY c.oid",
acl_subquery->data,
racl_subquery->data,
@@ -5281,7 +5287,8 @@ getTables(Archive *fout, int *numTables)
RELKIND_SEQUENCE,
RELKIND_RELATION, RELKIND_SEQUENCE,
RELKIND_VIEW, RELKIND_COMPOSITE_TYPE,
- RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+ RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+ RELKIND_PARTITIONED_REL);
destroyPQExpBuffer(acl_subquery);
destroyPQExpBuffer(racl_subquery);
@@ -5843,6 +5850,7 @@ getTables(Archive *fout, int *numTables)
i_toastreloptions = PQfnumber(res, "toast_reloptions");
i_reloftype = PQfnumber(res, "reloftype");
i_changed_acl = PQfnumber(res, "changed_acl");
+ i_partkeydef = PQfnumber(res, "partkeydef");
if (dopt->lockWaitTimeout && fout->remoteVersion >= 70300)
{
@@ -5913,6 +5921,7 @@ getTables(Archive *fout, int *numTables)
else
tblinfo[i].checkoption = pg_strdup(PQgetvalue(res, i, i_checkoption));
tblinfo[i].toast_reloptions = pg_strdup(PQgetvalue(res, i, i_toastreloptions));
+ tblinfo[i].partkeydef = pg_strdup(PQgetvalue(res, i, i_partkeydef));
/* other fields were zeroed above */
@@ -5956,7 +5965,9 @@ getTables(Archive *fout, int *numTables)
*
* We only need to lock the table for certain components; see pg_dump.h
*/
- if (tblinfo[i].dobj.dump && tblinfo[i].relkind == RELKIND_RELATION &&
+ if (tblinfo[i].dobj.dump &&
+ (tblinfo[i].relkind == RELKIND_RELATION ||
+ tblinfo->relkind == RELKIND_PARTITIONED_REL) &&
(tblinfo[i].dobj.dump & DUMP_COMPONENTS_REQUIRING_LOCK))
{
resetPQExpBuffer(query);
@@ -6044,7 +6055,10 @@ getInherits(Archive *fout, int *numInherits)
/* find all the inheritance information */
- appendPQExpBufferStr(query, "SELECT inhrelid, inhparent FROM pg_inherits");
+ appendPQExpBufferStr(query,
+ "SELECT inhrelid, inhparent "
+ "FROM pg_inherits "
+ "WHERE inhparent NOT IN (SELECT oid FROM pg_class WHERE relkind = 'P')");
res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
@@ -15413,6 +15427,9 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
appendPQExpBufferChar(q, ')');
}
+ if (tbinfo->relkind == RELKIND_PARTITIONED_REL)
+ appendPQExpBuffer(q, "\n%s", tbinfo->partkeydef);
+
if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
appendPQExpBuffer(q, "\nSERVER %s", fmtId(srvname));
}
@@ -15473,6 +15490,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
*/
if (dopt->binary_upgrade &&
(tbinfo->relkind == RELKIND_RELATION ||
+ tbinfo->relkind == RELKIND_PARTITIONED_REL ||
tbinfo->relkind == RELKIND_FOREIGN_TABLE))
{
for (j = 0; j < tbinfo->numatts; j++)
@@ -15491,7 +15509,8 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
appendStringLiteralAH(q, fmtId(tbinfo->dobj.name), fout);
appendPQExpBufferStr(q, "::pg_catalog.regclass;\n");
- if (tbinfo->relkind == RELKIND_RELATION)
+ if (tbinfo->relkind == RELKIND_RELATION ||
+ tbinfo->relkind == RELKIND_PARTITIONED_REL)
appendPQExpBuffer(q, "ALTER TABLE ONLY %s ",
fmtId(tbinfo->dobj.name));
else
@@ -15708,6 +15727,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
* dump properties we only have ALTER TABLE syntax for
*/
if ((tbinfo->relkind == RELKIND_RELATION ||
+ tbinfo->relkind == RELKIND_PARTITIONED_REL ||
tbinfo->relkind == RELKIND_MATVIEW) &&
tbinfo->relreplident != REPLICA_IDENTITY_DEFAULT)
{
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 2bfa2d9..0292859 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -310,6 +310,7 @@ typedef struct _tableInfo
bool *inhNotNull; /* true if NOT NULL is inherited */
struct _attrDefInfo **attrdefs; /* DEFAULT expressions */
struct _constraintInfo *checkexprs; /* CHECK constraints */
+ char *partkeydef; /* partition key definition */
/*
* Stuff computed only for dumpable tables.
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 0a771bd..78a1fdb 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -764,6 +764,7 @@ permissionsList(const char *pattern)
" WHEN 'm' THEN '%s'"
" WHEN 'S' THEN '%s'"
" WHEN 'f' THEN '%s'"
+ " WHEN 'P' THEN '%s'"
" END as \"%s\",\n"
" ",
gettext_noop("Schema"),
@@ -773,6 +774,7 @@ permissionsList(const char *pattern)
gettext_noop("materialized view"),
gettext_noop("sequence"),
gettext_noop("foreign table"),
+ gettext_noop("partitioned table"),
gettext_noop("Type"));
printACLColumn(&buf, "c.relacl");
@@ -819,7 +821,7 @@ permissionsList(const char *pattern)
appendPQExpBufferStr(&buf, "\nFROM pg_catalog.pg_class c\n"
" LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace\n"
- "WHERE c.relkind IN ('r', 'v', 'm', 'S', 'f')\n");
+ "WHERE c.relkind IN ('r', 'v', 'm', 'S', 'f', 'P')\n");
/*
* Unless a schema pattern is specified, we suppress system and temp
@@ -1463,8 +1465,8 @@ describeOneTableDetails(const char *schemaname,
* types, and foreign tables (c.f. CommentObject() in comment.c).
*/
if (tableinfo.relkind == 'r' || tableinfo.relkind == 'v' ||
- tableinfo.relkind == 'm' ||
- tableinfo.relkind == 'f' || tableinfo.relkind == 'c')
+ tableinfo.relkind == 'm' || tableinfo.relkind == 'f' ||
+ tableinfo.relkind == 'c' || tableinfo.relkind == 'P')
appendPQExpBufferStr(&buf, ", pg_catalog.col_description(a.attrelid, a.attnum)");
}
@@ -1529,6 +1531,14 @@ describeOneTableDetails(const char *schemaname,
printfPQExpBuffer(&title, _("Foreign table \"%s.%s\""),
schemaname, relationname);
break;
+ case 'P':
+ if (tableinfo.relpersistence == 'u')
+ printfPQExpBuffer(&title, _("Unlogged partitioned table \"%s.%s\""),
+ schemaname, relationname);
+ else
+ printfPQExpBuffer(&title, _("Partitioned table \"%s.%s\""),
+ schemaname, relationname);
+ break;
default:
/* untranslated unknown relkind */
printfPQExpBuffer(&title, "?%c? \"%s.%s\"",
@@ -1542,8 +1552,8 @@ describeOneTableDetails(const char *schemaname,
cols = 2;
if (tableinfo.relkind == 'r' || tableinfo.relkind == 'v' ||
- tableinfo.relkind == 'm' ||
- tableinfo.relkind == 'f' || tableinfo.relkind == 'c')
+ tableinfo.relkind == 'm' || tableinfo.relkind == 'f' ||
+ tableinfo.relkind == 'c' || tableinfo.relkind == 'P')
{
show_modifiers = true;
headers[cols++] = gettext_noop("Modifiers");
@@ -1563,12 +1573,12 @@ describeOneTableDetails(const char *schemaname,
{
headers[cols++] = gettext_noop("Storage");
if (tableinfo.relkind == 'r' || tableinfo.relkind == 'm' ||
- tableinfo.relkind == 'f')
+ tableinfo.relkind == 'f' || tableinfo.relkind == 'P')
headers[cols++] = gettext_noop("Stats target");
/* Column comments, if the relkind supports this feature. */
if (tableinfo.relkind == 'r' || tableinfo.relkind == 'v' ||
- tableinfo.relkind == 'm' ||
- tableinfo.relkind == 'c' || tableinfo.relkind == 'f')
+ tableinfo.relkind == 'm' || tableinfo.relkind == 'c' ||
+ tableinfo.relkind == 'f' || tableinfo.relkind == 'P')
headers[cols++] = gettext_noop("Description");
}
@@ -1668,7 +1678,7 @@ describeOneTableDetails(const char *schemaname,
/* Statistics target, if the relkind supports this feature */
if (tableinfo.relkind == 'r' || tableinfo.relkind == 'm' ||
- tableinfo.relkind == 'f')
+ tableinfo.relkind == 'f' || tableinfo.relkind == 'P')
{
printTableAddCell(&cont, PQgetvalue(res, i, firstvcol + 1),
false, false);
@@ -1676,14 +1686,33 @@ describeOneTableDetails(const char *schemaname,
/* Column comments, if the relkind supports this feature. */
if (tableinfo.relkind == 'r' || tableinfo.relkind == 'v' ||
- tableinfo.relkind == 'm' ||
- tableinfo.relkind == 'c' || tableinfo.relkind == 'f')
+ tableinfo.relkind == 'm' || tableinfo.relkind == 'c' ||
+ tableinfo.relkind == 'f' || tableinfo.relkind == 'P')
printTableAddCell(&cont, PQgetvalue(res, i, firstvcol + 2),
false, false);
}
}
/* Make footers */
+ if (pset.sversion >= 90600 && tableinfo.relkind == 'P')
+ {
+ /* Get the partition key information */
+ PGresult *result;
+ char *partkeydef;
+
+ printfPQExpBuffer(&buf,
+ "SELECT pg_catalog.pg_get_partkeydef('%s'::pg_catalog.oid);",
+ oid);
+ result = PSQLexec(buf.data);
+ if (!result || PQntuples(result) != 1)
+ goto error_return;
+
+ partkeydef = PQgetvalue(result, 0, 0);
+ printfPQExpBuffer(&tmpbuf, _("Partition Key: %s"), partkeydef);
+ printTableAddFooter(&cont, tmpbuf.data);
+ PQclear(result);
+ }
+
if (tableinfo.relkind == 'i')
{
/* Footer information about an index */
@@ -1822,7 +1851,7 @@ describeOneTableDetails(const char *schemaname,
PQclear(result);
}
else if (tableinfo.relkind == 'r' || tableinfo.relkind == 'm' ||
- tableinfo.relkind == 'f')
+ tableinfo.relkind == 'f' || tableinfo.relkind == 'P')
{
/* Footer information about a table */
PGresult *result = NULL;
@@ -2381,7 +2410,7 @@ describeOneTableDetails(const char *schemaname,
* Finish printing the footer information about a table.
*/
if (tableinfo.relkind == 'r' || tableinfo.relkind == 'm' ||
- tableinfo.relkind == 'f')
+ tableinfo.relkind == 'f' || tableinfo.relkind == 'P')
{
PGresult *result;
int tuples;
@@ -2592,7 +2621,7 @@ add_tablespace_footer(printTableContent *const cont, char relkind,
Oid tablespace, const bool newline)
{
/* relkinds for which we support tablespaces */
- if (relkind == 'r' || relkind == 'm' || relkind == 'i')
+ if (relkind == 'r' || relkind == 'm' || relkind == 'i' || relkind == 'P')
{
/*
* We ignore the database default tablespace so that users not using
@@ -2926,6 +2955,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
" WHEN 'S' THEN '%s'"
" WHEN 's' THEN '%s'"
" WHEN 'f' THEN '%s'"
+ " WHEN 'P' THEN '%s'"
" END as \"%s\",\n"
" pg_catalog.pg_get_userbyid(c.relowner) as \"%s\"",
gettext_noop("Schema"),
@@ -2937,6 +2967,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
gettext_noop("sequence"),
gettext_noop("special"),
gettext_noop("foreign table"),
+ gettext_noop("partitioned table"),
gettext_noop("Type"),
gettext_noop("Owner"));
@@ -2975,7 +3006,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
appendPQExpBufferStr(&buf, "\nWHERE c.relkind IN (");
if (showTables)
- appendPQExpBufferStr(&buf, "'r',");
+ appendPQExpBufferStr(&buf, "'r', 'P',");
if (showViews)
appendPQExpBufferStr(&buf, "'v',");
if (showMatViews)
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index a62ffe6..741ac46 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -427,7 +427,7 @@ static const SchemaQuery Query_for_list_of_tables = {
/* catname */
"pg_catalog.pg_class c",
/* selcondition */
- "c.relkind IN ('r')",
+ "c.relkind IN ('r', 'P')",
/* viscondition */
"pg_catalog.pg_table_is_visible(c.oid)",
/* namespace */
@@ -458,7 +458,7 @@ static const SchemaQuery Query_for_list_of_updatables = {
/* catname */
"pg_catalog.pg_class c",
/* selcondition */
- "c.relkind IN ('r', 'f', 'v')",
+ "c.relkind IN ('r', 'f', 'v', 'P')",
/* viscondition */
"pg_catalog.pg_table_is_visible(c.oid)",
/* namespace */
@@ -488,7 +488,7 @@ static const SchemaQuery Query_for_list_of_tsvmf = {
/* catname */
"pg_catalog.pg_class c",
/* selcondition */
- "c.relkind IN ('r', 'S', 'v', 'm', 'f')",
+ "c.relkind IN ('r', 'S', 'v', 'm', 'f', 'P')",
/* viscondition */
"pg_catalog.pg_table_is_visible(c.oid)",
/* namespace */
diff --git a/src/include/catalog/indexing.h b/src/include/catalog/indexing.h
index ca5eb3d..cadb741 100644
--- a/src/include/catalog/indexing.h
+++ b/src/include/catalog/indexing.h
@@ -319,6 +319,9 @@ DECLARE_UNIQUE_INDEX(pg_replication_origin_roiident_index, 6001, on pg_replicati
DECLARE_UNIQUE_INDEX(pg_replication_origin_roname_index, 6002, on pg_replication_origin using btree(roname text_pattern_ops));
#define ReplicationOriginNameIndex 6002
+DECLARE_UNIQUE_INDEX(pg_partitioned_partedrelid_index, 3351, on pg_partitioned using btree(partedrelid oid_ops));
+#define PartitionedRelidIndexId 3351
+
/* last step of initialization script: build the indexes declared above */
BUILD_INDICES
diff --git a/src/include/catalog/partition.h b/src/include/catalog/partition.h
new file mode 100644
index 0000000..2c876c4
--- /dev/null
+++ b/src/include/catalog/partition.h
@@ -0,0 +1,55 @@
+/*-------------------------------------------------------------------------
+ *
+ * partition.h
+ * Header file for structures and utility functions related to
+ * partitioning
+ *
+ * Copyright (c) 2007-2016, PostgreSQL Global Development Group
+ *
+ * src/include/utils/partition.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PARTITION_H
+#define PARTITION_H
+
+#include "fmgr.h"
+#include "utils/relcache.h"
+
+/* Type and collation information for partition key columns */
+typedef struct
+{
+ Oid *typid;
+ int32 *typmod;
+ int16 *typlen;
+ bool *typbyval;
+ char *typalign;
+ Oid *typcoll;
+} KeyTypeCollInfo;
+
+/* Partition key information for one level */
+typedef struct PartitionKeyData
+{
+ char strategy; /* partition strategy */
+ int16 partnatts; /* number of partition attributes */
+ AttrNumber *partattrs; /* partition attnums */
+ Oid *partopfamily; /* OIDs of opfamily per col */
+ FmgrInfo *partsupfunc; /* lookup info for support funcs */
+ List *partexprs; /* partition key expressions, if any */
+ KeyTypeCollInfo *tcinfo; /* type and collation info (all columns) */
+} PartitionKeyData;
+
+typedef struct PartitionKeyData *PartitionKey;
+
+extern void StorePartitionKey(Relation rel,
+ char strategy,
+ int16 partnatts,
+ AttrNumber *partattrs,
+ List *partexprs,
+ Oid *partopclass);
+extern void RemovePartitionKeyByRelId(Oid relid);
+extern void RelationBuildPartitionKey(Relation relation);
+extern void FreePartitionKey(PartitionKey key);
+extern bool relid_is_partitioned(Oid relid);
+
+#endif /* PARTITION_H */
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index fdc67c2..149f619 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -153,6 +153,7 @@ DESCR("");
#define RELKIND_RELATION 'r' /* ordinary table */
+#define RELKIND_PARTITIONED_REL 'P' /* partitioned table */
#define RELKIND_INDEX 'i' /* secondary index */
#define RELKIND_SEQUENCE 'S' /* sequence object */
#define RELKIND_TOASTVALUE 't' /* for out-of-line values */
diff --git a/src/include/catalog/pg_partitioned.h b/src/include/catalog/pg_partitioned.h
new file mode 100644
index 0000000..edc52db
--- /dev/null
+++ b/src/include/catalog/pg_partitioned.h
@@ -0,0 +1,67 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_partitioned.h
+ * definition of the system "partitioned" relation (pg_partitioned)
+ * along with the relation's initial contents.
+ *
+ *
+ * Portions Copyright (c) 1996-2015, PostgreSQL Global Development Group
+ *
+ * $PostgreSQL: pgsql/src/include/catalog/pg_partitioned.h $
+ *
+ * NOTES
+ * the genbki.sh script reads this file and generates .bki
+ * information from the DATA() statements.
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_PARTITIONED_REL_H
+#define PG_PARTITIONED_REL_H
+
+#include "catalog/genbki.h"
+
+/* ----------------
+ * pg_partitioned definition. cpp turns this into
+ * typedef struct FormData_pg_partitioned
+ * ----------------
+ */
+#define PartitionedRelationId 3350
+
+CATALOG(pg_partitioned,3350) BKI_WITHOUT_OIDS
+{
+ Oid partedrelid; /* partitioned table oid */
+ char partstrat; /* partition key strategy */
+ int16 partnatts; /* number of partition key columns */
+
+ /* variable-length fields start here, but we allow direct access to partattrs */
+ int2vector partattrs; /* attribute numbers of partition key
+ * columns */
+
+#ifdef CATALOG_VARLEN
+ oidvector partclass; /* operator class to compare keys */
+ pg_node_tree partexprs; /* expression trees for partition key members
+ * that are not simple column references; one
+ * for each zero entry in partkey[] */
+#endif
+} FormData_pg_partitioned;
+
+/* ----------------
+ * Form_pg_partitioned corresponds to a pointer to a tuple with
+ * the format of pg_partitioned relation.
+ * ----------------
+ */
+typedef FormData_pg_partitioned *Form_pg_partitioned;
+
+/* ----------------
+ * compiler constants for pg_partitioned
+ * ----------------
+ */
+#define Natts_pg_partitioned 6
+#define Anum_pg_partitioned_partedrelid 1
+#define Anum_pg_partitioned_partstrat 2
+#define Anum_pg_partitioned_partnatts 3
+#define Anum_pg_partitioned_partattrs 4
+#define Anum_pg_partitioned_partclass 5
+#define Anum_pg_partitioned_partexprs 6
+
+#endif /* PG_PARTITIONED_REL_H */
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index d94943b..24d7c5f 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -1969,6 +1969,8 @@ DATA(insert OID = 1642 ( pg_get_userbyid PGNSP PGUID 12 1 0 0 0 f f f f t f
DESCR("role name by OID (with fallback)");
DATA(insert OID = 1643 ( pg_get_indexdef PGNSP PGUID 12 1 0 0 0 f f f f t f s s 1 0 25 "26" _null_ _null_ _null_ _null_ _null_ pg_get_indexdef _null_ _null_ _null_ ));
DESCR("index description");
+DATA(insert OID = 3352 ( pg_get_partkeydef PGNSP PGUID 12 1 0 0 0 f f f f t f s s 1 0 25 "26" _null_ _null_ _null_ _null_ _null_ pg_get_partkeydef _null_ _null_ _null_ ));
+DESCR("partition key description");
DATA(insert OID = 1662 ( pg_get_triggerdef PGNSP PGUID 12 1 0 0 0 f f f f t f s s 1 0 25 "26" _null_ _null_ _null_ _null_ _null_ pg_get_triggerdef _null_ _null_ _null_ ));
DESCR("trigger description");
DATA(insert OID = 1387 ( pg_get_constraintdef PGNSP PGUID 12 1 0 0 0 f f f f t f s s 1 0 25 "26" _null_ _null_ _null_ _null_ _null_ pg_get_constraintdef _null_ _null_ _null_ ));
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index 28024f8..ceeb41f 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -42,8 +42,8 @@ extern bool CheckIndexCompatible(Oid oldId,
List *attributeList,
List *exclusionOpNames);
extern Oid GetDefaultOpClass(Oid type_id, Oid am_id);
-extern Oid GetIndexOpClass(List *opclass, Oid attrType,
- char *accessMethodName, Oid accessMethodId);
+extern Oid GetIndexOpClass(List *opclass, Oid attrType,
+ char *accessMethodName, Oid accessMethodId);
/* commands/functioncmds.c */
extern ObjectAddress CreateFunction(CreateFunctionStmt *stmt, const char *queryString);
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
index 01976a1..027665c 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -720,6 +720,7 @@ extern Datum pg_get_viewdef_wrap(PG_FUNCTION_ARGS);
extern Datum pg_get_viewdef_name(PG_FUNCTION_ARGS);
extern Datum pg_get_viewdef_name_ext(PG_FUNCTION_ARGS);
extern Datum pg_get_indexdef(PG_FUNCTION_ARGS);
+extern Datum pg_get_partkeydef(PG_FUNCTION_ARGS);
extern Datum pg_get_indexdef_ext(PG_FUNCTION_ARGS);
extern Datum pg_get_triggerdef(PG_FUNCTION_ARGS);
extern Datum pg_get_triggerdef_ext(PG_FUNCTION_ARGS);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index a0ba417..9bc2a31 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -90,6 +90,8 @@ typedef struct RelationData
/* use "struct" here to avoid needing to include rowsecurity.h: */
struct RowSecurityDesc *rd_rsdesc; /* row security policies, or NULL */
+ struct PartitionKeyData *rd_partkey; /* partition key, or NULL */
+
/* data managed by RelationGetIndexList: */
List *rd_indexlist; /* list of OIDs of indexes on relation */
Oid rd_oidindex; /* OID of unique index on OID, if any */
diff --git a/src/include/utils/syscache.h b/src/include/utils/syscache.h
index 256615b..e727842 100644
--- a/src/include/utils/syscache.h
+++ b/src/include/utils/syscache.h
@@ -72,6 +72,7 @@ enum SysCacheIdentifier
OPEROID,
OPFAMILYAMNAMENSP,
OPFAMILYOID,
+ PARTEDRELID,
PROCNAMEARGSNSP,
PROCOID,
RANGETYPE,
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index aaae9f9..a4cc948 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -326,9 +326,35 @@ ERROR: cannot use a constant expression as partition key
CREATE TABLE fail_agg_in_key (
a int
) PARTITION BY RANGE ((avg(a)));
-ERROR: aggregate functions are not allowed in partition key expressions
+ERROR: aggregate functions are not allowed in partition key expression
CREATE TABLE fail_window_fun_in_key (
a int,
b int
) PARTITION BY RANGE ((avg(a) OVER (PARTITION BY b)));
-ERROR: window functions are not allowed in partition key expressions
+ERROR: window functions are not allowed in partition key expression
+-- check relkind
+CREATE TABLE check_relkind (
+ a int
+) PARTITION BY RANGE (a);
+SELECT relkind FROM pg_class WHERE relname = 'check_relkind';
+ relkind
+---------
+ P
+(1 row)
+
+-- cannot drop column that is part of the partition key
+CREATE TABLE no_drop_alter_partcol (
+ a int
+) PARTITION BY RANGE (a);
+ALTER TABLE no_drop_alter_partcol DROP COLUMN a;
+ERROR: cannot drop column named in partition key
+ALTER TABLE no_drop_alter_partcol ALTER COLUMN a TYPE char(5);
+ERROR: cannot alter column named in partition key
+CREATE TABLE no_drop_alter_partexpr (
+ a text
+) PARTITION BY RANGE ((substring(a from 1 for 1)));
+ALTER TABLE no_drop_alter_partexpr DROP COLUMN a;
+ERROR: cannot drop column referenced in partition key expression
+ALTER TABLE no_drop_alter_partcol ALTER COLUMN a TYPE char(5);
+ERROR: cannot alter column named in partition key
+DROP TABLE check_relkind, no_drop_alter_partcol, no_drop_alter_partexpr;
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index 1c087a3..b1e473d 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -118,6 +118,7 @@ pg_namespace|t
pg_opclass|t
pg_operator|t
pg_opfamily|t
+pg_partitioned|t
pg_pltemplate|t
pg_policy|t
pg_proc|t
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index df47656..022f38b 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -337,3 +337,24 @@ CREATE TABLE fail_window_fun_in_key (
a int,
b int
) PARTITION BY RANGE ((avg(a) OVER (PARTITION BY b)));
+
+-- check relkind
+CREATE TABLE check_relkind (
+ a int
+) PARTITION BY RANGE (a);
+SELECT relkind FROM pg_class WHERE relname = 'check_relkind';
+
+-- cannot drop column that is part of the partition key
+CREATE TABLE no_drop_alter_partcol (
+ a int
+) PARTITION BY RANGE (a);
+ALTER TABLE no_drop_alter_partcol DROP COLUMN a;
+ALTER TABLE no_drop_alter_partcol ALTER COLUMN a TYPE char(5);
+
+CREATE TABLE no_drop_alter_partexpr (
+ a text
+) PARTITION BY RANGE ((substring(a from 1 for 1)));
+ALTER TABLE no_drop_alter_partexpr DROP COLUMN a;
+ALTER TABLE no_drop_alter_partcol ALTER COLUMN a TYPE char(5);
+
+DROP TABLE check_relkind, no_drop_alter_partcol, no_drop_alter_partexpr;
--
1.7.1
[text/x-diff] 0003-Add-syntax-to-create-partitions-v4.patch (77.8K, ../../5732984F.6090200@lab.ntt.co.jp/4-0003-Add-syntax-to-create-partitions-v4.patch)
download | inline diff:
From 204608723694dedc8eac745cd2c66ef1750723d4 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Thu, 3 Mar 2016 18:29:15 +0900
Subject: [PATCH 3/6] Add syntax to create partitions.
As follows:
CREATE TABLE name PARTITION OF parent
[ ( { column_name WITH OPTION column_constraint | table_constraint } [, ...] ) ]
partition_bound_spec [ PARTITION BY partition_key_spec ]
ALTER TABLE parent ATTACH PARTITION name
partition_bound_spec [ VALIDATE | NO VALIDATE ]
ALTER TABLE parent DETACH PARTITION name
partition_bound_spec refers to either list partition bound of form:
FOR VALUES IN (list-of-expressions)
or range partition bound of form:
FOR VALUES range_spec
where range_spec is:
START lower-bound [ INCLUSIVE | EXCLUSIVE ] END upper-bound [ INCLUSIVE | EXCLUSIVE ]
lower-bound and upper-bound is:
{ ( expression [,...] ) | UNBOUNDED }
In the above, each of lower-bound and upper-bound is either a single
expression for a single column key or list of expressions for a multi-
column key or empty. Note that in the absence of explicit specification,
lower-bound is inclusive and upper-bound is exclusive.
A partition is created as inheritance child of the parent with certain
differences from regular inheritance behavior. For example, when a new
partition is created, it cannot contain its own column definitions or
constraints (unlike CREATE TABLE child(...) INHERITS(parent)). When
"attaching" a table as partition of parent, it cannot contain columns
that are not contained in the parent (unlike ALTER TABLE child INHERIT
parent). No dependency is created between a partition and the parent.
One cannot create regular inheritance children of a partitioned table.
Whereas this commit enables inheritance, it does not yet store partition
bound info anywhere, IOW, the fact that a given inheritance child is a
"partition" is not persisted.
---
doc/src/sgml/ref/alter_table.sgml | 57 +++++
doc/src/sgml/ref/create_table.sgml | 77 ++++++-
src/backend/commands/createas.c | 1 +
src/backend/commands/sequence.c | 1 +
src/backend/commands/tablecmds.c | 490 +++++++++++++++++++++++++++---------
src/backend/commands/view.c | 1 +
src/backend/nodes/copyfuncs.c | 48 ++++
src/backend/nodes/equalfuncs.c | 42 +++
src/backend/nodes/outfuncs.c | 27 ++
src/backend/parser/gram.y | 167 ++++++++++++-
src/backend/parser/parse_agg.c | 7 +-
src/backend/parser/parse_expr.c | 23 ++
src/backend/parser/parse_utilcmd.c | 318 +++++++++++++++++++++++
src/include/catalog/partition.h | 1 +
src/include/nodes/nodes.h | 3 +
src/include/nodes/parsenodes.h | 43 +++-
src/include/parser/kwlist.h | 3 +
src/include/parser/parse_node.h | 3 +-
18 files changed, 1182 insertions(+), 130 deletions(-)
diff --git a/doc/src/sgml/ref/alter_table.sgml b/doc/src/sgml/ref/alter_table.sgml
index 5ca211e..97744c9 100644
--- a/doc/src/sgml/ref/alter_table.sgml
+++ b/doc/src/sgml/ref/alter_table.sgml
@@ -77,12 +77,15 @@ ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable>
NOT OF
OWNER TO { <replaceable class="PARAMETER">new_owner</replaceable> | CURRENT_USER | SESSION_USER }
REPLICA IDENTITY { DEFAULT | USING INDEX <replaceable class="PARAMETER">index_name</replaceable> | FULL | NOTHING }
+ ATTACH PARTITION <replaceable class="PARAMETER">name</replaceable> <replaceable class="PARAMETER">partition_bound_spec</replaceable> [ VALIDATE | NO VALIDATE ]
+ DETACH PARTITION <replaceable class="PARAMETER">name</replaceable>
<phrase>and <replaceable class="PARAMETER">table_constraint_using_index</replaceable> is:</phrase>
[ CONSTRAINT <replaceable class="PARAMETER">constraint_name</replaceable> ]
{ UNIQUE | PRIMARY KEY } USING INDEX <replaceable class="PARAMETER">index_name</replaceable>
[ DEFERRABLE | NOT DEFERRABLE ] [ INITIALLY DEFERRED | INITIALLY IMMEDIATE ]
+
</synopsis>
</refsynopsisdiv>
@@ -704,6 +707,51 @@ ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable>
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>ATTACH PARTITION</literal></term>
+ <listitem>
+ <para>
+ This form attaches an existing table as partition of the target table,
+ provided the target table is partitioned. The partition bound
+ specification must correspond with partition method and key of the
+ partitioned table. The table being attached must have all the columns
+ as the target table and no more. Also, it must have all the matching
+ constraints as the target table. That includes <literal>NOT NULL</>
+ and <literal>CHECK</> constraints. <literal>UNIQUE</literal>,
+ <literal>PRIMARY KEY</literal>, and <literal>FOREIGN KEY</literal>
+ constraints are not considered, but this might change in the future.
+ </para>
+
+ <para>
+ If <literal>NO VALIDATE</literal> clause is specified, existing rows in
+ source table are are not checked to see if they violate the partition
+ bound specification. This allows to quickly load a large amount of data
+ into the partitioned table. The default behavior is to perform the
+ check, as if <literal>VALIDATE</literal> clause were specified.
+ </para>
+
+ <para>
+ Note that once the table becomes a partition, it will be dropped or
+ truncated when the parent table is dropped or truncated. Dropping
+ it directly using <literal>DROP TABLE</literal> will fail; it must
+ first be detached from the parent. However, truncating a partition
+ directly works.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><literal>DETACH PARTITION</literal></term>
+ <listitem>
+ <para>
+ This form detaches specified partition of the target table, provided the
+ target table is partitioned and the specified partition exists. The
+ resulting table continues to exist as a standalone table with no ties
+ remaining with the parent partitioned table.
+ </para>
+ </listitem>
+ </varlistentry>
+
</variablelist>
</para>
@@ -936,6 +984,15 @@ ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable>
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><replaceable class="PARAMETER">partition_bound_spec</replaceable></term>
+ <listitem>
+ <para>
+ The partition bound specification for a new partition.
+ </para>
+ </listitem>
+ </varlistentry>
+
</variablelist>
</refsect1>
diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml
index 70a7579..79fb521 100644
--- a/doc/src/sgml/ref/create_table.sgml
+++ b/doc/src/sgml/ref/create_table.sgml
@@ -44,6 +44,17 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
[ ON COMMIT { PRESERVE ROWS | DELETE ROWS | DROP } ]
[ TABLESPACE <replaceable class="PARAMETER">tablespace_name</replaceable> ]
+CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name</replaceable>
+ PARTITION OF <replaceable class="PARAMETER">parent_table</replaceable> [ (
+ { <replaceable class="PARAMETER">column_name</replaceable> WITH OPTIONS [ <replaceable class="PARAMETER">column_constraint</replaceable> [ ... ] ]
+ | <replaceable>table_constraint</replaceable> }
+ [, ... ]
+) ] <replaceable class="PARAMETER">partition_bound_spec</replaceable>
+[ PARTITION BY {RANGE | LIST} ( { <replaceable class="parameter">column_name</replaceable> | ( <replaceable class="parameter">expression</replaceable> ) } [ <replaceable class="parameter">opclass</replaceable> ] [, ...] )
+[ WITH ( <replaceable class="PARAMETER">storage_parameter</replaceable> [= <replaceable class="PARAMETER">value</replaceable>] [, ... ] ) | WITH OIDS | WITHOUT OIDS ]
+[ ON COMMIT { PRESERVE ROWS | DELETE ROWS | DROP } ]
+[ TABLESPACE <replaceable class="PARAMETER">tablespace_name</replaceable> ]
+
<phrase>where <replaceable class="PARAMETER">column_constraint</replaceable> is:</phrase>
[ CONSTRAINT <replaceable class="PARAMETER">constraint_name</replaceable> ]
@@ -72,6 +83,10 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
{ INCLUDING | EXCLUDING } { DEFAULTS | CONSTRAINTS | INDEXES | STORAGE | COMMENTS | ALL }
+<phrase>and <replaceable class="PARAMETER">partition_bound_spec</replaceable> is:</phrase>
+
+FOR VALUES { <replaceable class="PARAMETER">list_spec</replaceable> | <replaceable class="PARAMETER">range_spec</replaceable> }
+
<phrase><replaceable class="PARAMETER">index_parameters</replaceable> in <literal>UNIQUE</literal>, <literal>PRIMARY KEY</literal>, and <literal>EXCLUDE</literal> constraints are:</phrase>
[ WITH ( <replaceable class="PARAMETER">storage_parameter</replaceable> [= <replaceable class="PARAMETER">value</replaceable>] [, ... ] ) ]
@@ -80,8 +95,20 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
<phrase><replaceable class="PARAMETER">exclude_element</replaceable> in an <literal>EXCLUDE</literal> constraint is:</phrase>
{ <replaceable class="parameter">column_name</replaceable> | ( <replaceable class="parameter">expression</replaceable> ) } [ <replaceable class="parameter">opclass</replaceable> ] [ ASC | DESC ] [ NULLS { FIRST | LAST } ]
-</synopsis>
+<phrase><replaceable class="PARAMETER">list_spec</replaceable> in <literal>FOR VALUES</literal> is:</phrase>
+
+IN ( <replaceable class="PARAMETER">expression</replaceable> [, ...] )
+
+<phrase><replaceable class="PARAMETER">range_spec</replaceable> in <literal>FOR VALUES</literal> is:</phrase>
+
+START <replaceable class="PARAMETER">lower-bound</replaceable> [ INCLUSIVE | EXCLUSIVE ] END <replaceable class="PARAMETER">upper-bound</replaceable> [ INCLUSIVE | EXCLUSIVE ]
+
+<phrase>where <replaceable class="PARAMETER">lower-bound</replaceable> and <replaceable class="PARAMETER">upper-bound</replaceable> are:</phrase>
+
+{ ( <replaceable class="PARAMETER">expression</replaceable> [, ...] ) | UNBOUNDED }
+
+</synopsis>
</refsynopsisdiv>
<refsect1 id="SQL-CREATETABLE-description">
@@ -232,6 +259,49 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
</varlistentry>
<varlistentry>
+ <term><literal>PARTITION OF <replaceable class="PARAMETER">parent_table</replaceable></literal></term>
+ <listitem>
+ <para>
+ Creates the table as <firstterm>partition</firstterm> of the specified
+ parent table (name optionally schema-qualified).
+ </para>
+
+ <para>
+ A partition bound specification must be present and must correspond with
+ partition method and key of the parent table. It is checked using the
+ specification that the new partition does not overlap with any existing
+ partitions of the parent.
+ </para>
+
+ <para>
+ A partition cannot have columns other than those inherited from the
+ parent. That includes the <structfield>oid</> column, which can be
+ specified using the <literal>WITH (OIDS)</literal> clause. On the other
+ hand, if parent has the <structfield>oid</> column, the partition
+ inherits the same, overriding the <literal>WITH (OIDS=FALSE)</literal>
+ clause, if any. Defaults and constraints can optionally be specified
+ for each of the inherited columns, which override those in the parent.
+ One can also specify table constraints, in addition to those inherited
+ from the parent. Note that all subsequent schema modifications to the
+ parent propagate to partition.
+ </para>
+
+ <para>
+ Any data row subsequently inserted into the parent table is mapped to
+ and stored in the partition, provided partition key of the row falls
+ within the partition bounds.
+ </para>
+
+ <para>
+ A partition is dropped or truncated when the parent table is dropped or
+ truncated. Dropping it directly using <literal>DROP TABLE</literal>
+ will fail; it must first be <firstterm>detached</> from the parent.
+ However, truncating a partition directly works.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><replaceable class="PARAMETER">column_name</replaceable></term>
<listitem>
<para>
@@ -340,6 +410,11 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
</para>
<para>
+ When specifying for a table being created as partition, one needs to
+ use column names from the parent table as part of the key.
+ </para>
+
+ <para>
Currently, there are following limitations on definition of partitioned
tables: one cannot specify any UNIQUE, PRIMARY KEY, EXCLUDE and/or
FOREIGN KEY constraints.
diff --git a/src/backend/commands/createas.c b/src/backend/commands/createas.c
index cb7a145..427470c 100644
--- a/src/backend/commands/createas.c
+++ b/src/backend/commands/createas.c
@@ -342,6 +342,7 @@ intorel_startup(DestReceiver *self, int operation, TupleDesc typeinfo)
col->is_local = true;
col->is_not_null = false;
col->is_from_type = false;
+ col->is_for_partition = false;
col->storage = 0;
col->raw_default = NULL;
col->cooked_default = NULL;
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index 5dcd4cf..21d9f96 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -159,6 +159,7 @@ DefineSequence(CreateSeqStmt *seq)
coldef->is_local = true;
coldef->is_not_null = true;
coldef->is_from_type = false;
+ coldef->is_for_partition = false;
coldef->storage = 0;
coldef->raw_default = NULL;
coldef->cooked_default = NULL;
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index bea7c72..3af134b 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -279,7 +279,8 @@ typedef struct
static void truncate_check_rel(Relation rel);
static List *MergeAttributes(List *schema, List *supers, char relpersistence,
- List **supOids, List **supconstr, int *supOidCount);
+ List **supOids, List **supconstr, int *supOidCount,
+ bool is_partition);
static bool MergeCheckConstraint(List *constraints, char *name, Node *expr);
static void MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel);
static void MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel);
@@ -448,6 +449,11 @@ static void ComputePartitionAttrs(Oid relid, List *partParams,
static bool find_attr_reference_walker(Node *node,
find_attr_reference_context *context);
static bool is_partition_attr(Relation rel, AttrNumber attnum, bool *is_expr);
+static void CreateInheritance(Relation child_rel, Relation parent_rel);
+static void RemoveInheritance(Relation child_rel, Relation parent_rel);
+static ObjectAddress ATExecAttachPartition(List **wqueue, Relation rel,
+ PartitionCmd *cmd);
+static ObjectAddress ATExecDetachPartition(Relation rel, RangeVar *name);
/* ----------------------------------------------------------------
@@ -590,10 +596,16 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
/*
* Look up inheritance ancestors and generate relation schema, including
* inherited attributes.
+ *
+ * The last parameter implicitly specifies that the table is being created
+ * as partition and schema consists of columns definitions corresponding
+ * to non-dropped columns of the parent constructed such that each
+ * attribute of the table is created as inherited and non-local.
*/
schema = MergeAttributes(schema, stmt->inhRelations,
stmt->relation->relpersistence,
- &inheritOids, &old_constraints, &parentOidCount);
+ &inheritOids, &old_constraints, &parentOidCount,
+ stmt->partbound != NULL);
/*
* Create a tuple descriptor from the relation schema. Note that this
@@ -1452,7 +1464,8 @@ storage_name(char c)
*/
static List *
MergeAttributes(List *schema, List *supers, char relpersistence,
- List **supOids, List **supconstr, int *supOidCount)
+ List **supOids, List **supconstr, int *supOidCount,
+ bool is_partition)
{
ListCell *entry;
List *inhSchema = NIL;
@@ -1497,8 +1510,8 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
/*
* Typed table column option that does not belong to a column from
- * the type. This works because the columns from the type come
- * first in the list.
+ * the type or the partition parent. This works because the columns
+ * from the type or the partition parent come first in the list.
*/
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_COLUMN),
@@ -1525,6 +1538,18 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
coldef->is_from_type = false;
list_delete_cell(schema, rest, prev);
}
+ else if (coldef->is_for_partition)
+ {
+ /*
+ * merge the column options into the column from the parent
+ */
+ coldef->is_not_null = restdef->is_not_null;
+ coldef->raw_default = restdef->raw_default;
+ coldef->cooked_default = restdef->cooked_default;
+ coldef->constraints = restdef->constraints;
+ coldef->is_for_partition = false; /* job is done */
+ list_delete_cell(schema, rest, prev);
+ }
else
ereport(ERROR,
(errcode(ERRCODE_DUPLICATE_COLUMN),
@@ -1563,8 +1588,12 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
*/
relation = heap_openrv(parent, ShareUpdateExclusiveLock);
- /* Cannot inherit from partitioned tables */
- if (relation->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+ /*
+ * Cannot inherit from a partitioned table unless being created as
+ * partition.
+ */
+ if (relation->rd_rel->relkind == RELKIND_PARTITIONED_REL &&
+ !is_partition)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot inherit from partitioned table \"%s\"",
@@ -1710,6 +1739,7 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
def->is_local = false;
def->is_not_null = attribute->attnotnull;
def->is_from_type = false;
+ def->is_for_partition = false;
def->storage = attribute->attstorage;
def->raw_default = NULL;
def->cooked_default = NULL;
@@ -1838,6 +1868,9 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
* If we had no inherited attributes, the result schema is just the
* explicitly declared columns. Otherwise, we need to merge the declared
* columns into the inherited schema list.
+ *
+ * Note: In case of a partition, there are only inherited attributes that
+ * we copied from the parent as so, in transformPartitionOf().
*/
if (inhSchema != NIL)
{
@@ -1867,16 +1900,20 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
/*
* Yes, try to merge the two column definitions. They must
- * have the same type, typmod, and collation.
+ * have the same type, typmod, and collation. Don't output
+ * the notices, if partition, per note above.
*/
- if (exist_attno == schema_attno)
- ereport(NOTICE,
- (errmsg("merging column \"%s\" with inherited definition",
- attributeName)));
- else
- ereport(NOTICE,
- (errmsg("moving and merging column \"%s\" with inherited definition", attributeName),
- errdetail("User-specified column moved to the position of the inherited column.")));
+ if (!is_partition)
+ {
+ if (exist_attno == schema_attno)
+ ereport(NOTICE,
+ (errmsg("merging column \"%s\" with inherited definition",
+ attributeName)));
+ else
+ ereport(NOTICE,
+ (errmsg("moving and merging column \"%s\" with inherited definition", attributeName),
+ errdetail("User-specified column moved to the position of the inherited column.")));
+ }
def = (ColumnDef *) list_nth(inhSchema, exist_attno - 1);
typenameTypeIdAndMod(NULL, def->typeName, &defTypeId, &deftypmod);
typenameTypeIdAndMod(NULL, newdef->typeName, &newTypeId, &newtypmod);
@@ -1913,8 +1950,9 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
storage_name(def->storage),
storage_name(newdef->storage))));
- /* Mark the column as locally defined */
- def->is_local = true;
+ /* Mark the column as locally defined, unless partition */
+ if (!is_partition)
+ def->is_local = true;
/* Merge of NOT NULL constraints = OR 'em together */
def->is_not_null |= newdef->is_not_null;
/* If new def has a default, override previous default */
@@ -2093,16 +2131,20 @@ StoreCatalogInheritance1(Oid relationId, Oid parentOid,
heap_freetuple(tuple);
/*
- * Store a dependency too
+ * Store a dependency too, iff a regular inheritance relationship, which
+ * it's not, if parent is a partitioned table.
*/
- parentobject.classId = RelationRelationId;
- parentobject.objectId = parentOid;
- parentobject.objectSubId = 0;
- childobject.classId = RelationRelationId;
- childobject.objectId = relationId;
- childobject.objectSubId = 0;
+ if (!relid_is_partitioned(parentOid))
+ {
+ parentobject.classId = RelationRelationId;
+ parentobject.objectId = parentOid;
+ parentobject.objectSubId = 0;
+ childobject.classId = RelationRelationId;
+ childobject.objectId = relationId;
+ childobject.objectSubId = 0;
- recordDependencyOn(&childobject, &parentobject, DEPENDENCY_NORMAL);
+ recordDependencyOn(&childobject, &parentobject, DEPENDENCY_NORMAL);
+ }
/*
* Post creation hook of this inheritance. Since object_access_hook
@@ -3108,6 +3150,11 @@ AlterTableGetLockLevel(List *cmds)
cmd_lockmode = AlterTableGetRelOptionsLockLevel((List *) cmd->def);
break;
+ case AT_AttachPartition:
+ case AT_DetachPartition:
+ cmd_lockmode = AccessExclusiveLock;
+ break;
+
default: /* oops */
elog(ERROR, "unrecognized alter table type: %d",
(int) cmd->subtype);
@@ -3425,6 +3472,12 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
/* No command-specific prep needed */
pass = AT_PASS_MISC;
break;
+ case AT_AttachPartition:
+ case AT_DetachPartition:
+ ATSimplePermissions(rel, ATT_TABLE);
+ /* No command-specific prep needed */
+ pass = AT_PASS_MISC;
+ break;
default: /* oops */
elog(ERROR, "unrecognized alter table type: %d",
(int) cmd->subtype);
@@ -3744,6 +3797,12 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
case AT_GenericOptions:
ATExecGenericOptions(rel, (List *) cmd->def);
break;
+ case AT_AttachPartition:
+ ATExecAttachPartition(wqueue, rel, (PartitionCmd *) cmd->def);
+ break;
+ case AT_DetachPartition:
+ ATExecDetachPartition(rel, ((PartitionCmd *) cmd->def)->name);
+ break;
default: /* oops */
elog(ERROR, "unrecognized alter table type: %d",
(int) cmd->subtype);
@@ -10188,12 +10247,7 @@ ATPrepAddInherit(Relation child_rel)
static ObjectAddress
ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode)
{
- Relation parent_rel,
- catalogRelation;
- SysScanDesc scan;
- ScanKeyData key;
- HeapTuple inheritsTuple;
- int32 inhseqno;
+ Relation parent_rel;
List *children;
ObjectAddress address;
@@ -10238,38 +10292,6 @@ ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode)
errmsg("cannot inherit from partitioned table \"%s\"", parent->relname)));
/*
- * Check for duplicates in the list of parents, and determine the highest
- * inhseqno already present; we'll use the next one for the new parent.
- * (Note: get RowExclusiveLock because we will write pg_inherits below.)
- *
- * Note: we do not reject the case where the child already inherits from
- * the parent indirectly; CREATE TABLE doesn't reject comparable cases.
- */
- catalogRelation = heap_open(InheritsRelationId, RowExclusiveLock);
- ScanKeyInit(&key,
- Anum_pg_inherits_inhrelid,
- BTEqualStrategyNumber, F_OIDEQ,
- ObjectIdGetDatum(RelationGetRelid(child_rel)));
- scan = systable_beginscan(catalogRelation, InheritsRelidSeqnoIndexId,
- true, NULL, 1, &key);
-
- /* inhseqno sequences start at 1 */
- inhseqno = 0;
- while (HeapTupleIsValid(inheritsTuple = systable_getnext(scan)))
- {
- Form_pg_inherits inh = (Form_pg_inherits) GETSTRUCT(inheritsTuple);
-
- if (inh->inhparent == RelationGetRelid(parent_rel))
- ereport(ERROR,
- (errcode(ERRCODE_DUPLICATE_TABLE),
- errmsg("relation \"%s\" would be inherited from more than once",
- RelationGetRelationName(parent_rel))));
- if (inh->inhseqno > inhseqno)
- inhseqno = inh->inhseqno;
- }
- systable_endscan(scan);
-
- /*
* Prevent circularity by seeing if proposed parent inherits from child.
* (In particular, this disallows making a rel inherit from itself.)
*
@@ -10302,6 +10324,69 @@ ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode)
RelationGetRelationName(child_rel),
RelationGetRelationName(parent_rel))));
+ /* OK to create inheritance */
+ CreateInheritance(child_rel, parent_rel);
+
+ ObjectAddressSet(address, RelationRelationId,
+ RelationGetRelid(parent_rel));
+
+ /* keep our lock on the parent relation until commit */
+ heap_close(parent_rel, NoLock);
+
+ return address;
+}
+
+/*
+ * CreateInheritance
+ * Catalog manipulation portion of creating inheritance between a child
+ * table and a parent table.
+ *
+ * Common to ATExecAddInherit() and ATExecAttachPartition().
+ */
+static void
+CreateInheritance(Relation child_rel, Relation parent_rel)
+{
+ Relation catalogRelation;
+ SysScanDesc scan;
+ ScanKeyData key;
+ HeapTuple inheritsTuple;
+ int32 inhseqno;
+
+ /* Note: get RowExclusiveLock because we will write pg_inherits below. */
+ catalogRelation = heap_open(InheritsRelationId, RowExclusiveLock);
+
+ /*
+ * Check for duplicates in the list of parents, and determine the highest
+ * inhseqno already present; we'll use the next one for the new parent.
+ * Also, if proposed child is a partition, it cannot already be inheriting.
+ *
+ * Note: we do not reject the case where the child already inherits from
+ * the parent indirectly; CREATE TABLE doesn't reject comparable cases.
+ */
+ ScanKeyInit(&key,
+ Anum_pg_inherits_inhrelid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(RelationGetRelid(child_rel)));
+ scan = systable_beginscan(catalogRelation, InheritsRelidSeqnoIndexId,
+ true, NULL, 1, &key);
+
+ /* inhseqno sequences start at 1 */
+ inhseqno = 0;
+ while (HeapTupleIsValid(inheritsTuple = systable_getnext(scan)))
+ {
+ Form_pg_inherits inh = (Form_pg_inherits) GETSTRUCT(inheritsTuple);
+
+ if (inh->inhparent == RelationGetRelid(parent_rel))
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_TABLE),
+ errmsg("relation \"%s\" would be inherited from more than once",
+ RelationGetRelationName(parent_rel))));
+
+ if (inh->inhseqno > inhseqno)
+ inhseqno = inh->inhseqno;
+ }
+ systable_endscan(scan);
+
/* Match up the columns and bump attinhcount as needed */
MergeAttributesIntoExisting(child_rel, parent_rel);
@@ -10316,16 +10401,8 @@ ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode)
inhseqno + 1,
catalogRelation);
- ObjectAddressSet(address, RelationRelationId,
- RelationGetRelid(parent_rel));
-
/* Now we're done with pg_inherits */
heap_close(catalogRelation, RowExclusiveLock);
-
- /* keep our lock on the parent relation until commit */
- heap_close(parent_rel, NoLock);
-
- return address;
}
/*
@@ -10376,7 +10453,7 @@ constraints_equivalent(HeapTuple a, HeapTuple b, TupleDesc tupleDesc)
* Check columns in child table match up with columns in parent, and increment
* their attinhcount.
*
- * Called by ATExecAddInherit
+ * Called by CreateInheritance
*
* Currently all parent columns must be found in child. Missing columns are an
* error. One day we might consider creating new columns like CREATE TABLE
@@ -10394,11 +10471,13 @@ MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel)
int parent_natts;
TupleDesc tupleDesc;
HeapTuple tuple;
+ bool is_attach_partition;
attrrel = heap_open(AttributeRelationId, RowExclusiveLock);
tupleDesc = RelationGetDescr(parent_rel);
parent_natts = tupleDesc->natts;
+ is_attach_partition = relid_is_partitioned(RelationGetRelid(parent_rel));
for (parent_attno = 1; parent_attno <= parent_natts; parent_attno++)
{
@@ -10421,14 +10500,16 @@ MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel)
attribute->atttypmod != childatt->atttypmod)
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
- errmsg("child table \"%s\" has different type for column \"%s\"",
+ errmsg("%s table \"%s\" has different type for column \"%s\"",
+ is_attach_partition ? "source" : "child",
RelationGetRelationName(child_rel),
attributeName)));
if (attribute->attcollation != childatt->attcollation)
ereport(ERROR,
(errcode(ERRCODE_COLLATION_MISMATCH),
- errmsg("child table \"%s\" has different collation for column \"%s\"",
+ errmsg("%s table \"%s\" has different collation for column \"%s\"",
+ is_attach_partition ? "source" : "child",
RelationGetRelationName(child_rel),
attributeName)));
@@ -10439,8 +10520,9 @@ MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel)
if (attribute->attnotnull && !childatt->attnotnull)
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
- errmsg("column \"%s\" in child table must be marked NOT NULL",
- attributeName)));
+ errmsg("column \"%s\" in %s table must be marked NOT NULL",
+ attributeName,
+ is_attach_partition ? "source" : "child")));
/*
* OK, bump the child column's inheritance count. (If we fail
@@ -10455,7 +10537,8 @@ MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel)
{
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
- errmsg("child table is missing column \"%s\"",
+ errmsg("%s table is missing column \"%s\"",
+ is_attach_partition ? "source" : "child",
attributeName)));
}
}
@@ -10469,7 +10552,7 @@ MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel)
*
* Constraints that are marked ONLY in the parent are ignored.
*
- * Called by ATExecAddInherit
+ * Called by CreateInheritance
*
* Currently all constraints in parent must be present in the child. One day we
* may consider adding new constraints like CREATE TABLE does.
@@ -10488,9 +10571,11 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
SysScanDesc parent_scan;
ScanKeyData parent_key;
HeapTuple parent_tuple;
+ bool is_attach_partition;
catalog_relation = heap_open(ConstraintRelationId, RowExclusiveLock);
tuple_desc = RelationGetDescr(catalog_relation);
+ is_attach_partition = relid_is_partitioned(RelationGetRelid(parent_rel));
/* Outer loop scans through the parent's constraint definitions */
ScanKeyInit(&parent_key,
@@ -10538,7 +10623,8 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
if (!constraints_equivalent(parent_tuple, child_tuple, tuple_desc))
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
- errmsg("child table \"%s\" has different definition for check constraint \"%s\"",
+ errmsg("%s table \"%s\" has different definition for check constraint \"%s\"",
+ is_attach_partition ? "source" : "child",
RelationGetRelationName(child_rel),
NameStr(parent_con->conname))));
@@ -10546,8 +10632,9 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
if (child_con->connoinherit)
ereport(ERROR,
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
- errmsg("constraint \"%s\" conflicts with non-inherited constraint on child table \"%s\"",
+ errmsg("constraint \"%s\" conflicts with non-inherited constraint on %s table \"%s\"",
NameStr(child_con->conname),
+ is_attach_partition ? "source" : "child",
RelationGetRelationName(child_rel))));
/*
@@ -10570,7 +10657,8 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
if (!found)
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
- errmsg("child table is missing constraint \"%s\"",
+ errmsg("%s table is missing constraint \"%s\"",
+ is_attach_partition ? "source" : "child",
NameStr(parent_con->conname))));
}
@@ -10581,6 +10669,41 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
/*
* ALTER TABLE NO INHERIT
*
+ * Return value is the address of the relation that is no longer parent.
+ */
+static ObjectAddress
+ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
+{
+ ObjectAddress address;
+ Relation parent_rel;
+
+ /*
+ * AccessShareLock on the parent is probably enough, seeing that DROP
+ * TABLE doesn't lock parent tables at all. We need some lock since we'll
+ * be inspecting the parent's schema.
+ */
+ parent_rel = heap_openrv(parent, AccessShareLock);
+
+ /*
+ * We don't bother to check ownership of the parent table --- ownership of
+ * the child is presumed enough rights.
+ */
+
+ /* Off to RemoveInheritance() where most of the work happens */
+ RemoveInheritance(rel, parent_rel);
+
+ /* keep our lock on the parent relation until commit */
+ heap_close(parent_rel, NoLock);
+
+ ObjectAddressSet(address, RelationRelationId,
+ RelationGetRelid(parent_rel));
+
+ return address;
+}
+
+/*
+ * RemoveInheritance
+ *
* Drop a parent from the child's parents. This just adjusts the attinhcount
* and attislocal of the columns and removes the pg_inherit and pg_depend
* entries.
@@ -10594,13 +10717,11 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
* coninhcount and conislocal for inherited constraints are adjusted in
* exactly the same way.
*
- * Return value is the address of the relation that is no longer parent.
+ * Common to ATExecDropInherit() and ATExecDetachPartition().
*/
-static ObjectAddress
-ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
+static void
+RemoveInheritance(Relation child_rel, Relation parent_rel)
{
- Relation parent_rel;
- Oid parent_oid;
Relation catalogRelation;
SysScanDesc scan;
ScanKeyData key[3];
@@ -10609,19 +10730,9 @@ ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
constraintTuple;
List *connames;
bool found = false;
- ObjectAddress address;
+ bool is_detach_partition;
- /*
- * AccessShareLock on the parent is probably enough, seeing that DROP
- * TABLE doesn't lock parent tables at all. We need some lock since we'll
- * be inspecting the parent's schema.
- */
- parent_rel = heap_openrv(parent, AccessShareLock);
-
- /*
- * We don't bother to check ownership of the parent table --- ownership of
- * the child is presumed enough rights.
- */
+ is_detach_partition = relid_is_partitioned(RelationGetRelid(parent_rel));
/*
* Find and destroy the pg_inherits entry linking the two, or error out if
@@ -10631,7 +10742,7 @@ ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
ScanKeyInit(&key[0],
Anum_pg_inherits_inhrelid,
BTEqualStrategyNumber, F_OIDEQ,
- ObjectIdGetDatum(RelationGetRelid(rel)));
+ ObjectIdGetDatum(RelationGetRelid(child_rel)));
scan = systable_beginscan(catalogRelation, InheritsRelidSeqnoIndexId,
true, NULL, 1, key);
@@ -10652,11 +10763,20 @@ ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
heap_close(catalogRelation, RowExclusiveLock);
if (!found)
- ereport(ERROR,
- (errcode(ERRCODE_UNDEFINED_TABLE),
- errmsg("relation \"%s\" is not a parent of relation \"%s\"",
- RelationGetRelationName(parent_rel),
- RelationGetRelationName(rel))));
+ {
+ if (!is_detach_partition)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_TABLE),
+ errmsg("relation \"%s\" is not a parent of relation \"%s\"",
+ RelationGetRelationName(parent_rel),
+ RelationGetRelationName(child_rel))));
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_TABLE),
+ errmsg("relation \"%s\" is not a partition of relation \"%s\"",
+ RelationGetRelationName(child_rel),
+ RelationGetRelationName(parent_rel))));
+ }
/*
* Search through child columns looking for ones matching parent rel
@@ -10665,7 +10785,7 @@ ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
ScanKeyInit(&key[0],
Anum_pg_attribute_attrelid,
BTEqualStrategyNumber, F_OIDEQ,
- ObjectIdGetDatum(RelationGetRelid(rel)));
+ ObjectIdGetDatum(RelationGetRelid(child_rel)));
scan = systable_beginscan(catalogRelation, AttributeRelidNumIndexId,
true, NULL, 1, key);
while (HeapTupleIsValid(attributeTuple = systable_getnext(scan)))
@@ -10727,7 +10847,7 @@ ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
ScanKeyInit(&key[0],
Anum_pg_constraint_conrelid,
BTEqualStrategyNumber, F_OIDEQ,
- ObjectIdGetDatum(RelationGetRelid(rel)));
+ ObjectIdGetDatum(RelationGetRelid(child_rel)));
scan = systable_beginscan(catalogRelation, ConstraintRelidIndexId,
true, NULL, 1, key);
@@ -10758,7 +10878,7 @@ ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
if (copy_con->coninhcount <= 0) /* shouldn't happen */
elog(ERROR, "relation %u has non-inherited constraint \"%s\"",
- RelationGetRelid(rel), NameStr(copy_con->conname));
+ RelationGetRelid(child_rel), NameStr(copy_con->conname));
copy_con->coninhcount--;
if (copy_con->coninhcount == 0)
@@ -10770,30 +10890,22 @@ ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
}
}
- parent_oid = RelationGetRelid(parent_rel);
-
systable_endscan(scan);
heap_close(catalogRelation, RowExclusiveLock);
- drop_parent_dependency(RelationGetRelid(rel),
- RelationRelationId,
- RelationGetRelid(parent_rel));
-
+ /* Remember that dependency exists iff regular inheritance. */
+ if (!is_detach_partition)
+ drop_parent_dependency(RelationGetRelid(child_rel),
+ RelationRelationId,
+ RelationGetRelid(parent_rel));
/*
* Post alter hook of this inherits. Since object_access_hook doesn't take
* multiple object identifiers, we relay oid of parent relation using
* auxiliary_id argument.
*/
InvokeObjectPostAlterHookArg(InheritsRelationId,
- RelationGetRelid(rel), 0,
+ RelationGetRelid(child_rel), 0,
RelationGetRelid(parent_rel), false);
-
- /* keep our lock on the parent relation until commit */
- heap_close(parent_rel, NoLock);
-
- ObjectAddressSet(address, RelationRelationId, parent_oid);
-
- return address;
}
/*
@@ -12426,3 +12538,137 @@ ComputePartitionAttrs(Oid relid, List *partParams, AttrNumber *partattrs,
partopclass[attn++] = opclassOid;
}
}
+
+
+/*
+ * ALTER TABLE <name> ATTACH PARTITION <partition-name> FOR VALUES
+ *
+ * Return the address of the newly attached partition.
+ */
+static ObjectAddress
+ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
+{
+ Relation attachRel,
+ inheritsRelation;
+ SysScanDesc scan;
+ ScanKeyData key;
+ HeapTuple tuple;
+ AttrNumber attno;
+ int natts;
+ TupleDesc tupleDesc;
+ ObjectAddress address;
+
+ attachRel = heap_openrv(cmd->name, AccessShareLock);
+
+ /*
+ * Must be owner of both parent and source table -- parent was checked by
+ * ATSimplePermissions call in ATPrepCmd
+ */
+ ATSimplePermissions(attachRel, ATT_TABLE);
+
+ if (attachRel->rd_rel->reloftype)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot attach a typed table as partition")));
+
+ if (attachRel->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot attach a partitioned table as partition")));
+
+ /* attachRel should not already be a inheritance child of some relation */
+ inheritsRelation = heap_open(InheritsRelationId, AccessShareLock);
+ ScanKeyInit(&key,
+ Anum_pg_inherits_inhrelid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(RelationGetRelid(attachRel)));
+ scan = systable_beginscan(inheritsRelation, InheritsRelidSeqnoIndexId,
+ true, NULL, 1, &key);
+ if (HeapTupleIsValid(systable_getnext(scan)))
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot attach table that inherits as partition")));
+ systable_endscan(scan);
+ heap_close(inheritsRelation, AccessShareLock);
+
+ /* If attachRel is temp, it must belong to this session */
+ if (attachRel->rd_rel->relpersistence == RELPERSISTENCE_TEMP &&
+ !attachRel->rd_islocaltemp)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot attach a temporary relation of another session as partition ")));
+
+ /* If parent has OIDs, attachRel must have OIDs */
+ if (rel->rd_rel->relhasoids && !attachRel->rd_rel->relhasoids)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot attach table \"%s\" without OIDs as partition of"
+ " table \"%s\" with OIDs", RelationGetRelationName(attachRel),
+ RelationGetRelationName(rel))));
+
+ /* OTOH, if parent doesn't have them, do not allow in attachRel either */
+ if (attachRel->rd_rel->relhasoids && !rel->rd_rel->relhasoids)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot attach table \"%s\" with OIDs as partition of table"
+ " \"%s\" without OIDs", RelationGetRelationName(attachRel),
+ RelationGetRelationName(rel))));
+
+ /* Check if there are any columns in attachRel that aren't in the parent */
+ tupleDesc = RelationGetDescr(attachRel);
+ natts = tupleDesc->natts;
+ for (attno = 1; attno <= natts; attno++)
+ {
+ Form_pg_attribute attribute = tupleDesc->attrs[attno - 1];
+ char *attributeName = NameStr(attribute->attname);
+
+ /* Ignore dropped */
+ if (attribute->attisdropped)
+ continue;
+
+ /* Find same column in parent (matching on column name). */
+ tuple = SearchSysCacheCopyAttName(RelationGetRelid(rel), attributeName);
+ if (!HeapTupleIsValid(tuple))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("table \"%s\" contains column \"%s\" not found in parent \"%s\"",
+ RelationGetRelationName(attachRel), attributeName,
+ RelationGetRelationName(rel)),
+ errdetail("Table being attached should contain only the columns"
+ " present in parent.")));
+ }
+
+ /* OK to create inheritance. Rest of the checks performed there */
+ CreateInheritance(attachRel, rel);
+
+ ObjectAddressSet(address, RelationRelationId, RelationGetRelid(attachRel));
+
+ /* keep our lock until commit */
+ heap_close(attachRel, NoLock);
+
+ return address;
+}
+
+/*
+ * ALTER TABLE DETACH PARTITION
+ *
+ * Return the address of the relation that is no longer a partition of rel.
+ */
+static ObjectAddress
+ATExecDetachPartition(Relation rel, RangeVar *name)
+{
+ Relation partRel;
+ ObjectAddress address;
+
+ partRel = heap_openrv(name, AccessShareLock);
+
+ /* All inheritance related checks are performed within the function */
+ RemoveInheritance(partRel, rel);
+
+ ObjectAddressSet(address, RelationRelationId, RelationGetRelid(partRel));
+
+ /* keep our lock until commit */
+ heap_close(partRel, NoLock);
+
+ return address;
+}
diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c
index e9d9ba2..c9c72d9 100644
--- a/src/backend/commands/view.c
+++ b/src/backend/commands/view.c
@@ -95,6 +95,7 @@ DefineVirtualRelation(RangeVar *relation, List *tlist, bool replace,
def->is_local = true;
def->is_not_null = false;
def->is_from_type = false;
+ def->is_for_partition = false;
def->storage = 0;
def->raw_default = NULL;
def->cooked_default = NULL;
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 968a3f4..111fcf5 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2606,6 +2606,7 @@ _copyColumnDef(const ColumnDef *from)
COPY_SCALAR_FIELD(is_local);
COPY_SCALAR_FIELD(is_not_null);
COPY_SCALAR_FIELD(is_from_type);
+ COPY_SCALAR_FIELD(is_for_partition);
COPY_SCALAR_FIELD(storage);
COPY_NODE_FIELD(raw_default);
COPY_NODE_FIELD(cooked_default);
@@ -3001,6 +3002,7 @@ CopyCreateStmtFields(const CreateStmt *from, CreateStmt *newnode)
COPY_NODE_FIELD(relation);
COPY_NODE_FIELD(tableElts);
COPY_NODE_FIELD(inhRelations);
+ COPY_NODE_FIELD(partbound);
COPY_NODE_FIELD(partby);
COPY_NODE_FIELD(ofTypename);
COPY_NODE_FIELD(constraints);
@@ -4183,6 +4185,43 @@ _copyPartitionElem(const PartitionElem *from)
return newnode;
}
+static PartitionListSpec *
+_copyPartitionListSpec(const PartitionListSpec *from)
+{
+ PartitionListSpec *newnode = makeNode(PartitionListSpec);
+
+ COPY_NODE_FIELD(values);
+ COPY_LOCATION_FIELD(location);
+
+ return newnode;
+}
+
+static PartitionRangeSpec *
+_copyPartitionRangeSpec(const PartitionRangeSpec *from)
+{
+ PartitionRangeSpec *newnode = makeNode(PartitionRangeSpec);
+
+ COPY_SCALAR_FIELD(lowerinc);
+ COPY_NODE_FIELD(lower);
+ COPY_SCALAR_FIELD(upperinc);
+ COPY_NODE_FIELD(upper);
+ COPY_LOCATION_FIELD(location);
+
+ return newnode;
+}
+
+static PartitionCmd *
+_copyPartitionCmd(const PartitionCmd *from)
+{
+ PartitionCmd *newnode = makeNode(PartitionCmd);
+
+ COPY_NODE_FIELD(name);
+ COPY_NODE_FIELD(bound);
+ COPY_SCALAR_FIELD(skip_validate);
+
+ return newnode;
+}
+
/* ****************************************************************
* pg_list.h copy functions
* ****************************************************************
@@ -5082,6 +5121,15 @@ copyObject(const void *from)
case T_PartitionElem:
retval = _copyPartitionElem(from);
break;
+ case T_PartitionListSpec:
+ retval = _copyPartitionListSpec(from);
+ break;
+ case T_PartitionRangeSpec:
+ retval = _copyPartitionRangeSpec(from);
+ break;
+ case T_PartitionCmd:
+ retval = _copyPartitionCmd(from);
+ break;
default:
elog(ERROR, "unrecognized node type: %d", (int) nodeTag(from));
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 56325fe..1a25acf 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1156,6 +1156,7 @@ _equalCreateStmt(const CreateStmt *a, const CreateStmt *b)
COMPARE_NODE_FIELD(relation);
COMPARE_NODE_FIELD(tableElts);
COMPARE_NODE_FIELD(inhRelations);
+ COMPARE_NODE_FIELD(partbound);
COMPARE_NODE_FIELD(partby);
COMPARE_NODE_FIELD(ofTypename);
COMPARE_NODE_FIELD(constraints);
@@ -2363,6 +2364,7 @@ _equalColumnDef(const ColumnDef *a, const ColumnDef *b)
COMPARE_SCALAR_FIELD(is_local);
COMPARE_SCALAR_FIELD(is_not_null);
COMPARE_SCALAR_FIELD(is_from_type);
+ COMPARE_SCALAR_FIELD(is_for_partition);
COMPARE_SCALAR_FIELD(storage);
COMPARE_NODE_FIELD(raw_default);
COMPARE_NODE_FIELD(cooked_default);
@@ -2641,6 +2643,37 @@ _equalPartitionElem(const PartitionElem *a, const PartitionElem *b)
return true;
}
+static bool
+_equalPartitionListSpec(const PartitionListSpec *a, const PartitionListSpec *b)
+{
+ COMPARE_NODE_FIELD(values);
+ COMPARE_LOCATION_FIELD(location);
+
+ return true;
+}
+
+static bool
+_equalPartitionRangeSpec(const PartitionRangeSpec *a, const PartitionRangeSpec *b)
+{
+ COMPARE_SCALAR_FIELD(lowerinc);
+ COMPARE_NODE_FIELD(lower);
+ COMPARE_SCALAR_FIELD(upperinc);
+ COMPARE_NODE_FIELD(upper);
+ COMPARE_LOCATION_FIELD(location);
+
+ return true;
+}
+
+static bool
+_equalPartitionCmd(const PartitionCmd *a, const PartitionCmd *b)
+{
+ COMPARE_NODE_FIELD(name);
+ COMPARE_NODE_FIELD(bound);
+ COMPARE_SCALAR_FIELD(skip_validate);
+
+ return true;
+}
+
/*
* Stuff from pg_list.h
*/
@@ -3397,6 +3430,15 @@ equal(const void *a, const void *b)
case T_PartitionElem:
retval = _equalPartitionElem(a, b);
break;
+ case T_PartitionListSpec:
+ retval = _equalPartitionListSpec(a, b);
+ break;
+ case T_PartitionRangeSpec:
+ retval = _equalPartitionRangeSpec(a, b);
+ break;
+ case T_PartitionCmd:
+ retval = _equalPartitionCmd(a, b);
+ break;
default:
elog(ERROR, "unrecognized node type: %d",
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 5623471..7537473 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2369,6 +2369,7 @@ _outCreateStmtInfo(StringInfo str, const CreateStmt *node)
WRITE_NODE_FIELD(relation);
WRITE_NODE_FIELD(tableElts);
WRITE_NODE_FIELD(inhRelations);
+ WRITE_NODE_FIELD(partbound);
WRITE_NODE_FIELD(partby);
WRITE_NODE_FIELD(ofTypename);
WRITE_NODE_FIELD(constraints);
@@ -2549,6 +2550,7 @@ _outColumnDef(StringInfo str, const ColumnDef *node)
WRITE_BOOL_FIELD(is_local);
WRITE_BOOL_FIELD(is_not_null);
WRITE_BOOL_FIELD(is_from_type);
+ WRITE_BOOL_FIELD(is_for_partition);
WRITE_CHAR_FIELD(storage);
WRITE_NODE_FIELD(raw_default);
WRITE_NODE_FIELD(cooked_default);
@@ -3239,6 +3241,25 @@ _outPartitionElem(StringInfo str, const PartitionElem *node)
WRITE_LOCATION_FIELD(location);
}
+static void
+_outPartitionListSpec(StringInfo str, const PartitionListSpec *node)
+{
+ WRITE_NODE_TYPE("PARTITIONLISTVALUES");
+
+ WRITE_NODE_FIELD(values);
+}
+
+static void
+_outPartitionRangeSpec(StringInfo str, const PartitionRangeSpec *node)
+{
+ WRITE_NODE_TYPE("PARTITIONRANGE");
+
+ WRITE_BOOL_FIELD(lowerinc);
+ WRITE_NODE_FIELD(lower);
+ WRITE_BOOL_FIELD(upperinc);
+ WRITE_NODE_FIELD(upper);
+}
+
/*
* outNode -
* converts a Node into ascii string and append it to 'str'
@@ -3823,6 +3844,12 @@ outNode(StringInfo str, const void *obj)
case T_PartitionElem:
_outPartitionElem(str, obj);
break;
+ case T_PartitionListSpec:
+ _outPartitionListSpec(str, obj);
+ break;
+ case T_PartitionRangeSpec:
+ _outPartitionRangeSpec(str, obj);
+ break;
default:
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 0607439..79fbb32 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -229,6 +229,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
VariableSetStmt *vsetstmt;
PartitionElem *partelem;
PartitionBy *partby;
+ PartitionRangeSpec *partrange;
}
%type <node> stmt schema_stmt
@@ -544,6 +545,14 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <partby> PartitionBy OptPartitionBy
%type <partelem> part_elem
%type <list> part_params
+%type <list> OptPartitionElementList PartitionElementList
+%type <node> PartitionElement
+%type <node> ForValues
+%type <node> partvalue
+%type <list> partvalue_list
+%type <boolean> lb_inc ub_inc
+%type <list> RangeBound
+%type <boolean> opt_validate_spec
/*
* Non-keyword token types. These are hard-wired into the "flex" lexer.
@@ -569,7 +578,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
/* ordinary key words in alphabetical order */
%token <keyword> ABORT_P ABSOLUTE_P ACCESS ACTION ADD_P ADMIN AFTER
AGGREGATE ALL ALSO ALTER ALWAYS ANALYSE ANALYZE AND ANY ARRAY AS ASC
- ASSERTION ASSIGNMENT ASYMMETRIC AT ATTRIBUTE AUTHORIZATION
+ ASSERTION ASSIGNMENT ASYMMETRIC AT ATTACH ATTRIBUTE AUTHORIZATION
BACKWARD BEFORE BEGIN_P BETWEEN BIGINT BINARY BIT
BOOLEAN_P BOTH BY
@@ -585,7 +594,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
DATA_P DATABASE DAY_P DEALLOCATE DEC DECIMAL_P DECLARE DEFAULT DEFAULTS
DEFERRABLE DEFERRED DEFINER DELETE_P DELIMITER DELIMITERS DEPENDS DESC
- DICTIONARY DISABLE_P DISCARD DISTINCT DO DOCUMENT_P DOMAIN_P DOUBLE_P DROP
+ DETACH DICTIONARY DISABLE_P DISCARD DISTINCT DO DOCUMENT_P DOMAIN_P
+ DOUBLE_P DROP
EACH ELSE ENABLE_P ENCODING ENCRYPTED END_P ENUM_P ESCAPE EVENT EXCEPT
EXCLUDE EXCLUDING EXCLUSIVE EXECUTE EXISTS EXPLAIN
@@ -599,7 +609,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
HANDLER HAVING HEADER_P HOLD HOUR_P
IDENTITY_P IF_P ILIKE IMMEDIATE IMMUTABLE IMPLICIT_P IMPORT_P IN_P
- INCLUDING INCREMENT INDEX INDEXES INHERIT INHERITS INITIALLY INLINE_P
+ INCLUDING INCLUSIVE INCREMENT INDEX INDEXES INHERIT INHERITS INITIALLY INLINE_P
INNER_P INOUT INPUT_P INSENSITIVE INSERT INSTEAD INT_P INTEGER
INTERSECT INTERVAL INTO INVOKER IS ISNULL ISOLATION
@@ -2373,6 +2383,38 @@ alter_table_cmd:
n->def = (Node *)$1;
$$ = (Node *) n;
}
+ /* ALTER TABLE <name> ATTACH PARTITION <table_name> FOR VALUES */
+ | ATTACH PARTITION qualified_name ForValues opt_validate_spec
+ {
+ AlterTableCmd *n = makeNode(AlterTableCmd);
+ PartitionCmd *cmd = makeNode(PartitionCmd);
+
+ n->subtype = AT_AttachPartition;
+ cmd->name = $3;
+ cmd->bound = (Node *) $4;
+ cmd->skip_validate = $5;
+ n->def = (Node *) cmd;
+
+ $$ = (Node *) n;
+ }
+ /* ALTER TABLE <name> DETACH PARTITION <partition_name> */
+ | DETACH PARTITION qualified_name
+ {
+ AlterTableCmd *n = makeNode(AlterTableCmd);
+ PartitionCmd *cmd = makeNode(PartitionCmd);
+
+ n->subtype = AT_DetachPartition;
+ cmd->name = $3;
+ n->def = (Node *) cmd;
+
+ $$ = (Node *) n;
+ }
+ ;
+
+opt_validate_spec:
+ NO VALIDATE { $$ = true; }
+ | VALIDATE { $$ = false; }
+ | /* EMPTY */ { $$ = false; }
;
alter_column_default:
@@ -2468,6 +2510,60 @@ reloption_elem:
}
;
+ForValues:
+ /* a LIST partition */
+ FOR VALUES IN_P '(' partvalue_list ')'
+ {
+ PartitionListSpec *n = makeNode(PartitionListSpec);
+
+ n->values = $5;
+ n->location = @1;
+
+ $$ = (Node *) n;
+ }
+
+ /* a RANGE partition */
+ | FOR VALUES START RangeBound lb_inc END_P RangeBound ub_inc
+ {
+ PartitionRangeSpec *n = makeNode(PartitionRangeSpec);
+
+ n->lowerinc = $5;
+ n->lower = $4;
+ n->upperinc = $8;
+ n->upper = $7;
+ n->location = @1;
+
+ $$ = (Node *) n;
+ }
+ ;
+
+RangeBound:
+ UNBOUNDED { $$ = NIL; }
+ | '(' partvalue_list ')' { $$ = $2; }
+ ;
+
+lb_inc:
+ EXCLUSIVE { $$ = false;}
+ | INCLUSIVE { $$ = true; }
+ | /* EMPTY */ { $$ = true; }
+ ;
+
+ub_inc:
+ INCLUSIVE { $$ = true; }
+ | EXCLUSIVE { $$ = false; }
+ | /* EMPTY */ { $$ = false; }
+ ;
+
+partvalue:
+ Sconst { $$ = makeStringConst($1, @1); }
+ | NumericOnly { $$ = makeAConst($1, @1); }
+ | NULL_P { $$ = makeNullAConst(@1); }
+ ;
+
+partvalue_list:
+ partvalue { $$ = list_make1($1); }
+ | partvalue_list ',' partvalue { $$ = lappend($1, $3); }
+ ;
/*****************************************************************************
*
@@ -2885,6 +2981,44 @@ CreateStmt: CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
n->if_not_exists = true;
$$ = (Node *)n;
}
+ | CREATE OptTemp TABLE qualified_name PARTITION OF qualified_name
+ OptPartitionElementList ForValues OptPartitionBy OptWith
+ OnCommitOption OptTableSpace
+ {
+ CreateStmt *n = makeNode(CreateStmt);
+ $4->relpersistence = $2;
+ n->relation = $4;
+ n->tableElts = $8;
+ n->inhRelations = list_make1($7);
+ n->partbound = (Node *) $9;
+ n->partby = $10;
+ n->ofTypename = NULL;
+ n->constraints = NIL;
+ n->options = $11;
+ n->oncommit = $12;
+ n->tablespacename = $13;
+ n->if_not_exists = false;
+ $$ = (Node *)n;
+ }
+ | CREATE OptTemp TABLE IF_P NOT EXISTS qualified_name PARTITION OF
+ qualified_name OptPartitionElementList ForValues OptPartitionBy
+ OptWith OnCommitOption OptTableSpace
+ {
+ CreateStmt *n = makeNode(CreateStmt);
+ $7->relpersistence = $2;
+ n->relation = $7;
+ n->tableElts = $11;
+ n->inhRelations = list_make1($10);
+ n->partbound = (Node *) $12;
+ n->partby = $13;
+ n->ofTypename = NULL;
+ n->constraints = NIL;
+ n->options = $14;
+ n->oncommit = $15;
+ n->tablespacename = $16;
+ n->if_not_exists = true;
+ $$ = (Node *)n;
+ }
;
/*
@@ -2930,6 +3064,11 @@ OptTypedTableElementList:
| /*EMPTY*/ { $$ = NIL; }
;
+OptPartitionElementList:
+ '(' PartitionElementList ')' { $$ = $2; }
+ | /*EMPTY*/ { $$ = NIL; }
+ ;
+
TableElementList:
TableElement
{
@@ -2952,6 +3091,17 @@ TypedTableElementList:
}
;
+PartitionElementList:
+ PartitionElement
+ {
+ $$ = list_make1($1);
+ }
+ | PartitionElementList ',' PartitionElement
+ {
+ $$ = lappend($1, $3);
+ }
+ ;
+
TableElement:
columnDef { $$ = $1; }
| TableLikeClause { $$ = $1; }
@@ -2963,6 +3113,11 @@ TypedTableElement:
| TableConstraint { $$ = $1; }
;
+PartitionElement:
+ columnOptions { $$ = $1; }
+ | TableConstraint { $$ = $1; }
+ ;
+
columnDef: ColId Typename create_generic_options ColQualList
{
ColumnDef *n = makeNode(ColumnDef);
@@ -2972,6 +3127,7 @@ columnDef: ColId Typename create_generic_options ColQualList
n->is_local = true;
n->is_not_null = false;
n->is_from_type = false;
+ n->is_for_partition = false;
n->storage = 0;
n->raw_default = NULL;
n->cooked_default = NULL;
@@ -2993,6 +3149,7 @@ columnOptions: ColId WITH OPTIONS ColQualList
n->is_local = true;
n->is_not_null = false;
n->is_from_type = false;
+ n->is_for_partition = false;
n->storage = 0;
n->raw_default = NULL;
n->cooked_default = NULL;
@@ -11122,6 +11279,7 @@ TableFuncElement: ColId Typename opt_collate_clause
n->is_local = true;
n->is_not_null = false;
n->is_from_type = false;
+ n->is_for_partition = false;
n->storage = 0;
n->raw_default = NULL;
n->cooked_default = NULL;
@@ -13805,6 +13963,7 @@ unreserved_keyword:
| ASSERTION
| ASSIGNMENT
| AT
+ | ATTACH
| ATTRIBUTE
| BACKWARD
| BEFORE
@@ -13851,6 +14010,7 @@ unreserved_keyword:
| DELIMITER
| DELIMITERS
| DEPENDS
+ | DETACH
| DICTIONARY
| DISABLE_P
| DISCARD
@@ -13893,6 +14053,7 @@ unreserved_keyword:
| IMPLICIT_P
| IMPORT_P
| INCLUDING
+ | INCLUSIVE
| INCREMENT
| INDEX
| INDEXES
diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c
index 77ffd84..d71fdfe 100644
--- a/src/backend/parser/parse_agg.c
+++ b/src/backend/parser/parse_agg.c
@@ -493,7 +493,9 @@ check_agglevels_and_constraints(ParseState *pstate, Node *expr)
err = _("grouping operations are not allowed in partition key expression");
break;
-
+ case EXPR_KIND_PARTITION_FOR_VALUES:
+ errkind = true;
+ break;
/*
* There is intentionally no default: case here, so that the
@@ -854,6 +856,9 @@ transformWindowFuncCall(ParseState *pstate, WindowFunc *wfunc,
case EXPR_KIND_PARTITION_KEY:
err = _("window functions are not allowed in partition key expression");
break;
+ case EXPR_KIND_PARTITION_FOR_VALUES:
+ errkind = true;
+ break;
/*
* There is intentionally no default: case here, so that the
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 147c6da..f14d776 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -352,6 +352,26 @@ transformExprRecurse(ParseState *pstate, Node *expr)
result = (Node *) expr;
break;
}
+ case T_PartitionListSpec:
+ {
+ PartitionListSpec *list = (PartitionListSpec *) expr;
+
+ list->values = transformExpressionList(pstate, list->values,
+ pstate->p_expr_kind);
+ result = expr;
+ break;
+ }
+ case T_PartitionRangeSpec:
+ {
+ PartitionRangeSpec *range = (PartitionRangeSpec *) expr;
+
+ range->lower = transformExpressionList(pstate, range->lower,
+ pstate->p_expr_kind);
+ range->upper = transformExpressionList(pstate, range->upper,
+ pstate->p_expr_kind);
+ result = expr;
+ break;
+ }
default:
/* should not reach here */
@@ -1698,6 +1718,7 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
case EXPR_KIND_OFFSET:
case EXPR_KIND_RETURNING:
case EXPR_KIND_VALUES:
+ case EXPR_KIND_PARTITION_FOR_VALUES:
/* okay */
break;
case EXPR_KIND_CHECK_CONSTRAINT:
@@ -3254,6 +3275,8 @@ ParseExprKindName(ParseExprKind exprKind)
return "WHEN";
case EXPR_KIND_PARTITION_KEY:
return "partition key expression";
+ case EXPR_KIND_PARTITION_FOR_VALUES:
+ return "FOR VALUES";
/*
* There is intentionally no default: case here, so that the
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 465597d..c0a1bb0 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -48,8 +48,10 @@
#include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "nodes/nodeFuncs.h"
+#include "optimizer/planner.h"
#include "parser/analyze.h"
#include "parser/parse_clause.h"
+#include "parser/parse_coerce.h"
#include "parser/parse_collate.h"
#include "parser/parse_expr.h"
#include "parser/parse_relation.h"
@@ -89,6 +91,7 @@ typedef struct
* the table */
IndexStmt *pkey; /* PRIMARY KEY index, if any */
bool ispartitioned; /* true if table is partitioned */
+ Node *partbound; /* transformed FOR VALUES */
} CreateStmtContext;
/* State shared by transformCreateSchemaStmt and its subroutines */
@@ -131,6 +134,10 @@ static void transformConstraintAttrs(CreateStmtContext *cxt,
List *constraintList);
static void transformColumnType(CreateStmtContext *cxt, ColumnDef *column);
static void setSchemaName(char *context_schema, char **stmt_schema_name);
+static void transformPartitionOf(CreateStmtContext *cxt, Node *bound);
+static void transformAttachPartition(CreateStmtContext *cxt, PartitionCmd *cmd);
+static Node *transformPartitionBound(CreateStmtContext *cxt, Relation parent,
+ Node *bound);
/*
@@ -232,6 +239,7 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
cxt.alist = NIL;
cxt.pkey = NULL;
cxt.ispartitioned = stmt->partby != NULL;
+ cxt.partbound = NULL;
/*
* Notice that we allow OIDs here only for plain tables, even though
@@ -250,6 +258,9 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
if (stmt->ofTypename)
transformOfType(&cxt, stmt->ofTypename);
+ if (stmt->partbound)
+ transformPartitionOf(&cxt, stmt->partbound);
+
if (stmt->partby)
{
int partnatts = list_length(stmt->partby->partParams);
@@ -355,6 +366,7 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
*/
stmt->tableElts = cxt.columns;
stmt->constraints = cxt.ckconstraints;
+ stmt->partbound = cxt.partbound;
result = lappend(cxt.blist, stmt);
result = list_concat(result, cxt.alist);
@@ -894,6 +906,7 @@ transformTableLikeClause(CreateStmtContext *cxt, TableLikeClause *table_like_cla
def->is_local = true;
def->is_not_null = attribute->attnotnull;
def->is_from_type = false;
+ def->is_for_partition = false;
def->storage = 0;
def->raw_default = NULL;
def->cooked_default = NULL;
@@ -1113,6 +1126,7 @@ transformOfType(CreateStmtContext *cxt, TypeName *ofTypename)
n->is_local = true;
n->is_not_null = false;
n->is_from_type = true;
+ n->is_for_partition = false;
n->storage = 0;
n->raw_default = NULL;
n->cooked_default = NULL;
@@ -2580,6 +2594,7 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
cxt.alist = NIL;
cxt.pkey = NULL;
cxt.ispartitioned = relid_is_partitioned(relid);
+ cxt.partbound = NULL;
/*
* The only subtypes that currently require parse transformation handling
@@ -2668,6 +2683,22 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
break;
}
+ case AT_AttachPartition:
+ {
+ PartitionCmd *partcmd = (PartitionCmd *) cmd->def;
+
+ transformAttachPartition(&cxt, partcmd);
+
+ /* assign transformed values */
+ partcmd->bound = cxt.partbound;
+ }
+
+ newcmds = lappend(newcmds, cmd);
+ break;
+ case AT_DetachPartition:
+ newcmds = lappend(newcmds, cmd);
+ break;
+
default:
newcmds = lappend(newcmds, cmd);
break;
@@ -3032,3 +3063,290 @@ setSchemaName(char *context_schema, char **stmt_schema_name)
"different from the one being created (%s)",
*stmt_schema_name, context_schema)));
}
+
+/*
+ * transformPartitionOf
+ * Analyze PARTITION OF ... FOR VALUES ...
+ */
+static void
+transformPartitionOf(CreateStmtContext *cxt, Node *bound)
+{
+ TupleDesc tupdesc;
+ int i;
+ Relation parentRel;
+ RangeVar *partof = linitial(cxt->inhRelations);
+
+ parentRel = heap_openrv(partof, AccessShareLock);
+
+ /* Check if the target table is partitioned at all */
+ if (parentRel->rd_rel->relkind != RELKIND_PARTITIONED_REL)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("\"%s\" is not partitioned",
+ RelationGetRelationName(parentRel))));
+ Assert(parentRel->rd_partkey != NULL);
+
+ /* If parent rel is temp, it must belong to this session */
+ if (parentRel->rd_rel->relpersistence == RELPERSISTENCE_TEMP &&
+ !parentRel->rd_islocaltemp)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot create as partition of temporary relation of another session")));
+
+ /*
+ * Do not allow OIDs in a partition, if not present in the parent. But
+ * force them in partition, if they are present in the parent.
+ */
+ if (cxt->hasoids && !parentRel->rd_rel->relhasoids)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot create table with OIDs as partition of table without OIDs")));
+
+ if (parentRel->rd_rel->relhasoids)
+ cxt->hasoids = true;
+
+ tupdesc = RelationGetDescr(parentRel);
+ for (i = 0; i < tupdesc->natts; i++)
+ {
+ Form_pg_attribute attribute = tupdesc->attrs[i];
+ ColumnDef *def;
+
+ if (attribute->attisdropped)
+ continue;
+
+ def = makeNode(ColumnDef);
+ def->colname = pstrdup(NameStr(attribute->attname));
+ def->typeName = makeTypeNameFromOid(attribute->atttypid,
+ attribute->atttypmod);
+ def->inhcount = 1;
+ def->is_local = false;
+ def->is_not_null = attribute->attnotnull;
+ def->is_from_type = false;
+ def->is_for_partition = true;
+ def->storage = attribute->attstorage;
+ def->raw_default = NULL;
+ def->cooked_default = NULL;
+ def->collClause = NULL;
+ def->collOid = attribute->attcollation;
+ def->constraints = NIL;
+ def->location = -1;
+
+ cxt->columns = lappend(cxt->columns, def);
+ }
+
+ /* tranform the values */
+ cxt->partbound = transformPartitionBound(cxt, parentRel, bound);
+
+ heap_close(parentRel, AccessShareLock);
+}
+
+/*
+ * transformAttachPartition
+ * Analyze ATTACH PARTITION ... FOR VALUES ...
+ */
+static void
+transformAttachPartition(CreateStmtContext *cxt, PartitionCmd *cmd)
+{
+ Relation parentRel = cxt->rel;
+
+ /* Check if the target table is partitioned at all */
+ if (parentRel->rd_rel->relkind != RELKIND_PARTITIONED_REL)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("\"%s\" is not partitioned", cxt->relation->relname)));
+ Assert(parentRel->rd_partkey != NULL);
+
+ /* tranform the values */
+ cxt->partbound = transformPartitionBound(cxt, parentRel, cmd->bound);
+}
+
+/*
+ * transformPartitionBound
+ *
+ * Transform partition bound per the partition key
+ */
+static Node *
+transformPartitionBound(CreateStmtContext *cxt, Relation parent, Node *bound)
+{
+ int i;
+ ListCell *cell;
+ Oid parentId = RelationGetRelid(parent);
+ PartitionKey key = parent->rd_partkey;
+ PartitionListSpec *list, *result_list;
+ PartitionRangeSpec *range, *result_range;
+
+ switch (key->strategy)
+ {
+ case PARTITION_STRAT_LIST:
+ if (!IsA(bound, PartitionListSpec))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("invalid bound specification for a list partition"),
+ parser_errposition(cxt->pstate, exprLocation(bound))));
+
+ list = (PartitionListSpec *) transformExpr(cxt->pstate, bound,
+ EXPR_KIND_PARTITION_FOR_VALUES);
+
+ result_list = makeNode(PartitionListSpec);
+
+ foreach(cell, list->values)
+ {
+ Node *value = (Node *) lfirst(cell);
+ Node *orig_value = value;
+ Oid valuetype;
+
+ valuetype = exprType(value);
+ value = coerce_to_target_type(cxt->pstate,
+ value, valuetype,
+ key->tcinfo->typid[0],
+ key->tcinfo->typmod[0],
+ COERCION_ASSIGNMENT,
+ COERCE_IMPLICIT_CAST,
+ -1);
+ if (value == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("specified value cannot be cast to type \"%s\""
+ " of key column \"%s\"",
+ format_type_be(key->tcinfo->typid[0]),
+ get_relid_attribute_name(parentId, key->partattrs[0])),
+ parser_errposition(cxt->pstate, exprLocation(orig_value))));
+
+ /* Simplify the expression */
+ value = (Node *) expression_planner((Expr *) value);
+
+ result_list->values = lappend(result_list->values, value);
+ }
+ return (Node *) result_list;
+
+ case PARTITION_STRAT_RANGE:
+ if (!IsA(bound, PartitionRangeSpec))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("invalid bound specification for a range partition"),
+ parser_errposition(cxt->pstate, exprLocation(bound))));
+
+ range = (PartitionRangeSpec *) transformExpr(cxt->pstate, bound,
+ EXPR_KIND_PARTITION_FOR_VALUES);
+
+ if (!range->lower && !range->upper)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("both START and END cannot be UNBOUNDED"),
+ parser_errposition(cxt->pstate, range->location)));
+
+ result_range = makeNode(PartitionRangeSpec);
+ result_range->lowerinc = range->lowerinc;
+ result_range->upperinc = range->upperinc;
+
+ if (range->lower)
+ {
+ if (list_length(range->lower) > key->partnatts)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("START has more values specified than number"
+ " of columns in the partition key"),
+ parser_errposition(cxt->pstate,
+ exprLocation(list_nth(range->lower,
+ list_length(range->lower) - 1)))));
+ else if (list_length(range->lower) < key->partnatts)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("START has fewer values specified than number"
+ " of columns in the partition key"),
+ parser_errposition(cxt->pstate,
+ exprLocation(list_nth(range->lower,
+ list_length(range->lower) - 1)))));
+
+ i = 0;
+ foreach (cell, range->lower)
+ {
+ Node *value = (Node *) lfirst(cell);
+ Node *orig_value = value;
+ Oid valuetype;
+
+ valuetype = exprType(value);
+ value = coerce_to_target_type(cxt->pstate,
+ value, valuetype,
+ key->tcinfo->typid[i],
+ key->tcinfo->typmod[i],
+ COERCION_ASSIGNMENT,
+ COERCE_IMPLICIT_CAST,
+ -1);
+ if (value == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("specified value cannot be cast to type"
+ " \"%s\" of key column \"%s\"",
+ format_type_be(key->tcinfo->typid[i]),
+ get_relid_attribute_name(parentId, key->partattrs[i])),
+ parser_errposition(cxt->pstate, exprLocation(orig_value))));
+
+ /* Simplify the expression */
+ value = (Node *) expression_planner((Expr *) value);
+
+ result_range->lower = lappend(result_range->lower, value);
+ ++i;
+ }
+ }
+ else
+ result_range->lowerinc = false;
+
+ if (range->upper)
+ {
+ if (list_length(range->upper) > key->partnatts)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("END has more values specified than number"
+ " of columns in the partition key"),
+ parser_errposition(cxt->pstate,
+ exprLocation(list_nth(range->upper,
+ list_length(range->upper) - 1)))));
+ else if (list_length(range->upper) < key->partnatts)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("END has fewer values specified than number"
+ " of columns in the partition key"),
+ parser_errposition(cxt->pstate,
+ exprLocation(list_nth(range->upper,
+ list_length(range->upper) - 1)))));
+
+ i = 0;
+ foreach (cell, range->upper)
+ {
+ Node *value = (Node *) lfirst(cell);
+ Node *orig_value = value;
+ Oid valuetype;
+
+ valuetype = exprType(value);
+ value = coerce_to_target_type(cxt->pstate,
+ value, valuetype,
+ key->tcinfo->typid[i],
+ key->tcinfo->typmod[i],
+ COERCION_ASSIGNMENT,
+ COERCE_IMPLICIT_CAST,
+ -1);
+ if (value == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("specified value cannot be cast to type"
+ " \"%s\" of key column \"%s\"",
+ format_type_be(key->tcinfo->typid[i]),
+ get_relid_attribute_name(parentId, key->partattrs[i])),
+ parser_errposition(cxt->pstate, exprLocation(orig_value))));
+
+ /* Simplify the expression */
+ value = (Node *) expression_planner((Expr *) value);
+
+ result_range->upper = lappend(result_range->upper, value);
+ ++i;
+ }
+ }
+ else
+ result_range->upperinc = false;
+
+ return (Node *) result_range;
+ }
+
+ return NULL;
+}
diff --git a/src/include/catalog/partition.h b/src/include/catalog/partition.h
index 2c876c4..31dbe17 100644
--- a/src/include/catalog/partition.h
+++ b/src/include/catalog/partition.h
@@ -14,6 +14,7 @@
#define PARTITION_H
#include "fmgr.h"
+#include "parser/parse_node.h"
#include "utils/relcache.h"
/* Type and collation information for partition key columns */
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 9e582a5..56bbe43 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -405,6 +405,7 @@ typedef enum NodeTag
T_AlterPolicyStmt,
T_CreateTransformStmt,
T_CreateAmStmt,
+ T_PartitionCmd,
/*
* TAGS FOR PARSE TREE NODES (parsenodes.h)
@@ -454,6 +455,8 @@ typedef enum NodeTag
T_RoleSpec,
T_PartitionElem,
T_PartitionBy,
+ T_PartitionListSpec,
+ T_PartitionRangeSpec,
/*
* TAGS FOR REPLICATION GRAMMAR PARSE NODES (replnodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 4f697b8..d9dff96 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -591,6 +591,7 @@ typedef struct ColumnDef
bool is_local; /* column has local (non-inherited) def'n */
bool is_not_null; /* NOT NULL constraint specified? */
bool is_from_type; /* column definition came from table type */
+ bool is_for_partition; /* column definition is for a partition */
char storage; /* attstorage setting, or 0 for default */
Node *raw_default; /* default value (untransformed parse tree) */
Node *cooked_default; /* default value (transformed expr tree) */
@@ -731,6 +732,40 @@ typedef struct PartitionBy
int location; /* token location, or -1 if unknown */
} PartitionBy;
+/*
+ * PartitionRangeSpec - range type syntax for range partition bound
+ */
+typedef struct PartitionListSpec
+{
+ NodeTag type;
+ List *values;
+ int location;
+} PartitionListSpec;
+
+/*
+ * PartitionRangeSpec - range type syntax for range partition bound
+ */
+typedef struct PartitionRangeSpec
+{
+ NodeTag type;
+ bool lowerinc;
+ List *lower;
+ bool upperinc;
+ List *upper;
+ int location; /* token location, or -1 if unknown */
+} PartitionRangeSpec;
+
+/*
+ * PartitionCmd - ALTER TABLE partition commands
+ */
+typedef struct PartitionCmd
+{
+ NodeTag type;
+ RangeVar *name;
+ Node *bound;
+ bool skip_validate;
+} PartitionCmd;
+
/****************************************************************************
* Nodes for a Query tree
****************************************************************************/
@@ -1558,7 +1593,9 @@ typedef enum AlterTableType
AT_DisableRowSecurity, /* DISABLE ROW SECURITY */
AT_ForceRowSecurity, /* FORCE ROW SECURITY */
AT_NoForceRowSecurity, /* NO FORCE ROW SECURITY */
- AT_GenericOptions /* OPTIONS (...) */
+ AT_GenericOptions, /* OPTIONS (...) */
+ AT_AttachPartition, /* ATTACH PARTITION */
+ AT_DetachPartition /* DETACH PARTITION */
} AlterTableType;
typedef struct ReplicaIdentityStmt
@@ -1783,7 +1820,9 @@ typedef struct CreateStmt
RangeVar *relation; /* relation to create */
List *tableElts; /* column definitions (list of ColumnDef) */
List *inhRelations; /* relations to inherit from (list of
- * inhRelation) */
+ * inhRelation); (ab)used also as partition
+ * parent */
+ Node *partbound; /* FOR VALUES clause */
PartitionBy *partby; /* PARTITION BY clause */
TypeName *ofTypename; /* OF typename */
List *constraints; /* constraints (list of Constraint nodes) */
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 40da67a..70c264c 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -49,6 +49,7 @@ PG_KEYWORD("assertion", ASSERTION, UNRESERVED_KEYWORD)
PG_KEYWORD("assignment", ASSIGNMENT, UNRESERVED_KEYWORD)
PG_KEYWORD("asymmetric", ASYMMETRIC, RESERVED_KEYWORD)
PG_KEYWORD("at", AT, UNRESERVED_KEYWORD)
+PG_KEYWORD("attach", ATTACH, UNRESERVED_KEYWORD)
PG_KEYWORD("attribute", ATTRIBUTE, UNRESERVED_KEYWORD)
PG_KEYWORD("authorization", AUTHORIZATION, TYPE_FUNC_NAME_KEYWORD)
PG_KEYWORD("backward", BACKWARD, UNRESERVED_KEYWORD)
@@ -127,6 +128,7 @@ PG_KEYWORD("delimiter", DELIMITER, UNRESERVED_KEYWORD)
PG_KEYWORD("delimiters", DELIMITERS, UNRESERVED_KEYWORD)
PG_KEYWORD("depends", DEPENDS, UNRESERVED_KEYWORD)
PG_KEYWORD("desc", DESC, RESERVED_KEYWORD)
+PG_KEYWORD("detach", DETACH, UNRESERVED_KEYWORD)
PG_KEYWORD("dictionary", DICTIONARY, UNRESERVED_KEYWORD)
PG_KEYWORD("disable", DISABLE_P, UNRESERVED_KEYWORD)
PG_KEYWORD("discard", DISCARD, UNRESERVED_KEYWORD)
@@ -191,6 +193,7 @@ PG_KEYWORD("implicit", IMPLICIT_P, UNRESERVED_KEYWORD)
PG_KEYWORD("import", IMPORT_P, UNRESERVED_KEYWORD)
PG_KEYWORD("in", IN_P, RESERVED_KEYWORD)
PG_KEYWORD("including", INCLUDING, UNRESERVED_KEYWORD)
+PG_KEYWORD("inclusive", INCLUSIVE, UNRESERVED_KEYWORD)
PG_KEYWORD("increment", INCREMENT, UNRESERVED_KEYWORD)
PG_KEYWORD("index", INDEX, UNRESERVED_KEYWORD)
PG_KEYWORD("indexes", INDEXES, UNRESERVED_KEYWORD)
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index a13c6fb..3d45663 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -65,7 +65,8 @@ typedef enum ParseExprKind
EXPR_KIND_EXECUTE_PARAMETER, /* parameter value in EXECUTE */
EXPR_KIND_TRIGGER_WHEN, /* WHEN condition in CREATE TRIGGER */
EXPR_KIND_POLICY, /* USING or WITH CHECK expr in policy */
- EXPR_KIND_PARTITION_KEY /* partition key expression */
+ EXPR_KIND_PARTITION_KEY, /* partition key expression */
+ EXPR_KIND_PARTITION_FOR_VALUES /* partition bound */
} ParseExprKind;
--
1.7.1
[text/x-diff] 0004-Infrastructure-for-partition-metadata-storage-and-ma-v4.patch (98.6K, ../../5732984F.6090200@lab.ntt.co.jp/5-0004-Infrastructure-for-partition-metadata-storage-and-ma-v4.patch)
download | inline diff:
From 3639bf66ff5380a7704588b5e1d3a19dcfb16cd1 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Fri, 18 Mar 2016 15:54:17 +0900
Subject: [PATCH 4/6] Infrastructure for partition metadata storage and manipulation.
Catalog pg_partition with:
partrelid oid
partbound pg_node_tree
Now since partition metadata is stored in a dedicated catalog, it is possible
to use that knowledge to enforce special behaviors for inheritance children
(or relations in general) that are partitions. Some such behaviors include:
* Cannot drop using DROP TABLE without first "detaching" from its parent
* Cannot add, drop new columns
* Cannot rename, change type of existing columns
It also becomes now possible to check, from the FOR VALUES specification, that
the new partition (in CREATE TABLE PARTITION OF parent or ALTER TABLE parent
ATTACH PARTITION) does not overlap with any existing partition of parent.
One can get a partition's check expression (that is, an expression tree that
can be used as a check constraint for the rows in it). ExecConstraints() now
checks using that expression, whether a row being inserted or new row value in
update case, violate the partition boundary specification. Also, optimizer's
constraint exclusion uses it remove useless partitions. This expression tree
is cached in the partition's relcache.
When attaching a partition, rows in the table are checked whether any violates
the partition bound specification (AND'ed with the parent's check expression,
if parent itself is a partition), unless NO VALIDATE clause is specified with
the command.
This commit also invents an internal representation of partition bounds in
the form of structs PartitionListBound and PartitionRangeBound, which consist
of datums and information like number of values in a list, booleans for
whether a range bound is infinite, inclusive (vs. exclusive), lower bound
(vs. upper bound), etc. One can get "partition descriptor" for a partitioned
table, which comes in the form of a struct PartitionDesc. It consists of
of an array of oids of partitions, array of pointers to PartitionListBound
structs (NULL if not list partitioned, and 2 arrays of pointers to
PartitionRangeBound structs, (each NULL, if not range partitioned).
A pointer to this structure is also cached in relcache.
One more data structure that this commit invents for future use is struct
PartitionDescNode. It represents a node in partition tree where each node
of the tree corresponds to a partitioned table in the tree. The tree
structure is such that a parent node links to the first child node (which
corresponds with the first partition of the parent that is partitioned
itself), which in turn, links to its right sibling (the next partition
that is partitioned) and so on. Each node consists of some book-keeping
information including a pointer to PartitionDesc of the corresponding table
and recursive pointers just mentioned viz. downlink and next.
Support to route tuples inserted into a partitioned parent to the appropriate
leaf partition will be added shortly which leverages PartitionDescNode.
---
doc/src/sgml/catalogs.sgml | 56 +
src/backend/catalog/Makefile | 2 +-
src/backend/catalog/heap.c | 6 +
src/backend/catalog/partition.c | 1531 ++++++++++++++++++++++++++++
src/backend/commands/copy.c | 2 +-
src/backend/commands/lockcmds.c | 4 +-
src/backend/commands/tablecmds.c | 210 ++++-
src/backend/executor/execMain.c | 75 ++-
src/backend/executor/nodeModifyTable.c | 4 +-
src/backend/nodes/readfuncs.c | 33 +
src/backend/optimizer/util/plancat.c | 13 +
src/backend/parser/analyze.c | 1 +
src/backend/parser/parse_clause.c | 4 +-
src/backend/parser/parse_relation.c | 3 +-
src/backend/parser/parse_utilcmd.c | 6 +
src/backend/utils/adt/ruleutils.c | 78 ++
src/backend/utils/cache/relcache.c | 7 +
src/backend/utils/cache/syscache.c | 12 +
src/bin/pg_dump/common.c | 86 ++
src/bin/pg_dump/pg_dump.c | 98 ++-
src/bin/pg_dump/pg_dump.h | 12 +
src/bin/psql/describe.c | 46 +-
src/include/catalog/indexing.h | 3 +
src/include/catalog/partition.h | 57 +
src/include/catalog/pg_partition.h | 54 +
src/include/nodes/execnodes.h | 4 +
src/include/utils/rel.h | 4 +
src/include/utils/syscache.h | 1 +
src/test/regress/expected/sanity_check.out | 1 +
29 files changed, 2377 insertions(+), 36 deletions(-)
create mode 100644 src/include/catalog/pg_partition.h
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 652f779..9e7c1d9 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -224,6 +224,11 @@
</row>
<row>
+ <entry><link linkend="catalog-pg-partition"><structname>pg_partition</structname></link></entry>
+ <entry>information about partitions</entry>
+ </row>
+
+ <row>
<entry><link linkend="catalog-pg-partitioned"><structname>pg_partitioned</structname></link></entry>
<entry>information about partitioned tables, including the partition key</entry>
</row>
@@ -4677,6 +4682,57 @@
</sect1>
+ <sect1 id="catalog-pg-partition">
+ <title><structname>pg_partition</structname></title>
+
+ <indexterm zone="catalog-pg-partition">
+ <primary>pg_partition</primary>
+ </indexterm>
+
+ <para>
+ The catalog <structname>pg_partition</structname> stores partition bounds
+ of tables that are partitions.
+ </para>
+
+ <table>
+ <title><structname>pg_partition</> Columns</title>
+
+ <tgroup cols="4">
+ <thead>
+ <row>
+ <entry>Name</entry>
+ <entry>Type</entry>
+ <entry>References</entry>
+ <entry>Description</entry>
+ </row>
+ </thead>
+
+ <tbody>
+
+ <row>
+ <entry><structfield>partrelid</structfield></entry>
+ <entry><type>oid</type></entry>
+ <entry><literal><link linkend="catalog-pg-class"><structname>pg_class</structname></link>.oid</literal></entry>
+ <entry>The OID of the <structname>pg_class</> entry for this partition</entry>
+ </row>
+
+ <row>
+ <entry><structfield>partbound</structfield></entry>
+ <entry><type>pg_node_tree</type></entry>
+ <entry></entry>
+ <entry>
+ Expression tree (in <function>nodeToString()</function> representation)
+ for partition boundary value. For list partitions, it is simply a list
+ of values. For range partitions, it consists of lower and upper bound
+ values along with their inclusivity.
+ </entry>
+ </row>
+
+ </tbody>
+ </tgroup>
+ </table>
+ </sect1>
+
<sect1 id="catalog-pg-partitioned">
<title><structname>pg_partitioned</structname></title>
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index 4dc6300..ee1be31 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -41,7 +41,7 @@ POSTGRES_BKI_SRCS = $(addprefix $(top_srcdir)/src/include/catalog/,\
pg_foreign_data_wrapper.h pg_foreign_server.h pg_user_mapping.h \
pg_foreign_table.h pg_policy.h pg_replication_origin.h \
pg_default_acl.h pg_init_privs.h pg_seclabel.h pg_shseclabel.h \
- pg_collation.h pg_range.h pg_transform.h pg_partitioned.h\
+ pg_collation.h pg_range.h pg_transform.h pg_partitioned.h pg_partition.h\
toasting.h indexing.h \
)
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 932a48b..2e99661 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -1808,7 +1808,13 @@ heap_drop_with_catalog(Oid relid)
* If a partitioned table, delete the pg_partitioned tuples.
*/
if (rel->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+ {
RemovePartitionKeyByRelId(relid);
+ RelationDropPartitions(relid);
+ }
+
+ if (relid_is_partition(relid))
+ RemovePartitionEntryByRelId(relid);
/*
* Schedule unlinking of the relation's physical files at commit.
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index 5e238dc..46bbb9e 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -18,19 +18,26 @@
#include "access/heapam.h"
#include "access/htup_details.h"
#include "access/nbtree.h"
+#include "access/sysattr.h"
#include "catalog/dependency.h"
#include "catalog/indexing.h"
#include "catalog/objectaddress.h"
#include "catalog/partition.h"
#include "catalog/pg_collation.h"
+#include "catalog/pg_inherits.h"
+#include "catalog/pg_inherits_fn.h"
#include "catalog/pg_opclass.h"
#include "catalog/pg_partitioned.h"
+#include "catalog/pg_partition.h"
#include "catalog/pg_type.h"
#include "executor/executor.h"
#include "miscadmin.h"
+#include "nodes/makefuncs.h"
#include "nodes/nodeFuncs.h"
+#include "nodes/parsenodes.h"
#include "optimizer/clauses.h"
#include "optimizer/planmain.h"
+#include "optimizer/var.h"
#include "storage/lmgr.h"
#include "utils/array.h"
#include "utils/builtins.h"
@@ -41,10 +48,88 @@
#include "utils/lsyscache.h"
#include "utils/syscache.h"
+/*
+ * Information about a single partition
+ */
+typedef struct PartitionInfo
+{
+ Oid oid; /* partition OID */
+ PartitionListBound *list; /* list partition bound */
+ PartitionRangeBound *rangelower; /* range partition lower bound */
+ PartitionRangeBound *rangeupper; /* range partition upper bound */
+} PartitionInfo;
+
+typedef struct translate_var_attno_mutator_context
+{
+ AttrNumber old_attno;
+ AttrNumber new_attno;
+} translate_var_attno_mutator_context;
+
+/*
+ * Information about partitions of a partitioned table.
+ */
+typedef struct PartitionDescData
+{
+ int nparts; /* number of partitions */
+ Oid *oids; /* OIDs of the partitions */
+ PartitionListBound **lists; /* list bounds */
+ PartitionRangeBound **rangelowers; /* range lower bounds */
+ PartitionRangeBound **rangeuppers; /* range upper bounds */
+} PartitionDescData;
+
static PartitionKey CopyPartitionKey(PartitionKey fromkey);
static KeyTypeCollInfo *copy_key_type_coll_info(int nkeycols,
KeyTypeCollInfo *tcinfo);
static void free_key_type_coll_info(KeyTypeCollInfo *tcinfo);
+static bool list_overlaps_existing_partition(PartitionKey key,
+ PartitionListSpec *list_spec,
+ PartitionDesc pdesc,
+ Oid *with);
+static bool partition_range_empty(PartitionKey key,
+ PartitionRangeSpec *range_spec);
+static int32 compare_range_keys(PartitionKey key, Datum *val1, Datum *val2);
+static bool range_overlaps_existing_partition(PartitionKey key,
+ PartitionRangeSpec *range_spec,
+ PartitionDesc pdesc,
+ Oid *with);
+static PartitionListBound *make_list_from_spec(PartitionKey key, List *values);
+static PartitionRangeBound *make_range_from_spec(PartitionKey key, List *val,
+ bool inclusive, bool lower);
+static bool partition_range_overlaps(PartitionKey key,
+ PartitionRangeBound *part1_lower,
+ PartitionRangeBound *part1_upper,
+ PartitionRangeBound *part2_lower,
+ PartitionRangeBound *part2_upper);
+static int32 partition_range_cmp_bounds(PartitionKey key,
+ PartitionRangeBound *b1,
+ PartitionRangeBound *b2);
+
+static PartitionDesc RelationGetPartitionDesc(Relation relation);
+static PartitionDesc GetPartitionDescCopy(PartitionKey key, PartitionDesc src);
+static PartitionInfo **GetRelPartitions(Relation rel, int *nparts);
+static List *get_partitions(Oid relid, int lockmode);
+static int32 range_partition_cmp_upper(const void *a, const void *b,
+ void *arg);
+static PartitionListBound *copyPartitionListBound(PartitionListBound *src,
+ PartitionKey key);
+static PartitionRangeBound *copyPartitionRangeBound(PartitionRangeBound *src,
+ PartitionKey key);
+static void free_partition_info_array(PartitionInfo **p, int num);
+
+static Expr *get_check_expr_for_list(PartitionKey key, List *values);
+static Expr *get_check_expr_for_range(PartitionKey key,
+ List *lowervalues,
+ bool lowerinc,
+ List *uppervalues,
+ bool upperinc);
+static Node *translate_var_attno(Node *expr, AttrNumber attno,
+ AttrNumber new_attno);
+static Node *translate_var_attno_mutator(Node *node,
+ translate_var_attno_mutator_context *cxt);
+static Node *generate_partition_check_expr(Relation rel);
+
+static PartitionDescNode GetPartitionDescNodeRecurse(Relation rel, int offset);
+static int get_leaf_partition_count(PartitionDescNode pdnode);
/*
* StorePartitionKey
@@ -449,3 +534,1449 @@ FreePartitionKey(PartitionKey key)
free_key_type_coll_info(key->tcinfo);
pfree(key);
}
+
+/*
+ * StorePartitionBound
+ *
+ * Store partition boundary info in pg_partition after checking for its
+ * validity.
+ */
+void
+StorePartitionBound(Oid relid, Oid parentId, Node *bound)
+{
+ Relation pg_partition;
+ Relation parent;
+ PartitionKey key;
+ PartitionDesc pdesc;
+ Oid with;
+ HeapTuple tuple;
+ char *boundString;
+ Datum values[Natts_pg_partition];
+ bool nulls[Natts_pg_partition];
+ ParseState *pstate = make_parsestate(NULL);
+
+ parent = heap_open(parentId, AccessShareLock);
+ key = parent->rd_partkey;
+ pdesc = RelationGetPartitionDesc(parent);
+
+ /*
+ * Check if 'bound' happens to overlap with any of parent's existing
+ * partitions.
+ */
+ switch (key->strategy)
+ {
+ case PARTITION_STRAT_LIST:
+ {
+ PartitionListSpec *list;
+
+ Assert(IsA(bound, PartitionListSpec));
+ list = (PartitionListSpec *) bound;
+ if (list_overlaps_existing_partition(key, list, pdesc, &with))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("new partition's list of values overlaps with that"
+ " of partition \"%s\" of \"%s\"",
+ get_rel_name(with), get_rel_name(parentId)),
+ errhint("Please specify a list of values that does not"
+ " overlap with any existing list partition's list."),
+ parser_errposition(pstate, list->location)));
+ break;
+ }
+
+ case PARTITION_STRAT_RANGE:
+ {
+ PartitionRangeSpec *range;
+
+ Assert(IsA(bound, PartitionRangeSpec));
+ range = (PartitionRangeSpec *) bound;
+ if (partition_range_empty(key, range))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("new partition's range contains no element"),
+ errhint("Please specify a non-empty range."),
+ parser_errposition(pstate, range->location)));
+
+ if (range_overlaps_existing_partition(key, range, pdesc, &with))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("new partition's range overlaps with that of"
+ " partition \"%s\" of \"%s\"",
+ get_rel_name(with), get_rel_name(parentId)),
+ errhint("Please specify a range that does not overlap"
+ " with any existing partition's range."),
+ parser_errposition(pstate, range->location)));
+ break;
+ }
+ }
+
+ pg_partition = heap_open(PartitionRelationId, RowExclusiveLock);
+
+ boundString = nodeToString(bound);
+
+ /* Build the tuple - bound can never be null */
+ MemSet(nulls, false, sizeof(nulls));
+
+ values[Anum_pg_partition_partrelid - 1] = ObjectIdGetDatum(relid);
+ values[Anum_pg_partition_partbound - 1] = CStringGetTextDatum(boundString);
+
+ tuple = heap_form_tuple(RelationGetDescr(pg_partition), values, nulls);
+
+ simple_heap_insert(pg_partition, tuple);
+
+ /* Update the indexes on pg_partition */
+ CatalogUpdateIndexes(pg_partition, tuple);
+
+ /*
+ * Invalid the parent's relcache so that the partition is now included in
+ * its partition descriptor.
+ */
+ CacheInvalidateRelcache(parent);
+
+ heap_close(parent, AccessShareLock);
+ heap_close(pg_partition, RowExclusiveLock);
+}
+
+/*
+ * Does new list partition 'bound' overlap any of existing list partitions?
+ */
+static bool
+list_overlaps_existing_partition(PartitionKey key,
+ PartitionListSpec *list_spec,
+ PartitionDesc pdesc,
+ Oid *with)
+{
+ PartitionListBound *new_list;
+ int i;
+
+ if (pdesc->nparts == 0)
+ return false;
+
+ new_list = make_list_from_spec(key, list_spec->values);
+
+ for (i = 0; i < pdesc->nparts; i++)
+ {
+ int j;
+
+ if (pdesc->lists[i]->contains_null && new_list->contains_null)
+ return true;
+
+ for (j = 0; j < pdesc->lists[i]->nvalues; j++)
+ {
+ int k;
+
+ for (k = 0; k < new_list->nvalues; k++)
+ {
+ int32 result;
+
+ result = DatumGetInt32(FunctionCall2Coll(&key->partsupfunc[0],
+ key->tcinfo->typcoll[0],
+ pdesc->lists[i]->values[j],
+ new_list->values[k]));
+ if (!result)
+ {
+ *with = pdesc->oids[i];
+ return true;
+ }
+ }
+ }
+ }
+
+ return false;
+}
+
+/*
+ * Is new partition's range empty?
+ */
+static bool
+partition_range_empty(PartitionKey key, PartitionRangeSpec *range_spec)
+{
+ PartitionRangeBound *lower,
+ *upper;
+
+ lower = make_range_from_spec(key, range_spec->lower, range_spec->lowerinc,
+ true);
+ upper = make_range_from_spec(key, range_spec->upper, range_spec->upperinc,
+ false);
+
+ /*
+ * Only either lower or upper is allowed to be unbounded, which if
+ * one of them is, the range is not empty.
+ */
+ if (lower->infinite || upper->infinite)
+ return false;
+
+ /*
+ * If upper > lower, ouright empty or if lower = upper and either of
+ * them is exclusive.
+ */
+ if (compare_range_keys(key, upper->val, lower->val) < 0 ||
+ (compare_range_keys(key, lower->val, upper->val) == 0 &&
+ (!lower->inclusive || !upper->inclusive)))
+ return true;
+
+ return false;
+}
+
+/* Compare range composite keys using logic similar to RowCompareExpr */
+static int32
+compare_range_keys(PartitionKey key, Datum *val1, Datum *val2)
+{
+ int32 result;
+ int i;
+
+ for (i = 0; i < key->partnatts; i++)
+ {
+ result = DatumGetInt32(FunctionCall2Coll(&key->partsupfunc[i],
+ key->tcinfo->typcoll[i],
+ val1[i], val2[i]));
+ if (!result)
+ continue;
+ break;
+ }
+
+ return result;
+}
+
+/*
+ * Does new range partition's range overlap any of existing range partitions?
+ */
+static bool
+range_overlaps_existing_partition(PartitionKey key,
+ PartitionRangeSpec *range_spec,
+ PartitionDesc pdesc,
+ Oid *with)
+{
+ int i;
+ PartitionRangeBound *new_lower,
+ *new_upper;
+
+ if (pdesc->nparts == 0)
+ return false;
+
+ /*
+ * Create internal representation of rangelower and rangeupper from
+ * range_spec.
+ */
+ new_lower = make_range_from_spec(key, range_spec->lower, range_spec->lowerinc,
+ true);
+ new_upper = make_range_from_spec(key, range_spec->upper, range_spec->upperinc,
+ false);
+
+ /* Check with existing partitions for overlap */
+ for (i = 0; i < pdesc->nparts; i++)
+ {
+ if (partition_range_overlaps(key, new_lower, new_upper,
+ pdesc->rangelowers[i],
+ pdesc->rangeuppers[i]))
+ {
+ *with = pdesc->oids[i];
+ return true;
+ }
+ }
+
+ return false;
+}
+
+/*
+ * Make a PartitionListBound from list of values using the information in key
+ */
+static PartitionListBound *
+make_list_from_spec(PartitionKey key, List *values)
+{
+ PartitionListBound *list;
+ ListCell *cell;
+ int i;
+
+ list = (PartitionListBound *) palloc0(sizeof(PartitionListBound));
+ list->nvalues = list_length(values);
+ list->values = (Datum *) palloc(list->nvalues * sizeof(Datum));
+
+ i = 0;
+ foreach (cell, values)
+ {
+ Const *val = lfirst(cell);
+
+ if (val->constisnull)
+ list->contains_null = true;
+ else
+ list->values[i] = datumCopy(val->constvalue,
+ key->tcinfo->typbyval[0],
+ key->tcinfo->typlen[0]);
+ i++;
+ }
+
+ return list;
+}
+
+/*
+ * Make a PartitionRangeBound from arguments using the information in key
+ */
+static PartitionRangeBound *
+make_range_from_spec(PartitionKey key, List *val, bool inclusive, bool lower)
+{
+ PartitionRangeBound *range;
+ ListCell *cell;
+
+ range = (PartitionRangeBound *) palloc0(sizeof(PartitionRangeBound));
+ range->infinite = (val == NIL);
+ range->inclusive = inclusive;
+ range->lower = lower;
+
+ if (val)
+ {
+ int i;
+
+ range->val = (Datum *) palloc0(key->partnatts * sizeof(Datum));
+
+ i = 0;
+ foreach (cell, val)
+ {
+ Const *val = lfirst(cell);
+
+ if (val->constisnull)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("cannot specify NULL in range bound")));
+ else
+ range->val[i] = datumCopy(val->constvalue,
+ key->tcinfo->typbyval[i],
+ key->tcinfo->typlen[i]);
+ i++;
+ }
+ }
+
+ return range;
+}
+
+/* Check two range partitions for overlap */
+static bool
+partition_range_overlaps(PartitionKey key,
+ PartitionRangeBound *part1_lower,
+ PartitionRangeBound *part1_upper,
+ PartitionRangeBound *part2_lower,
+ PartitionRangeBound *part2_upper)
+{
+ if (partition_range_cmp_bounds(key, part1_lower, part2_lower) >= 0 &&
+ partition_range_cmp_bounds(key, part1_lower, part2_upper) <= 0)
+ return true;
+
+ if (partition_range_cmp_bounds(key, part2_lower, part1_lower) >= 0 &&
+ partition_range_cmp_bounds(key, part2_lower, part1_upper) <= 0)
+ return true;
+
+ return false;
+}
+
+/* Returns for two range partition bounds whether, b1 <=, =, >= b2 */
+static int32
+partition_range_cmp_bounds(PartitionKey key,
+ PartitionRangeBound *b1,
+ PartitionRangeBound *b2)
+{
+ int32 result;
+
+ /*
+ * First, handle cases involving infinity, which don't require invoking
+ * the comparison proc.
+ */
+ if (b1->infinite && b2->infinite)
+ {
+ /*
+ * Both are infinity, so they are equal unless one is lower and the
+ * other not.
+ */
+ if (b1->lower == b2->lower)
+ return 0;
+ else
+ return b1->lower ? -1 : 1;
+ }
+ else if (b1->infinite)
+ return b1->lower ? -1 : 1;
+ else if (b2->infinite)
+ return b2->lower ? 1 : -1;
+
+ /*
+ * Both boundaries are finite, so compare the held values.
+ */
+ result = compare_range_keys(key, b1->val, b2->val);
+
+ /*
+ * If the comparison is anything other than equal, we're done. If they
+ * compare equal though, we still have to consider whether the boundaries
+ * are inclusive or exclusive.
+ */
+ if (result == 0)
+ {
+ if (!b1->inclusive && !b2->inclusive)
+ {
+ /* both are exclusive */
+ if (b1->lower == b2->lower)
+ return 0;
+ else
+ return b1->lower ? 1 : -1;
+ }
+ else if (!b1->inclusive)
+ return b1->lower ? 1 : -1;
+ else if (!b2->inclusive)
+ return b2->lower ? -1 : 1;
+ else
+ {
+ /*
+ * Both are inclusive and the values held are equal, so they are
+ * equal regardless of whether they are upper or lower boundaries,
+ * or a mix.
+ */
+ return 0;
+ }
+ }
+
+ return result;
+}
+
+/*
+ * RemovePartitionByRelId
+ * Remove a pg_partition entry for a partition.
+ */
+void
+RemovePartitionEntryByRelId(Oid relid)
+{
+ Relation rel;
+ HeapTuple tuple;
+
+ rel = heap_open(PartitionRelationId, RowExclusiveLock);
+ tuple = SearchSysCache1(PARTRELID, ObjectIdGetDatum(relid));
+
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "cache lookup failed for partition entry of relation %u",
+ relid);
+
+ simple_heap_delete(rel, &tuple->t_self);
+
+ /* Update the indexes on pg_partitioned_rel */
+ CatalogUpdateIndexes(rel, tuple);
+
+ ReleaseSysCache(tuple);
+ heap_close(rel, RowExclusiveLock);
+}
+
+/*
+ * does relid have pg_partition entry?
+ */
+bool
+relid_is_partition(Oid relid)
+{
+ return SearchSysCacheExists1(PARTRELID, ObjectIdGetDatum(relid));
+}
+
+/*
+ * Returns inheritance parent of relid by scanning pg_inherits
+ */
+Oid
+get_partition_parent(Oid relid)
+{
+ Form_pg_inherits form;
+ Relation catalogRelation;
+ SysScanDesc scan;
+ ScanKeyData key[2];
+ HeapTuple tuple;
+
+ catalogRelation = heap_open(InheritsRelationId, AccessShareLock);
+
+ ScanKeyInit(&key[0],
+ Anum_pg_inherits_inhrelid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(relid));
+ ScanKeyInit(&key[1],
+ Anum_pg_inherits_inhseqno,
+ BTEqualStrategyNumber, F_INT4EQ,
+ Int32GetDatum(1));
+
+ scan = systable_beginscan(catalogRelation, InheritsRelidSeqnoIndexId, true,
+ NULL, 2, key);
+
+ tuple = systable_getnext(scan);
+ Assert(HeapTupleIsValid(tuple));
+
+ form = (Form_pg_inherits) GETSTRUCT(tuple);
+
+ systable_endscan(scan);
+ heap_close(catalogRelation, AccessShareLock);
+
+ return form->inhparent;
+}
+
+/*
+ * RelationDropPartitions
+ * Find and drop all partitions of relation with OID 'relid'
+ */
+void
+RelationDropPartitions(Oid relid)
+{
+ List *partitionOids;
+ ListCell *cell;
+
+ partitionOids = get_partitions(relid, AccessExclusiveLock);
+
+ foreach (cell, partitionOids)
+ {
+ Oid partitionOid = lfirst_oid(cell);
+ ObjectAddress obj;
+
+ obj.classId = RelationRelationId;
+ obj.objectId = partitionOid;
+ obj.objectSubId = 0;
+
+ performDeletion(&obj, DROP_CASCADE, 0);
+ }
+}
+
+/*
+ * RelationGetPartitionDesc
+ * Get the PartitionDesc of a partitioned table
+ *
+ * Memory for the struct is allocated in the caller's memory context while
+ * a copy is cached in rd_partdesc so that we don't compute the result
+ * unnecessarily (We don't want to return a pointer to the relcache copy
+ * though, since it could disappear due to relcache invalidation.)
+ *
+ * Never returns NULL. If there are no partitions yet, returns the struct
+ * with nparts set to 0.
+ */
+static PartitionDesc
+RelationGetPartitionDesc(Relation relation)
+{
+ int i,
+ nparts;
+ PartitionKey key = relation->rd_partkey;
+ MemoryContext oldcxt;
+ PartitionDesc result;
+ PartitionInfo **parts;
+
+ /* Quick copy and exit if already computed the descriptor */
+ if (relation->rd_partdesc != NULL)
+ return GetPartitionDescCopy(key, relation->rd_partdesc);
+
+ result = (PartitionDesc) palloc0(sizeof(PartitionDescData));
+
+ parts = GetRelPartitions(relation, &nparts);
+ result->nparts = nparts;
+
+ if (parts)
+ {
+ result->oids = (Oid *) palloc0(nparts * sizeof(Oid));
+
+ switch (key->strategy)
+ {
+ case PARTITION_STRAT_LIST:
+ result->lists = (PartitionListBound **) palloc0(nparts *
+ sizeof(PartitionListBound *));
+ break;
+
+ case PARTITION_STRAT_RANGE:
+ result->rangelowers = (PartitionRangeBound **) palloc0(nparts *
+ sizeof(PartitionRangeBound *));
+ result->rangeuppers = (PartitionRangeBound **) palloc0(nparts *
+ sizeof(PartitionRangeBound *));
+
+ /* Sort on range->upper */
+ qsort_arg(parts, nparts, sizeof(PartitionInfo *),
+ range_partition_cmp_upper, key);
+ break;
+ }
+
+ for (i = 0; i < nparts; i++)
+ {
+ result->oids[i] = parts[i]->oid;
+
+ if (parts[i]->list)
+ result->lists[i] = copyPartitionListBound(parts[i]->list, key);
+ else
+ {
+ result->rangelowers[i] = copyPartitionRangeBound(parts[i]->rangelower, key);
+ result->rangeuppers[i] = copyPartitionRangeBound(parts[i]->rangeupper, key);
+ }
+ }
+
+ /* Clean up */
+ free_partition_info_array(parts, nparts);
+ }
+
+ /* Save a copy in the relcache */
+ relation->rd_pdcxt = AllocSetContextCreate(CacheMemoryContext,
+ RelationGetRelationName(relation),
+ ALLOCSET_DEFAULT_MINSIZE,
+ ALLOCSET_DEFAULT_INITSIZE,
+ ALLOCSET_DEFAULT_MAXSIZE);
+
+ relation->rd_partdesc = MemoryContextAllocZero(relation->rd_pdcxt,
+ sizeof(PartitionDescData));
+ oldcxt = MemoryContextSwitchTo(relation->rd_pdcxt);
+ relation->rd_partdesc = GetPartitionDescCopy(key, result);
+ MemoryContextSwitchTo(oldcxt);
+
+ return result;
+}
+
+/*
+ * GetPartitionDescCopy
+ * Copy PartitionDesc of a partitioned relation into caller's context
+ *
+ * Like its caller, never returns NULL.
+ */
+static PartitionDesc
+GetPartitionDescCopy(PartitionKey key, PartitionDesc src)
+{
+ PartitionDesc result;
+ int i;
+
+ result = (PartitionDesc) palloc0(sizeof(PartitionDescData));
+ result->nparts = src->nparts;
+
+ /* If there are partitions, actually copy partitions */
+ if (src->nparts > 0)
+ {
+ result->oids = (Oid *) palloc0(result->nparts * sizeof(Oid));
+
+ switch (key->strategy)
+ {
+ case PARTITION_STRAT_LIST:
+ result->lists = (PartitionListBound **) palloc0(src->nparts *
+ sizeof(PartitionListBound *));
+ break;
+
+ case PARTITION_STRAT_RANGE:
+ result->rangelowers = (PartitionRangeBound **) palloc0(src->nparts *
+ sizeof(PartitionRangeBound *));
+ result->rangeuppers = (PartitionRangeBound **) palloc0(src->nparts *
+ sizeof(PartitionRangeBound *));
+ break;
+ }
+
+ for (i = 0; i < src->nparts; i++)
+ {
+ result->oids[i] = src->oids[i];
+
+ switch (key->strategy)
+ {
+ case PARTITION_STRAT_LIST:
+ result->lists[i] = copyPartitionListBound(src->lists[i], key);
+ break;
+
+ case PARTITION_STRAT_RANGE:
+ result->rangelowers[i] = copyPartitionRangeBound(src->rangelowers[i], key);
+ result->rangeuppers[i] = copyPartitionRangeBound(src->rangeuppers[i], key);
+ break;
+ }
+ }
+ }
+
+ return result;
+}
+
+/*
+ * GetRelPartitions
+ * Return information of partitions of rel
+ *
+ * Returns number of partitions in *nparts.
+ */
+static PartitionInfo **
+GetRelPartitions(Relation rel, int *nparts)
+{
+ int i;
+ Relation partrel;
+ List *partoids;
+ ListCell *cell;
+ PartitionInfo **parts;
+ PartitionKey key = rel->rd_partkey;
+
+ partoids = get_partitions(RelationGetRelid(rel), NoLock);
+
+ /* no partitions found */
+ if ((*nparts = list_length(partoids)) == 0)
+ return NULL;
+
+ partrel = heap_open(PartitionRelationId, AccessShareLock);
+
+ parts = (PartitionInfo **) palloc0(*nparts * sizeof(PartitionInfo *));
+
+ i = 0;
+ foreach(cell, partoids)
+ {
+ Oid partrelid = lfirst_oid(cell);
+ HeapTuple tuple;
+ Datum datum;
+ bool isnull;
+ Node *bound;
+
+ tuple = SearchSysCache1(PARTRELID, partrelid);
+ Assert(HeapTupleIsValid(tuple));
+
+ parts[i] = (PartitionInfo *) palloc0(sizeof(PartitionInfo));
+ parts[i]->oid = partrelid;
+
+ datum = fastgetattr(tuple,
+ Anum_pg_partition_partbound,
+ partrel->rd_att,
+ &isnull);
+
+ bound = stringToNode(TextDatumGetCString(datum));
+
+ switch (key->strategy)
+ {
+ case PARTITION_STRAT_LIST:
+ {
+ PartitionListSpec *list_spec;
+
+ Assert(IsA(bound, PartitionListSpec));
+ list_spec = (PartitionListSpec *) bound;
+ parts[i]->list = make_list_from_spec(key, list_spec->values);
+ }
+ break;
+
+ case PARTITION_STRAT_RANGE:
+ {
+ PartitionRangeSpec *range_spec;
+
+ Assert(IsA(bound, PartitionRangeSpec));
+ range_spec = (PartitionRangeSpec *) bound;
+
+ parts[i]->rangelower = make_range_from_spec(key,
+ range_spec->lower,
+ range_spec->lowerinc,
+ true);
+ parts[i]->rangeupper = make_range_from_spec(key,
+ range_spec->upper,
+ range_spec->upperinc,
+ false);
+ }
+ break;
+ }
+
+ ReleaseSysCache(tuple);
+ i++;
+ }
+
+ heap_close(partrel, AccessShareLock);
+
+ return parts;
+}
+
+/*
+ * Wrapper around find_inheritance_children for use by partitioning
+ * related code
+ */
+static List *
+get_partitions(Oid relid, int lockmode)
+{
+ return find_inheritance_children(relid, lockmode);
+}
+
+/*
+ * range_partition_cmp_upper
+ * Compare two range partition PartitionInfos using their rangeupper
+ * bounds
+ *
+ * Used as a callback to qsort_arg for sorting range partitions.
+ */
+static int32
+range_partition_cmp_upper(const void *a, const void *b, void *arg)
+{
+ PartitionRangeBound *b1 = (*(const PartitionInfo **) a)->rangeupper,
+ *b2 = (*(const PartitionInfo **) b)->rangeupper;
+
+ if (b1->infinite || b2->infinite)
+ return b1->infinite ? 1 : -1;
+
+ return compare_range_keys((PartitionKey) arg, b1->val, b2->val);
+}
+
+/*
+ * Helper routine to copy a list partition bound struct
+ */
+static PartitionListBound *
+copyPartitionListBound(PartitionListBound *src, PartitionKey key)
+{
+ int i;
+ PartitionListBound *result;
+
+ result = (PartitionListBound *) palloc0(sizeof(PartitionListBound));
+ result->nvalues = src->nvalues;
+ result->contains_null = src->contains_null;
+ result->values = (Datum *) palloc0(src->nvalues * sizeof(Datum));
+
+ for (i = 0; i < src->nvalues; i++)
+ result->values[i] = datumCopy(src->values[i],
+ key->tcinfo->typbyval[0],
+ key->tcinfo->typlen[0]);
+ return result;
+}
+
+/*
+ * Helper routine to copy a range partition bound struct
+ */
+static PartitionRangeBound *
+copyPartitionRangeBound(PartitionRangeBound *src, PartitionKey key)
+{
+ int i;
+ PartitionRangeBound *result;
+
+ result = (PartitionRangeBound *) palloc0(sizeof(PartitionRangeBound));
+ result->infinite = src->infinite;
+ result->inclusive = src->inclusive;
+ result->lower = src->lower;
+
+ if (src->val)
+ {
+ result->val = (Datum *) palloc0(key->partnatts * sizeof(Datum));
+ for (i = 0; i < key->partnatts; i++)
+ result->val[i] = datumCopy(src->val[i],
+ key->tcinfo->typbyval[i],
+ key->tcinfo->typlen[i]);
+ }
+
+ return result;
+}
+
+/*
+ * Helper routine to deep-free a PartitionInfo array
+ */
+static void
+free_partition_info_array(PartitionInfo **p, int num)
+{
+ int i;
+
+ for (i = 0; i < num; i++)
+ {
+ if (p[i]->list)
+ {
+ pfree(p[i]->list->values);
+ pfree(p[i]->list);
+ }
+
+ if (p[i]->rangelower)
+ {
+ if (p[i]->rangelower->val)
+ pfree(p[i]->rangelower->val);
+ pfree(p[i]->rangelower);
+ }
+
+ if (p[i]->rangeupper)
+ {
+ if (p[i]->rangeupper->val)
+ pfree(p[i]->rangeupper->val);
+ pfree(p[i]->rangeupper);
+ }
+
+ pfree(p[i]);
+ }
+
+ pfree(p);
+}
+
+/*
+ * get_check_expr_from_partbound - for direct use by tablecmds.c
+ */
+Expr *
+get_check_expr_from_partbound(Relation rel, Relation parent, Node *bound)
+{
+ PartitionKey key = parent->rd_partkey;
+ Expr *my_expr;
+ int i;
+ ListCell *partexprs_item;
+
+ if (IsA(bound, PartitionListSpec))
+ {
+ PartitionListSpec *list_spec = (PartitionListSpec *) bound;
+
+ my_expr = get_check_expr_for_list(key, list_spec->values);
+ }
+ else if (IsA(bound, PartitionRangeSpec))
+ {
+ PartitionRangeSpec *range_spec = (PartitionRangeSpec *) bound;
+
+ my_expr = get_check_expr_for_range(key,
+ range_spec->lower,
+ range_spec->lowerinc,
+ range_spec->upper,
+ range_spec->upperinc);
+ }
+
+ /*
+ * Translate vars in the generated expression with the correct attnos.
+ * Note that the vars in my_expr bear attnos dictated by key which carries
+ * attnos of the parent.
+ */
+ partexprs_item = list_head(key->partexprs);
+ for (i = 0; i < key->partnatts; i++)
+ {
+ AttrNumber attno = key->partattrs[i],
+ new_attno;
+ char *attname;
+
+ if (attno != 0)
+ {
+ attname = get_attname(RelationGetRelid(parent), attno);
+ new_attno = get_attnum(RelationGetRelid(rel), attname);
+
+ my_expr = (Expr *) translate_var_attno((Node *) my_expr,
+ attno,
+ new_attno);
+ }
+ else
+ {
+ Node *expr = (Node *) lfirst(partexprs_item);
+ Bitmapset *expr_attrs = NULL;
+ int index;
+
+ pull_varattnos(expr, 1, &expr_attrs);
+ partexprs_item = lnext(partexprs_item);
+
+ index = -1;
+ while ((index = bms_next_member(expr_attrs, index)) > 0)
+ {
+ AttrNumber attno = index + FirstLowInvalidHeapAttributeNumber;
+ attname = get_attname(RelationGetRelid(parent), attno);
+ new_attno = get_attnum(RelationGetRelid(rel), attname);
+ my_expr = (Expr *) translate_var_attno((Node *) my_expr,
+ attno,
+ new_attno);
+ }
+ }
+ }
+
+ return my_expr;
+}
+
+/*
+ * get_check_expr_for_list
+ */
+static Expr *
+get_check_expr_for_list(PartitionKey key, List *values)
+{
+ ArrayExpr *values_arr = makeNode(ArrayExpr);
+ ScalarArrayOpExpr *arrayop_expr;
+ Node *key_col;
+ Oid operoid;
+ StrategyNumber strategy = BTEqualStrategyNumber;
+
+ /* Btree equality operator of specified opfamily */
+ operoid = get_opfamily_member(key->partopfamily[0],
+ key->tcinfo->typid[0],
+ key->tcinfo->typid[0],
+ strategy);
+
+ /* lexpr */
+ if (key->partattrs[0] != 0)
+ key_col = (Node *) makeVar(1, key->partattrs[0],
+ key->tcinfo->typid[0],
+ key->tcinfo->typmod[0],
+ key->tcinfo->typcoll[0],
+ 0);
+ else
+ key_col = (Node *) linitial(key->partexprs);
+
+ /* rexpr */
+ values_arr->array_typeid = get_array_type(key->tcinfo->typid[0]);
+ values_arr->array_collid = key->tcinfo->typcoll[0];
+ values_arr->element_typeid = key->tcinfo->typid[0];
+ values_arr->elements = values;
+ values_arr->multidims = false;
+ values_arr->location = -1;
+
+ /* build the actual predicate expression node */
+ arrayop_expr = makeNode(ScalarArrayOpExpr);
+ arrayop_expr->opno = operoid;
+ arrayop_expr->opfuncid = get_opcode(operoid);
+ arrayop_expr->useOr = true;
+ arrayop_expr->inputcollid = key->tcinfo->typcoll[0];
+ arrayop_expr->args = list_make2(key_col, values_arr);
+ arrayop_expr->location = -1;
+
+ return (Expr *) arrayop_expr;
+}
+
+/*
+ * get_check_expr_for_range
+ */
+static Expr *
+get_check_expr_for_range(PartitionKey key,
+ List *lowervalues,
+ bool lowerinc,
+ List *uppervalues,
+ bool upperinc)
+{
+ List *and_args = NIL;
+ ListCell *cell1,
+ *cell2,
+ *partexprs_item;
+ int i;
+ uint16 strategy;
+
+ /*
+ * Handle the case with only one bound.
+ *
+ * In this case, consider only the first column of the key and generate
+ * appropriate opclause since only the comparison with the first column
+ * would have determined whether a rows went into such a partition.
+ * It's always true that -INF < someval and someval < +INF.
+ */
+ if (lowervalues == NIL || uppervalues == NIL)
+ {
+ List *values;
+ Const *key_val;
+ Node *key_col;
+ Oid operoid;
+ bool islower,
+ inclusive;
+
+ if (lowervalues != NIL)
+ {
+ values = lowervalues;
+ islower = true;
+ inclusive = lowerinc;
+ }
+ else
+ {
+ values = uppervalues;
+ islower = false;
+ inclusive = upperinc;
+ }
+
+ /* lexpr */
+ if (key->partattrs[0] != 0)
+ key_col = (Node *) makeVar(1, key->partattrs[0],
+ key->tcinfo->typid[0],
+ key->tcinfo->typmod[0],
+ key->tcinfo->typcoll[0],
+ 0);
+ else
+ key_col = (Node *) linitial(key->partexprs);
+
+ /* rexpr */
+ key_val = linitial(values);
+
+ if (islower)
+ strategy = inclusive ? BTGreaterEqualStrategyNumber : BTGreaterStrategyNumber;
+ else
+ strategy = inclusive ? BTLessEqualStrategyNumber : BTLessStrategyNumber;
+
+ operoid = get_opfamily_member(key->partopfamily[0],
+ key->tcinfo->typid[0],
+ key->tcinfo->typid[0],
+ strategy);
+
+ and_args = lappend(and_args,
+ make_opclause(operoid, BOOLOID,
+ false,
+ (Expr *) key_col,
+ (Expr *) key_val,
+ InvalidOid,
+ key->tcinfo->typcoll[0]));
+
+ return makeBoolExpr(AND_EXPR, and_args, -1);
+ }
+
+ /* Both bounds constrain values going into the partition. */
+ i = 0;
+ partexprs_item = list_head(key->partexprs);
+ forboth (cell1, lowervalues, cell2, uppervalues)
+ {
+ Node *key_col;
+ Const *lower_val = lfirst(cell1);
+ Const *upper_val = lfirst(cell2);
+ Expr *lower_clause,
+ *upper_clause;
+ Oid eqop,
+ lowerop,
+ upperop;
+ EState *estate;
+ MemoryContext oldcxt;
+ Expr *test_expr;
+ ExprState *test_exprstate;
+ Datum test_result;
+ bool isNull;
+
+ /* lexpr */
+ if (key->partattrs[i] != 0)
+ {
+ key_col = (Node *) makeVar(1, key->partattrs[i],
+ key->tcinfo->typid[i],
+ key->tcinfo->typmod[i],
+ key->tcinfo->typcoll[i],
+ 0);
+ }
+ else
+ {
+ key_col = (Node *) lfirst(partexprs_item);
+ partexprs_item = lnext(partexprs_item);
+ }
+
+ /*
+ * If lower_val = upper_val (= val, say), create a column = val
+ * constraint.
+ */
+ eqop = get_opfamily_member(key->partopfamily[i],
+ key->tcinfo->typid[i],
+ key->tcinfo->typid[i],
+ BTEqualStrategyNumber);
+
+ estate = CreateExecutorState();
+ oldcxt = MemoryContextSwitchTo(estate->es_query_cxt);
+ test_expr = make_opclause(eqop,
+ BOOLOID,
+ false,
+ (Expr *) lower_val,
+ (Expr *) upper_val,
+ InvalidOid,
+ key->tcinfo->typcoll[i]);
+ fix_opfuncids((Node *) test_expr);
+ test_exprstate = ExecInitExpr(test_expr, NULL);
+ test_result = ExecEvalExprSwitchContext(test_exprstate,
+ GetPerTupleExprContext(estate),
+ &isNull, NULL);
+ MemoryContextSwitchTo(oldcxt);
+ FreeExecutorState(estate);
+
+ if (DatumGetBool(test_result))
+ {
+ and_args = lappend(and_args,
+ make_opclause(eqop,
+ BOOLOID,
+ false,
+ (Expr *) key_col,
+ /* could've been upper_val */
+ (Expr *) lower_val,
+ InvalidOid,
+ key->tcinfo->typcoll[i]));
+
+ /* Go to the next column. */
+ }
+ else
+ {
+ /*
+ * For all columns but last, use inclusive strategy (<= or >=).
+ *
+ * For the last column, the bound's inclusivity determines.
+ */
+ strategy = i < key->partnatts - 1 || lowerinc
+ ? BTGreaterEqualStrategyNumber
+ : BTGreaterStrategyNumber;
+ lowerop = get_opfamily_member(key->partopfamily[i],
+ key->tcinfo->typid[i],
+ key->tcinfo->typid[i],
+ strategy);
+ strategy = i < key->partnatts - 1 || upperinc
+ ? BTLessEqualStrategyNumber
+ : BTLessStrategyNumber;
+ upperop = get_opfamily_member(key->partopfamily[i],
+ key->tcinfo->typid[i],
+ key->tcinfo->typid[i],
+ strategy);
+
+ lower_clause = make_opclause(lowerop,
+ BOOLOID,
+ false,
+ (Expr *) key_col,
+ (Expr *) lower_val,
+ InvalidOid,
+ key->tcinfo->typcoll[i]);
+ upper_clause = make_opclause(upperop,
+ BOOLOID,
+ false,
+ (Expr *) key_col,
+ (Expr *) upper_val,
+ InvalidOid,
+ key->tcinfo->typcoll[i]);
+
+ and_args = lappend(and_args,
+ makeBoolExpr(AND_EXPR,
+ list_make2(lower_clause, upper_clause), -1));
+
+ /* No need to constraint further columns. */
+ break;
+ }
+
+ i++;
+ }
+
+ return makeBoolExpr(AND_EXPR, and_args, -1);
+}
+
+/*
+ * translate_var_attno
+ * Changes Vars with a given attno in the provided expression tree to
+ * Vars with new_attno
+ */
+static Node *
+translate_var_attno(Node *expr, AttrNumber attno, AttrNumber new_attno)
+{
+ translate_var_attno_mutator_context cxt;
+
+ cxt.old_attno = attno;
+ cxt.new_attno = new_attno;
+
+ return expression_tree_mutator(expr, translate_var_attno_mutator, &cxt);
+}
+
+/*
+ * translate_var_attno_mutator
+ */
+static Node *
+translate_var_attno_mutator(Node *node,
+ translate_var_attno_mutator_context *cxt)
+{
+ if (node == NULL)
+ return NULL;
+
+ if (IsA(node, Var) && ((Var *) node)->varattno == cxt->old_attno)
+ {
+ Var *newvar = copyObject(node);
+
+ newvar->varattno = cxt->new_attno;
+
+ return (Node *) newvar;
+ }
+
+ return expression_tree_mutator(node, translate_var_attno_mutator,
+ (void *) cxt);
+}
+
+/*
+ * RelationGetPartitionConstraint
+ * Returns the equivelent CHECK constraint expression for a partition
+ */
+Node *
+RelationGetPartitionCheckExpr(Relation rel)
+{
+ /* Quick exit */
+ if (!relid_is_partition(RelationGetRelid(rel)))
+ return NULL;
+
+ /* Quick copy */
+ if (rel->rd_partcheck)
+ return copyObject(rel->rd_partcheck);
+
+ /* None of those, generate */
+ return generate_partition_check_expr(rel);
+}
+
+/*
+ * generate_partition_constraints
+ * Generate partition check constraint expression rel's pg_partition
+ * entry
+ *
+ * Result expression tree is stored CacheMemoryContext to ensure it survives
+ * as long as the relcache entry. But we should be running in a less long-lived
+ * working context. To avoid leaking cache memory if this routine fails partway
+ * routine fails partway through, we build in working memory and then copy
+ * the completed structure into cache memory.
+ */
+static Node *
+generate_partition_check_expr(Relation rel)
+{
+ HeapTuple tuple;
+ MemoryContext oldcxt;
+ Datum boundDatum;
+ bool isnull;
+ Node *bound;
+ Node *my_expr = NULL,
+ *result = NULL;
+ Oid relid = RelationGetRelid(rel);
+ Relation parent = heap_open(get_partition_parent(relid), NoLock);
+
+ /* quick copy */
+ if (rel->rd_partcheck)
+ {
+ heap_close(parent, NoLock);
+ return copyObject(rel->rd_partcheck);
+ }
+
+ tuple = SearchSysCache1(PARTRELID, relid);
+ Assert(HeapTupleIsValid(tuple));
+ boundDatum = SysCacheGetAttr(PARTRELID, tuple,
+ Anum_pg_partition_partbound,
+ &isnull);
+ Assert(!isnull);
+ bound = stringToNode(TextDatumGetCString(boundDatum));
+
+ my_expr = (Node *) get_check_expr_from_partbound(rel, parent, bound);
+
+ /*
+ * If parent is also a partition, create a AND_EXPR BoolExpr with just
+ * created expression and parent's check expression, as its args.
+ */
+ if (relid_is_partition(RelationGetRelid(parent)))
+ {
+ Node *parent_expr;
+
+ parent_expr = generate_partition_check_expr(parent);
+ result = (Node *) makeBoolExpr(AND_EXPR,
+ list_make2(parent_expr, my_expr), -1);
+ }
+ else
+ result = my_expr;
+
+ oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
+ rel->rd_partcheck = copyObject(result);
+ MemoryContextSwitchTo(oldcxt);
+
+ ReleaseSysCache(tuple);
+ heap_close(parent, NoLock);
+
+ return result;
+}
+
+/*
+ * RelationGetPartitionDescNode
+ * Recursively form partition node tree rooted at rel's node
+ */
+PartitionDescNode
+RelationGetPartitionDescNode(Relation rel)
+{
+ PartitionDescNode root;
+
+ /*
+ * Recurse to build the tree of partition descriptor nodes with
+ * rel's descriptor as the root of the tree.
+ */
+ root = GetPartitionDescNodeRecurse(rel, 0);
+ root->index = 0;
+ root->num_leaf_partitions = get_leaf_partition_count(root);
+
+ return root;
+}
+
+/*
+ * GetPartitionDescNodeRecurse
+ * Workhorse of RelationGetPartitionDescNode
+ *
+ * offset is the index (0-based) of the first leaf node in the tree, as
+ * as determined and passed by the parent or the left sibling.
+ */
+static PartitionDescNode
+GetPartitionDescNodeRecurse(Relation rel, int offset)
+{
+ PartitionDescNode root,
+ prev;
+ PartitionDesc pdesc = RelationGetPartitionDesc(rel);
+ int i;
+
+ /* First build our own node */
+ root = (PartitionDescNode) palloc0(sizeof(PartitionDescNodeData));
+ root->pdesc = pdesc;
+ root->relid = RelationGetRelid(rel);
+ root->offset = offset;
+ root->downlink = NULL;
+ root->next = NULL;
+
+ /*
+ * Recursively add nodes for partitions that are themselves partitioned,
+ * linking ourselves to the first such node via 'downlink'. Each node
+ * thus created (being on the same level) is linked to the right sibling
+ * via 'next'. For each node, determine the offset to pass and once
+ * created, update its index and num_leaf_partitions.
+ */
+ prev = NULL;
+ for (i = 0; i < pdesc->nparts; i++)
+ {
+ Oid relid = pdesc->oids[i];
+ int offset;
+ Relation rel;
+ PartitionDescNode pdnode;
+
+ if (!relid_is_partitioned(relid))
+ continue;
+
+ rel = heap_open(relid, AccessShareLock);
+
+ /* Offset for the new node */
+ if (prev)
+ /*
+ * Count leaves on the left - prev node has the account of those
+ * on its own left and then there may have been some siblings
+ * in-between that were leaves themselves.
+ */
+ offset = prev->offset + prev->num_leaf_partitions +
+ (i - prev->index - 1);
+ else
+ /*
+ * We're the first child - account of leaf nodes comes from the
+ * parent. Left siblings were all leaves themselves.
+ */
+ offset = root->offset + i;
+
+ pdnode = GetPartitionDescNodeRecurse(rel, offset);
+ pdnode->index = i;
+ pdnode->num_leaf_partitions = get_leaf_partition_count(pdnode);
+
+ heap_close(rel, AccessShareLock);
+
+ /* Found our first child; link to it. */
+ if (root->downlink == NULL)
+ root->downlink = pdnode;
+
+ /* Link new node to the left sibling, if any */
+ if (prev)
+ prev->next = pdnode;
+
+ prev = pdnode;
+ }
+
+ return root;
+}
+
+/*
+ * get_leaf_partition_count
+ * Recursively count the number of leaf partitions in the partition
+ * tree rooted at pdnode
+ */
+static int
+get_leaf_partition_count(PartitionDescNode pdnode)
+{
+ int i;
+ int result = 0;
+ PartitionDescNode node = pdnode->downlink;
+
+ for (i = 0; i < pdnode->pdesc->nparts; i++)
+ {
+ /* Indexes 0..(node->index - 1) are of leaf partitions */
+ if (node && i == node->index)
+ {
+ result += get_leaf_partition_count(node);
+ node = node->next;
+ }
+ else
+ result += 1;
+ }
+
+ return result;
+}
+
+/*
+ * get_leaf_partition_count
+ * Recursively compute the list of OIDs of leaf partitions in the
+ * partition tree rooted at pdnode
+ */
+List *
+get_leaf_partition_oids(PartitionDescNode pdnode)
+{
+ int i;
+ List *result = NIL;
+ Oid *myparts = pdnode->pdesc->oids;
+ int n_myparts = pdnode->pdesc->nparts;
+ PartitionDescNode node = pdnode->downlink;
+
+ for (i = 0; i < n_myparts; i++)
+ {
+ /* Indexes 0..(node->index - 1) are leaf partitions */
+ if (node && i == node->index)
+ {
+ result = list_concat(result, get_leaf_partition_oids(node));
+ node = node->next;
+ }
+ else
+ result = lappend_oid(result, myparts[i]);
+ }
+
+ return result;
+}
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 403738d..14e81de 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -2464,7 +2464,7 @@ CopyFrom(CopyState cstate)
if (!skip_tuple)
{
/* Check the constraints of the tuple */
- if (cstate->rel->rd_att->constr)
+ if (cstate->rel->rd_att->constr || resultRelInfo->ri_PartitionCheck)
ExecConstraints(resultRelInfo, slot, estate);
if (useHeapMultiInsert)
diff --git a/src/backend/commands/lockcmds.c b/src/backend/commands/lockcmds.c
index 62671a8..5120c93 100644
--- a/src/backend/commands/lockcmds.c
+++ b/src/backend/commands/lockcmds.c
@@ -16,6 +16,7 @@
#include "access/heapam.h"
#include "catalog/namespace.h"
+#include "catalog/partition.h"
#include "catalog/pg_inherits_fn.h"
#include "commands/lockcmds.h"
#include "miscadmin.h"
@@ -63,7 +64,8 @@ LockTableCommand(LockStmt *lockstmt)
RangeVarCallbackForLockTable,
(void *) &lockstmt->mode);
- if (recurse)
+ /* Force inheritance recursion, if partitioned table. */
+ if (recurse || relid_is_partitioned(reloid))
LockTableRecurse(reloid, lockstmt->mode, lockstmt->nowait);
}
}
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 3af134b..c894d11 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -121,6 +121,19 @@ static List *on_commits = NIL;
/*
+ * For AT ATTACH PARTITION <table> FOR VALUES <partition_bound_spec>,
+ *
+ * Struct describing partition check expression corresponding to
+ * <partition_bound_spec> to validate for rows in <table> during
+ * Phase 3 scan.
+ */
+typedef struct PartitionCheck
+{
+ Expr *expr;
+ List *state;
+} PartitionCheck;
+
+/*
* State information for ALTER TABLE
*
* The pending-work queue for an ALTER TABLE is a List of AlteredTableInfo
@@ -163,6 +176,8 @@ typedef struct AlteredTableInfo
Oid newTableSpace; /* new tablespace; 0 means no change */
bool chgPersistence; /* T if SET LOGGED/UNLOGGED is used */
char newrelpersistence; /* if above is true */
+ PartitionCheck *partitionCheck; /* partition check expression for
+ * ATTACH PARTITION validation */
/* Objects to rebuild after completing ALTER TYPE operations */
List *changedConstraintOids; /* OIDs of constraints to rebuild */
List *changedConstraintDefs; /* string definitions of same */
@@ -712,6 +727,19 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
StoreCatalogInheritance(relationId, inheritOids);
/*
+ * Process and store partition bound. Do this before
+ * CommandCounterIncrement() below, so that we are not deemed an existing
+ * partition by StorePartitionBound() (which is basically found out by
+ * querying pg_inherits).
+ */
+ if (stmt->partbound)
+ {
+ Oid parentId = linitial_oid(inheritOids);
+
+ StorePartitionBound(relationId, parentId, stmt->partbound);
+ }
+
+ /*
* We must bump the command counter to make the newly-created relation
* tuple visible for opening.
*/
@@ -995,6 +1023,13 @@ RangeVarCallbackForDropRelation(const RangeVar *rel, Oid relOid, Oid oldRelOid,
if (!OidIsValid(relOid))
return;
+ if (relid_is_partition(relOid))
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("\"%s\" is a partition of \"%s\"", rel->relname,
+ get_rel_name(get_partition_parent(relOid))),
+ errhint("Use ALTER TABLE DETACH PARTITION to be able to drop it.")));
+
tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relOid));
if (!HeapTupleIsValid(tuple))
return; /* concurrently dropped, so nothing to do */
@@ -1086,7 +1121,8 @@ ExecuteTruncate(TruncateStmt *stmt)
rels = lappend(rels, rel);
relids = lappend_oid(relids, myrelid);
- if (recurse)
+ /* Force inheritance recursion, if partitioned table. */
+ if (recurse || relid_is_partitioned(myrelid))
{
ListCell *child;
List *children;
@@ -1810,8 +1846,12 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
Node *expr;
bool found_whole_row;
- /* ignore if the constraint is non-inheritable */
- if (check[i].ccnoinherit)
+ /*
+ * ignore if the constraint is non-inheritable and this is not
+ * the partition case (constraints of partitioned tables are
+ * always present in their partitions)
+ */
+ if (check[i].ccnoinherit && !is_partition)
continue;
/* Adjust Vars to match new table's column numbering */
@@ -2246,6 +2286,11 @@ renameatt_check(Oid myrelid, Form_pg_class classform, bool recursing)
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot rename column of typed table")));
+ if (relid_is_partition(myrelid) && !recursing)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot rename column of a partition")));
+
/*
* Renaming the columns of sequences or toast tables doesn't actually
* break anything from the system's point of view, since internal
@@ -2472,11 +2517,13 @@ renameatt(RenameStmt *stmt)
return InvalidObjectAddress;
}
+ /* Force inheritance recursion, if partitioned table. */
attnum =
renameatt_internal(relid,
stmt->subname, /* old att name */
stmt->newname, /* new att name */
- interpretInhOption(stmt->relation->inhOpt), /* recursive? */
+ interpretInhOption(stmt->relation->inhOpt) ||
+ relid_is_partitioned(relid), /* recursive? */
false, /* recursing? */
0, /* expected inhcount */
stmt->behavior);
@@ -2624,11 +2671,13 @@ RenameConstraint(RenameStmt *stmt)
}
}
+ /* Force inheritance recursion, if partitioned table. */
return
rename_constraint_internal(relid, typid,
stmt->subname,
stmt->newname,
- stmt->relation ? interpretInhOption(stmt->relation->inhOpt) : false, /* recursive? */
+ stmt->relation ? interpretInhOption(stmt->relation->inhOpt) ||
+ relid_is_partitioned(relid) : false, /* recursive? */
false, /* recursing? */
0 /* expected inhcount */ );
@@ -2870,8 +2919,11 @@ AlterTable(Oid relid, LOCKMODE lockmode, AlterTableStmt *stmt)
CheckTableNotInUse(rel, "ALTER TABLE");
+ /* Force inheritance recursion, if partitioned table */
ATController(stmt,
- rel, stmt->cmds, interpretInhOption(stmt->relation->inhOpt),
+ rel, stmt->cmds,
+ interpretInhOption(stmt->relation->inhOpt) ||
+ relid_is_partitioned(RelationGetRelid(rel)),
lockmode);
}
@@ -3988,7 +4040,7 @@ ATRewriteTables(AlterTableStmt *parsetree, List **wqueue, LOCKMODE lockmode)
* Test the current data within the table against new constraints
* generated by ALTER TABLE commands, but don't rebuild data.
*/
- if (tab->constraints != NIL || tab->new_notnull)
+ if (tab->constraints != NIL || tab->new_notnull || tab->partitionCheck)
ATRewriteTable(tab, InvalidOid, lockmode);
/*
@@ -4132,6 +4184,21 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
}
}
+ /*
+ * Build expression execution states for partition check expression
+ * as well.
+ */
+ if (tab->partitionCheck)
+ {
+ List *expr;
+
+ needscan = true;
+ Assert(tab->partitionCheck->expr);
+ expr = list_make1(tab->partitionCheck->expr);
+ tab->partitionCheck->state = (List *)
+ ExecPrepareExpr((Expr *) expr, estate);
+ }
+
foreach(l, tab->newvals)
{
NewColumnValue *ex = lfirst(l);
@@ -4321,6 +4388,12 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
}
}
+ if (tab->partitionCheck &&
+ !ExecQual(tab->partitionCheck->state, econtext, true))
+ ereport(ERROR,
+ (errcode(ERRCODE_CHECK_VIOLATION),
+ errmsg("table contains a row violating the partition bound specification")));
+
/* Write the tuple out to the new relation */
if (newrel)
heap_insert(newrel, tuple, mycid, hi_options, bistate);
@@ -4518,7 +4591,8 @@ ATSimpleRecursion(List **wqueue, Relation rel,
*/
if (recurse &&
(rel->rd_rel->relkind == RELKIND_RELATION ||
- rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE))
+ rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE ||
+ rel->rd_rel->relkind == RELKIND_PARTITIONED_REL))
{
Oid relid = RelationGetRelid(rel);
ListCell *child;
@@ -4840,6 +4914,11 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
if (recursing)
ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
+ if (relid_is_partition(RelationGetRelid(rel)) && !recursing)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot add column to a partition")));
+
attrdesc = heap_open(AttributeRelationId, RowExclusiveLock);
/*
@@ -5295,6 +5374,7 @@ ATExecDropNotNull(Relation rel, const char *colName, LOCKMODE lockmode)
List *indexoidlist;
ListCell *indexoidscan;
ObjectAddress address;
+ Oid relid;
/*
* lookup the attribute
@@ -5362,6 +5442,28 @@ ATExecDropNotNull(Relation rel, const char *colName, LOCKMODE lockmode)
list_free(indexoidlist);
/*
+ * If rel is partition, throw error if the attribute is not null in
+ * parent
+ */
+ relid = RelationGetRelid(rel);
+ if (relid_is_partition(relid))
+ {
+ Oid parentId = get_partition_parent(relid);
+ Relation parent = heap_open(parentId, AccessShareLock);
+ TupleDesc tupDesc = RelationGetDescr(parent);
+ AttrNumber parent_attnum;
+
+ parent_attnum = get_attnum(parentId, colName);
+ if (tupDesc->attrs[parent_attnum - 1]->attnotnull)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("column \"%s\" is not null in parent",
+ colName),
+ errhint("Please drop not null in the parent instead")));
+ heap_close(parent, AccessShareLock);
+ }
+
+ /*
* Okay, actually perform the catalog change ... if needed
*/
if (((Form_pg_attribute) GETSTRUCT(tuple))->attnotnull)
@@ -5891,6 +5993,11 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
if (recursing)
ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
+ if (relid_is_partition(RelationGetRelid(rel)) && !recursing)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot drop column from a partition")));
+
/*
* get the number of the attribute
*/
@@ -5936,11 +6043,13 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
if (!is_expr)
ereport(ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
- errmsg("cannot drop column named in partition key")));
+ errmsg("cannot drop column named in%s partition key",
+ recursing ? " downstream" : "")));
else
ereport(ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
- errmsg("cannot drop column referenced in partition key expression")));
+ errmsg("cannot drop column referenced in%s partition key expression",
+ recursing ? " downstream" : "")));
}
ReleaseSysCache(tuple);
@@ -6374,8 +6483,9 @@ ATAddCheckConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
/*
* If adding a NO INHERIT constraint, no need to find our children.
+ * However, that applies only to regular inheritance parents.
*/
- if (constr->is_no_inherit)
+ if (constr->is_no_inherit && !relid_is_partitioned(RelationGetRelid(rel)))
return address;
/*
@@ -7936,9 +8046,12 @@ ATExecDropConstraint(Relation rel, const char *constrName,
/*
* Propagate to children as appropriate. Unlike most other ALTER
* routines, we have to do this one level of recursion at a time; we can't
- * use find_all_inheritors to do it in one pass.
+ * use find_all_inheritors to do it in one pass. Note that if the parent
+ * is a partitioned table, we propagate to children (partitions) despite
+ * is_no_inherit_constraint.
*/
- if (!is_no_inherit_constraint)
+ if (!is_no_inherit_constraint ||
+ relid_is_partitioned(RelationGetRelid(rel)))
children = find_inheritance_children(RelationGetRelid(rel), lockmode);
else
children = NIL;
@@ -8068,6 +8181,11 @@ ATPrepAlterColumnType(List **wqueue,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot alter column type of typed table")));
+ if (relid_is_partition(RelationGetRelid(rel)) && !recursing)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot alter column type of a partition")));
+
/* lookup the attribute so we can check inheritance status */
tuple = SearchSysCacheAttName(RelationGetRelid(rel), colName);
if (!HeapTupleIsValid(tuple))
@@ -8098,11 +8216,13 @@ ATPrepAlterColumnType(List **wqueue,
if (!is_expr)
ereport(ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
- errmsg("cannot alter column named in partition key")));
+ errmsg("cannot alter column named in%s partition key",
+ recursing ? " downstream" : "")));
else
ereport(ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
- errmsg("cannot alter column referenced in partition key expression")));
+ errmsg("cannot alter column referenced in%s partition key expression",
+ recursing ? " downstream" : "")));
}
/* Look up the target type */
@@ -10235,6 +10355,11 @@ ATPrepAddInherit(Relation child_rel)
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot change inheritance of typed table")));
+ if (relid_is_partition(RelationGetRelid(child_rel)))
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot change inheritance of a partition")));
+
if (child_rel->rd_rel->relkind == RELKIND_PARTITIONED_REL)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
@@ -10291,6 +10416,11 @@ ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode)
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot inherit from partitioned table \"%s\"", parent->relname)));
+ if (relid_is_partition(RelationGetRelid(parent_rel)))
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot inherit from a partition")));
+
/*
* Prevent circularity by seeing if proposed parent inherits from child.
* (In particular, this disallows making a rel inherit from itself.)
@@ -10677,6 +10807,11 @@ ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
ObjectAddress address;
Relation parent_rel;
+ if (relid_is_partition(RelationGetRelid(rel)))
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot change inheritance of a partition")));
+
/*
* AccessShareLock on the parent is probably enough, seeing that DROP
* TABLE doesn't lock parent tables at all. We need some lock since we'll
@@ -12566,6 +12701,12 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
*/
ATSimplePermissions(attachRel, ATT_TABLE);
+ if (relid_is_partition(RelationGetRelid(attachRel)))
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("\"%s\" is already a partition",
+ RelationGetRelationName(attachRel))));
+
if (attachRel->rd_rel->reloftype)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
@@ -12599,6 +12740,12 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
errmsg("cannot attach a temporary relation of another session as partition ")));
/* If parent has OIDs, attachRel must have OIDs */
+ if (attachRel->rd_rel->reloftype)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot attach a typed table as partition")));
+
+ /* If parent has OIDs then child must have OIDs */
if (rel->rd_rel->relhasoids && !attachRel->rd_rel->relhasoids)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
@@ -12641,6 +12788,30 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
/* OK to create inheritance. Rest of the checks performed there */
CreateInheritance(attachRel, rel);
+ /*
+ * Set up to have the rows in table to be checked for violation of the
+ * partition bound spec in phase 3 scan.
+ */
+ if (!cmd->skip_validate)
+ {
+ AlteredTableInfo *tab;
+ PartitionCheck *check;
+ Expr *my_expr,
+ *parent_expr = (Expr *) RelationGetPartitionCheckExpr(rel);
+
+ tab = ATGetQueueEntry(wqueue, attachRel);
+ check = (PartitionCheck *) palloc0(sizeof(PartitionCheck));
+ my_expr = get_check_expr_from_partbound(attachRel, rel, cmd->bound);
+ check->expr = parent_expr ? makeBoolExpr(AND_EXPR,
+ list_make2(parent_expr, my_expr), -1)
+ : my_expr;
+ tab->partitionCheck = check;
+ }
+
+ /* Store bound into the catalog */
+ StorePartitionBound(RelationGetRelid(attachRel), RelationGetRelid(rel),
+ cmd->bound);
+
ObjectAddressSet(address, RelationRelationId, RelationGetRelid(attachRel));
/* keep our lock until commit */
@@ -12665,6 +12836,15 @@ ATExecDetachPartition(Relation rel, RangeVar *name)
/* All inheritance related checks are performed within the function */
RemoveInheritance(partRel, rel);
+ /* Remove the pg_partition entry */
+ RemovePartitionEntryByRelId(RelationGetRelid(partRel));
+
+ /*
+ * Invalidate the relcache so that the partition is no longer included
+ * in our partition descriptor.
+ */
+ CacheInvalidateRelcache(rel);
+
ObjectAddressSet(address, RelationRelationId, RelationGetRelid(partRel));
/* keep our lock until commit */
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 67492a5..23ed0b6 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -42,6 +42,7 @@
#include "access/transam.h"
#include "access/xact.h"
#include "catalog/namespace.h"
+#include "catalog/partition.h"
#include "commands/matview.h"
#include "commands/trigger.h"
#include "executor/execdebug.h"
@@ -1250,6 +1251,8 @@ InitResultRelInfo(ResultRelInfo *resultRelInfo,
resultRelInfo->ri_ConstraintExprs = NULL;
resultRelInfo->ri_junkFilter = NULL;
resultRelInfo->ri_projectReturning = NULL;
+ resultRelInfo->ri_PartitionCheck = (Expr *)
+ RelationGetPartitionCheckExpr(resultRelationDesc);
}
/*
@@ -1683,6 +1686,49 @@ ExecRelCheck(ResultRelInfo *resultRelInfo,
return NULL;
}
+/*
+ * ExecPartitionCheck --- check that tuple meets the partition boundary
+ * specification.
+ *
+ * Note: This is called, *iff* resultRelInfo is the main target table.
+ */
+static bool
+ExecPartitionCheck(ResultRelInfo *resultRelInfo, TupleTableSlot *slot,
+ EState *estate)
+{
+ ExprContext *econtext;
+
+ /*
+ * If first time through, build expression state tree for the partition
+ * check expression. Keep it in the per-query memory context so they'll
+ * survive throughout the query.
+ */
+ if (resultRelInfo->ri_PartitionCheckExpr == NULL)
+ {
+ List *qual = make_ands_implicit(resultRelInfo->ri_PartitionCheck);
+ resultRelInfo->ri_PartitionCheckExpr = (List *)
+ ExecPrepareExpr((Expr *) qual, estate);
+ }
+
+ /*
+ * We will use the EState's per-tuple context for evaluating constraint
+ * expressions (creating it if it's not already there).
+ */
+ econtext = GetPerTupleExprContext(estate);
+
+ /* Arrange for econtext's scan tuple to be the tuple under test */
+ econtext->ecxt_scantuple = slot;
+
+ /*
+ * NOTE: SQL specifies that a NULL result from a constraint expression
+ * is not to be treated as a failure. Therefore, tell ExecQual to
+ * return TRUE for NULL.
+ *
+ * XXX - although, it's unlikely that NULL would result.
+ */
+ return ExecQual(resultRelInfo->ri_PartitionCheckExpr, econtext, true);
+}
+
void
ExecConstraints(ResultRelInfo *resultRelInfo,
TupleTableSlot *slot, EState *estate)
@@ -1694,9 +1740,9 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
Bitmapset *insertedCols;
Bitmapset *updatedCols;
- Assert(constr);
+ Assert(constr || resultRelInfo->ri_PartitionCheck);
- if (constr->has_not_null)
+ if (constr && constr->has_not_null)
{
int natts = tupdesc->natts;
int attrChk;
@@ -1727,7 +1773,7 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
}
}
- if (constr->num_check > 0)
+ if (constr && constr->num_check > 0)
{
const char *failed;
@@ -1751,6 +1797,29 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
errtableconstraint(rel, failed)));
}
}
+
+ if (resultRelInfo->ri_PartitionCheck)
+ {
+ if (!ExecPartitionCheck(resultRelInfo, slot, estate))
+ {
+ char *val_desc;
+
+ insertedCols = GetInsertedColumns(resultRelInfo, estate);
+ updatedCols = GetUpdatedColumns(resultRelInfo, estate);
+ modifiedCols = bms_union(insertedCols, updatedCols);
+ val_desc = ExecBuildSlotValueDescription(RelationGetRelid(rel),
+ slot,
+ tupdesc,
+ modifiedCols,
+ 64);
+ ereport(ERROR,
+ (errcode(ERRCODE_CHECK_VIOLATION),
+ errmsg("new row violates the partition boundary"
+ " specification of \"%s\"",
+ RelationGetRelationName(rel)),
+ val_desc ? errdetail("Failing row contains %s.", val_desc) : 0));
+ }
+ }
}
/*
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 7ad1140..6ecb6be 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -354,7 +354,7 @@ ExecInsert(ModifyTableState *mtstate,
/*
* Check the constraints of the tuple
*/
- if (resultRelationDesc->rd_att->constr)
+ if (resultRelationDesc->rd_att->constr || resultRelInfo->ri_PartitionCheck)
ExecConstraints(resultRelInfo, slot, estate);
if (onconflict != ONCONFLICT_NONE && resultRelInfo->ri_NumIndices > 0)
@@ -907,7 +907,7 @@ lreplace:;
/*
* Check the constraints of the tuple
*/
- if (resultRelationDesc->rd_att->constr)
+ if (resultRelationDesc->rd_att->constr || resultRelInfo->ri_PartitionCheck)
ExecConstraints(resultRelInfo, slot, estate);
/*
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 6f28047..1ea1439 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -2249,6 +2249,35 @@ _readExtensibleNode(void)
}
/*
+ * _readPartitionListSpec
+ */
+static PartitionListSpec *
+_readPartitionListSpec(void)
+{
+ READ_LOCALS(PartitionListSpec);
+
+ READ_NODE_FIELD(values);
+
+ READ_DONE();
+}
+
+/*
+ * _readPartitionRangeSpec
+ */
+static PartitionRangeSpec *
+_readPartitionRangeSpec(void)
+{
+ READ_LOCALS(PartitionRangeSpec);
+
+ READ_BOOL_FIELD(lowerinc);
+ READ_NODE_FIELD(lower);
+ READ_BOOL_FIELD(upperinc);
+ READ_NODE_FIELD(upper);
+
+ READ_DONE();
+}
+
+/*
* parseNodeString
*
* Given a character string representing a node tree, parseNodeString creates
@@ -2478,6 +2507,10 @@ parseNodeString(void)
return_value = _readAlternativeSubPlan();
else if (MATCH("EXTENSIBLENODE", 14))
return_value = _readExtensibleNode();
+ else if (MATCH("PARTITIONLISTVALUES", 19))
+ return_value = _readPartitionListSpec();
+ else if (MATCH("PARTITIONRANGE", 14))
+ return_value = _readPartitionRangeSpec();
else
{
elog(ERROR, "badly formatted node string \"%.32s\"...", token);
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index 9c11b09..ed3193b 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -27,6 +27,7 @@
#include "catalog/catalog.h"
#include "catalog/dependency.h"
#include "catalog/heap.h"
+#include "catalog/partition.h"
#include "catalog/pg_am.h"
#include "catalog/pg_constraint.h"
#include "foreign/fdwapi.h"
@@ -1124,6 +1125,7 @@ get_relation_constraints(PlannerInfo *root,
Index varno = rel->relid;
Relation relation;
TupleConstr *constr;
+ Node *pexpr;
/*
* We assume the relation has already been safely locked.
@@ -1203,6 +1205,17 @@ get_relation_constraints(PlannerInfo *root,
}
}
+ /* Append partition check constraint, if any */
+ pexpr = RelationGetPartitionCheckExpr(relation);
+ if (pexpr)
+ {
+ /* Fix Vars to have the desired varno */
+ if (varno != 1)
+ ChangeVarNodes(pexpr, 1, varno, 0);
+
+ result = list_concat(result, make_ands_implicit((Expr *) pexpr));
+ }
+
heap_close(relation, NoLock);
return result;
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 7d2fedf..f55129a 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -25,6 +25,7 @@
#include "postgres.h"
#include "access/sysattr.h"
+#include "catalog/partition.h"
#include "catalog/pg_type.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
diff --git a/src/backend/parser/parse_clause.c b/src/backend/parser/parse_clause.c
index 307e383..5124dbf 100644
--- a/src/backend/parser/parse_clause.c
+++ b/src/backend/parser/parse_clause.c
@@ -202,8 +202,8 @@ setTargetTable(ParseState *pstate, RangeVar *relation,
pstate->p_target_rangetblentry = rte;
/*
- * Override specified inheritance option, if relation is a partitioned
- * table and it's a target of INSERT (ie, alsoSource == false).
+ * For UPDATE/DELETE, force inheritance recursion, if partitioned
+ * table.
*/
if (alsoSource)
rte->inh |= relid_is_partitioned(rte->relid);
diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c
index abcc1d6..b3870f6 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -1218,8 +1218,7 @@ addRangeTableEntry(ParseState *pstate,
* The initial default on access checks is always check-for-READ-access,
* which is the right thing for all except target tables.
*
- * Note: we override the specified inheritance option, if relation is a
- * partitioned table.
+ * Note: We force inheritance recursion, if partitioned table.
*/
rte->lateral = false;
rte->inh = inh || relid_is_partitioned(RelationGetRelid(rel));
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index c0a1bb0..d2273c6 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -32,6 +32,7 @@
#include "catalog/dependency.h"
#include "catalog/heap.h"
#include "catalog/index.h"
+#include "catalog/partition.h"
#include "catalog/namespace.h"
#include "catalog/partition.h"
#include "catalog/pg_am.h"
@@ -3105,6 +3106,11 @@ transformPartitionOf(CreateStmtContext *cxt, Node *bound)
if (parentRel->rd_rel->relhasoids)
cxt->hasoids = true;
+ /*
+ * Create ColumnDefs correponding to parent's attributes as "inherited"
+ * attributes. Also, set is_for_partition whose function is similar to
+ * is_from_type for MergeAttributes()'s purposes.
+ */
tupdesc = RelationGetDescr(parentRel);
for (i = 0; i < tupdesc->natts; i++)
{
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 167c856..b3d9cc2 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -8221,6 +8221,84 @@ get_rule_expr(Node *node, deparse_context *context,
}
break;
+ case T_PartitionListSpec:
+ {
+ PartitionListSpec *list_spec = (PartitionListSpec *) node;
+ ListCell *cell;
+ char *sep;
+
+ appendStringInfoString(buf, "FOR VALUES");
+
+ appendStringInfoString(buf, " IN (");
+ sep = "";
+ foreach (cell, list_spec->values)
+ {
+ Const *val = lfirst(cell);
+
+ appendStringInfoString(buf, sep);
+ get_const_expr(val, context, -1);
+ sep = ", ";
+ }
+
+ appendStringInfoString(buf, ")");
+ }
+ break;
+
+ case T_PartitionRangeSpec:
+ {
+ PartitionRangeSpec *range_spec = (PartitionRangeSpec *) node;
+ ListCell *cell;
+ char *sep;
+
+ appendStringInfoString(buf, "FOR VALUES");
+
+ appendStringInfoString(buf, " START");
+ if (!range_spec->lower)
+ appendStringInfoString(buf, " UNBOUNDED");
+ else
+ {
+ appendStringInfoString(buf, " (");
+
+ sep = "";
+ foreach (cell, range_spec->lower)
+ {
+ Const *val = lfirst(cell);
+
+ appendStringInfoString(buf, sep);
+ get_const_expr(val, context, -1);
+ sep = ", ";
+ }
+ appendStringInfoString(buf, ")");
+
+ if (!range_spec->lowerinc)
+ appendStringInfoString(buf, " EXCLUSIVE");
+ }
+
+ appendStringInfoString(buf, " END");
+
+ if (!range_spec->upper)
+ appendStringInfoString(buf, " UNBOUNDED");
+ else
+ {
+ appendStringInfoString(buf, " (");
+
+ sep = "";
+ foreach (cell, range_spec->upper)
+ {
+ Const *val = lfirst(cell);
+
+ appendStringInfoString(buf, sep);
+ get_const_expr(val, context, -1);
+ sep = ", ";
+ }
+ appendStringInfoString(buf, ")");
+
+ if (range_spec->upperinc)
+ appendStringInfoString(buf, " INCLUSIVE");
+ }
+ }
+ break;
+
case T_List:
{
char *sep;
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index e48a1f4..36a2dbf 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -50,6 +50,7 @@
#include "catalog/pg_database.h"
#include "catalog/pg_namespace.h"
#include "catalog/pg_opclass.h"
+#include "catalog/partition.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_rewrite.h"
#include "catalog/pg_shseclabel.h"
@@ -2055,6 +2056,10 @@ RelationDestroyRelation(Relation relation, bool remember_tupdesc)
if (relation->rd_rsdesc)
MemoryContextDelete(relation->rd_rsdesc->rscxt);
FreePartitionKey(relation->rd_partkey);
+ if (relation->rd_pdcxt)
+ MemoryContextDelete(relation->rd_pdcxt);
+ if (relation->rd_partcheck)
+ pfree(relation->rd_partcheck);
if (relation->rd_fdwroutine)
pfree(relation->rd_fdwroutine);
pfree(relation);
@@ -4984,6 +4989,8 @@ load_relcache_init_file(bool shared)
rel->trigdesc = NULL;
rel->rd_rsdesc = NULL;
rel->rd_partkey = NULL;
+ rel->rd_partdesc = NULL;
+ rel->rd_partcheck = NULL;
rel->rd_indexprs = NIL;
rel->rd_indpred = NIL;
rel->rd_exclops = NULL;
diff --git a/src/backend/utils/cache/syscache.c b/src/backend/utils/cache/syscache.c
index a6563fe..4a5783a 100644
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -49,6 +49,7 @@
#include "catalog/pg_operator.h"
#include "catalog/pg_opfamily.h"
#include "catalog/pg_partitioned.h"
+#include "catalog/pg_partition.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_range.h"
#include "catalog/pg_rewrite.h"
@@ -569,6 +570,17 @@ static const struct cachedesc cacheinfo[] = {
},
8
},
+ {PartitionRelationId, /* PARTRELID */
+ PartitionRelidIndexId,
+ 1,
+ {
+ Anum_pg_partition_partrelid,
+ 0,
+ 0,
+ 0
+ },
+ 128
+ },
{PartitionedRelationId, /* PARTEDRELID */
PartitionedRelidIndexId,
1,
diff --git a/src/bin/pg_dump/common.c b/src/bin/pg_dump/common.c
index 1cbb987..c8e56bd 100644
--- a/src/bin/pg_dump/common.c
+++ b/src/bin/pg_dump/common.c
@@ -68,6 +68,8 @@ static int numextmembers;
static void flagInhTables(TableInfo *tbinfo, int numTables,
InhInfo *inhinfo, int numInherits);
+static void flagPartitions(TableInfo *tblinfo, int numTables,
+ PartInfo *partinfo, int numPartitions);
static void flagInhAttrs(DumpOptions *dopt, TableInfo *tblinfo, int numTables);
static DumpableObject **buildIndexArray(void *objArray, int numObjs,
Size objSize);
@@ -75,6 +77,8 @@ static int DOCatalogIdCompare(const void *p1, const void *p2);
static int ExtensionMemberIdCompare(const void *p1, const void *p2);
static void findParentsByOid(TableInfo *self,
InhInfo *inhinfo, int numInherits);
+static void findPartitionParentByOid(TableInfo *self, PartInfo *partinfo,
+ int numPartitions);
static int strInArray(const char *pattern, char **arr, int arr_size);
@@ -93,8 +97,10 @@ getSchemaData(Archive *fout, int *numTablesPtr)
NamespaceInfo *nspinfo;
ExtensionInfo *extinfo;
InhInfo *inhinfo;
+ PartInfo *partinfo;
int numAggregates;
int numInherits;
+ int numPartitions;
int numRules;
int numProcLangs;
int numCasts;
@@ -232,6 +238,10 @@ getSchemaData(Archive *fout, int *numTablesPtr)
inhinfo = getInherits(fout, &numInherits);
if (g_verbose)
+ write_msg(NULL, "reading partition information\n");
+ partinfo = getPartitions(fout, &numPartitions);
+
+ if (g_verbose)
write_msg(NULL, "reading event triggers\n");
getEventTriggers(fout, &numEventTriggers);
@@ -245,6 +255,11 @@ getSchemaData(Archive *fout, int *numTablesPtr)
write_msg(NULL, "finding inheritance relationships\n");
flagInhTables(tblinfo, numTables, inhinfo, numInherits);
+ /* Link tables to partition parents, mark parents as interesting */
+ if (g_verbose)
+ write_msg(NULL, "finding partition relationships\n");
+ flagPartitions(tblinfo, numTables, partinfo, numPartitions);
+
if (g_verbose)
write_msg(NULL, "reading column info for interesting tables\n");
getTableAttrs(fout, tblinfo, numTables);
@@ -319,6 +334,43 @@ flagInhTables(TableInfo *tblinfo, int numTables,
}
}
+/* flagPartitions -
+ * Fill in parent link fields of every target table that is partition,
+ * and mark parents of partitions as interesting
+ *
+ * modifies tblinfo
+ */
+static void
+flagPartitions(TableInfo *tblinfo, int numTables,
+ PartInfo *partinfo, int numPartitions)
+{
+ int i;
+
+ for (i = 0; i < numTables; i++)
+ {
+ /* Some kinds are never partitions */
+ if (tblinfo[i].relkind == RELKIND_SEQUENCE ||
+ tblinfo[i].relkind == RELKIND_VIEW ||
+ tblinfo[i].relkind == RELKIND_MATVIEW)
+ continue;
+
+ /* Don't bother computing anything for non-target tables, either */
+ if (!tblinfo[i].dobj.dump)
+ continue;
+
+ /* Find the parent TableInfo and save */
+ findPartitionParentByOid(&tblinfo[i], partinfo, numPartitions);
+
+ /* Mark the parent as interesting for getTableAttrs */
+ if (tblinfo[i].partitionOf)
+ {
+ tblinfo[i].partitionOf->interesting = true;
+ addObjectDependency(&tblinfo[i].dobj,
+ tblinfo[i].partitionOf->dobj.dumpId);
+ }
+ }
+}
+
/* flagInhAttrs -
* for each dumpable table in tblinfo, flag its inherited attributes
*
@@ -920,6 +972,40 @@ findParentsByOid(TableInfo *self,
}
/*
+ * findPartitionParentByOid
+ * find a partition's parent in tblinfo[]
+ */
+static void
+findPartitionParentByOid(TableInfo *self, PartInfo *partinfo,
+ int numPartitions)
+{
+ Oid oid = self->dobj.catId.oid;
+ int i;
+
+ for (i = 0; i < numPartitions; i++)
+ {
+ if (partinfo[i].partrelid == oid)
+ {
+ TableInfo *parent;
+
+ parent = findTableByOid(partinfo[i].partparent);
+ if (parent == NULL)
+ {
+ write_msg(NULL, "failed sanity check, parent OID %u of table \"%s\" (OID %u) not found\n",
+ partinfo[i].partparent,
+ self->dobj.name,
+ oid);
+ exit_nicely(1);
+ }
+ self->partitionOf = parent;
+
+ /* While we're at it, also save the partdef */
+ self->partitiondef = partinfo[i].partdef;
+ }
+ }
+}
+
+/*
* parseOidArray
* parse a string of numbers delimited by spaces into a character array
*
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 163d013..06c8c23 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -6085,6 +6085,63 @@ getInherits(Archive *fout, int *numInherits)
}
/*
+ * getPartitions
+ * read all the partition inheritance and partition bound information
+ * from the system catalogs return them in the PartInfo* structure
+ *
+ * numPartitions is set to the number of pairs read in
+ */
+PartInfo *
+getPartitions(Archive *fout, int *numPartitions)
+{
+ PGresult *res;
+ int ntups;
+ int i;
+ PQExpBuffer query = createPQExpBuffer();
+ PartInfo *partinfo;
+
+ int i_partrelid;
+ int i_partparent;
+ int i_partbound;
+
+ /* Make sure we are in proper schema */
+ selectSourceSchema(fout, "pg_catalog");
+
+ /* find all the inheritance information */
+
+ appendPQExpBufferStr(query,
+ "SELECT partrelid, inhparent AS partparent, "
+ " pg_get_expr(partbound, partrelid) AS partbound "
+ "FROM pg_partition, pg_inherits "
+ "WHERE partrelid = inhrelid");
+
+ res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
+
+ ntups = PQntuples(res);
+
+ *numPartitions = ntups;
+
+ partinfo = (PartInfo *) pg_malloc(ntups * sizeof(PartInfo));
+
+ i_partrelid = PQfnumber(res, "partrelid");
+ i_partparent = PQfnumber(res, "partparent");
+ i_partbound = PQfnumber(res, "partbound");
+
+ for (i = 0; i < ntups; i++)
+ {
+ partinfo[i].partrelid = atooid(PQgetvalue(res, i, i_partrelid));
+ partinfo[i].partparent = atooid(PQgetvalue(res, i, i_partparent));
+ partinfo[i].partdef = pg_strdup(PQgetvalue(res, i, i_partbound));
+ }
+
+ PQclear(res);
+
+ destroyPQExpBuffer(query);
+
+ return partinfo;
+}
+
+/*
* getIndexes
* get information about every index on a dumpable table
*
@@ -15278,6 +15335,17 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
if (tbinfo->reloftype && !dopt->binary_upgrade)
appendPQExpBuffer(q, " OF %s", tbinfo->reloftype);
+ if (tbinfo->partitionOf && !dopt->binary_upgrade)
+ {
+ TableInfo *parentRel = tbinfo->partitionOf;
+
+ appendPQExpBuffer(q, " PARTITION OF ");
+ if (parentRel->dobj.namespace != tbinfo->dobj.namespace)
+ appendPQExpBuffer(q, "%s.",
+ fmtId(parentRel->dobj.namespace->dobj.name));
+ appendPQExpBufferStr(q, fmtId(parentRel->dobj.name));
+ }
+
if (tbinfo->relkind != RELKIND_MATVIEW)
{
/* Dump the attributes */
@@ -15306,8 +15374,11 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
(!tbinfo->inhNotNull[j] ||
dopt->binary_upgrade));
- /* Skip column if fully defined by reloftype */
- if (tbinfo->reloftype &&
+ /*
+ * Skip column if fully defined by reloftype or the
+ * partition parent.
+ */
+ if ((tbinfo->reloftype || tbinfo->partitionOf) &&
!has_default && !has_notnull && !dopt->binary_upgrade)
continue;
@@ -15336,7 +15407,8 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
}
/* Attribute type */
- if (tbinfo->reloftype && !dopt->binary_upgrade)
+ if ((tbinfo->reloftype || tbinfo->partitionOf) &&
+ !dopt->binary_upgrade)
{
appendPQExpBufferStr(q, " WITH OPTIONS");
}
@@ -15401,15 +15473,22 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
if (actual_atts)
appendPQExpBufferStr(q, "\n)");
- else if (!(tbinfo->reloftype && !dopt->binary_upgrade))
+ else if (!((tbinfo->reloftype || tbinfo->partitionOf) &&
+ !dopt->binary_upgrade))
{
/*
* We must have a parenthesized attribute list, even though
- * empty, when not using the OF TYPE syntax.
+ * empty, when not using the OF TYPE or PARTITION OF syntax.
*/
appendPQExpBufferStr(q, " (\n)");
}
+ if (tbinfo->partitiondef && !dopt->binary_upgrade)
+ {
+ appendPQExpBufferStr(q, "\n");
+ appendPQExpBufferStr(q, tbinfo->partitiondef);
+ }
+
if (numParents > 0 && !dopt->binary_upgrade)
{
appendPQExpBufferStr(q, "\nINHERITS (");
@@ -15579,6 +15658,15 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
tbinfo->reloftype);
}
+ if (tbinfo->partitionOf)
+ {
+ appendPQExpBufferStr(q, "\n-- For binary upgrade, set up partitions this way.\n");
+ appendPQExpBuffer(q, "ALTER TABLE ONLY %s ATTACH PARTITION %s %s;\n",
+ fmtId(tbinfo->partitionOf->dobj.name),
+ tbinfo->dobj.name,
+ tbinfo->partitiondef);
+ }
+
appendPQExpBufferStr(q, "\n-- For binary upgrade, set heap's relfrozenxid and relminmxid\n");
appendPQExpBuffer(q, "UPDATE pg_catalog.pg_class\n"
"SET relfrozenxid = '%u', relminmxid = '%u'\n"
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 0292859..760067a 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -320,6 +320,8 @@ typedef struct _tableInfo
struct _tableDataInfo *dataObj; /* TableDataInfo, if dumping its data */
int numTriggers; /* number of triggers for table */
struct _triggerInfo *triggers; /* array of TriggerInfo structs */
+ struct _tableInfo *partitionOf; /* TableInfo for the partition parent */
+ char *partitiondef; /* partition key definition */
} TableInfo;
typedef struct _attrDefInfo
@@ -460,6 +462,15 @@ typedef struct _inhInfo
Oid inhparent; /* OID of its parent */
} InhInfo;
+/* PartInfo isn't a DumpableObject, just temporary state */
+typedef struct _partInfo
+{
+ Oid partrelid; /* OID of a partition */
+ Oid partparent; /* OID of its parent */
+ char *partdef; /* partition bound definition */
+} PartInfo;
+
+
typedef struct _prsInfo
{
DumpableObject dobj;
@@ -626,6 +637,7 @@ extern ConvInfo *getConversions(Archive *fout, int *numConversions);
extern TableInfo *getTables(Archive *fout, int *numTables);
extern void getOwnedSeqs(Archive *fout, TableInfo tblinfo[], int numTables);
extern InhInfo *getInherits(Archive *fout, int *numInherits);
+extern PartInfo *getPartitions(Archive *fout, int *numPartitions);
extern void getIndexes(Archive *fout, TableInfo tblinfo[], int numTables);
extern void getConstraints(Archive *fout, TableInfo tblinfo[], int numTables);
extern RuleInfo *getRules(Archive *fout, int *numRules);
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 78a1fdb..6bbfc9a 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -1694,6 +1694,34 @@ describeOneTableDetails(const char *schemaname,
}
/* Make footers */
+ if (pset.sversion >= 90600)
+ {
+ /* Get the partition information */
+ PGresult *result;
+ char *parent_name;
+ char *partdef;
+
+ printfPQExpBuffer(&buf,
+ "SELECT inhparent::pg_catalog.regclass, pg_get_expr(partbound, partrelid)"
+ " FROM pg_catalog.pg_partition p"
+ " JOIN pg_catalog.pg_inherits i"
+ " ON p.partrelid = i.inhrelid"
+ " WHERE p.partrelid = '%s';", oid);
+ result = PSQLexec(buf.data);
+ if (!result)
+ goto error_return;
+
+ if (PQntuples(result) > 0)
+ {
+ parent_name = PQgetvalue(result, 0, 0);
+ partdef = PQgetvalue(result, 0, 1);
+ printfPQExpBuffer(&tmpbuf, _("Partition Of: %s %s"), parent_name,
+ partdef);
+ printTableAddFooter(&cont, tmpbuf.data);
+ PQclear(result);
+ }
+ }
+
if (pset.sversion >= 90600 && tableinfo.relkind == 'P')
{
/* Get the partition key information */
@@ -2455,8 +2483,12 @@ describeOneTableDetails(const char *schemaname,
PQclear(result);
}
- /* print inherited tables */
- printfPQExpBuffer(&buf, "SELECT c.oid::pg_catalog.regclass FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i WHERE c.oid=i.inhparent AND i.inhrelid = '%s' ORDER BY inhseqno;", oid);
+ /* print inherited tables (exclude, if parent is a partitioned table) */
+ printfPQExpBuffer(&buf,
+ "SELECT c.oid::pg_catalog.regclass"
+ " FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i"
+ " WHERE c.oid=i.inhparent AND i.inhrelid = '%s'"
+ " AND c.relkind != 'P' ORDER BY inhseqno;", oid);
result = PSQLexec(buf.data);
if (!result)
@@ -2485,9 +2517,15 @@ describeOneTableDetails(const char *schemaname,
PQclear(result);
}
- /* print child tables */
+ /* print child tables (exclude, if parent is a partitioned table) */
if (pset.sversion >= 80300)
- printfPQExpBuffer(&buf, "SELECT c.oid::pg_catalog.regclass FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i WHERE c.oid=i.inhrelid AND i.inhparent = '%s' ORDER BY c.oid::pg_catalog.regclass::pg_catalog.text;", oid);
+ printfPQExpBuffer(&buf,
+ "SELECT c.oid::pg_catalog.regclass"
+ " FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i"
+ " WHERE c.oid=i.inhrelid AND"
+ " i.inhparent = '%s' AND"
+ " EXISTS (SELECT 1 FROM pg_class c WHERE c.oid = '%s' AND c.relkind != 'P')"
+ " ORDER BY c.oid::pg_catalog.regclass::pg_catalog.text;", oid, oid);
else
printfPQExpBuffer(&buf, "SELECT c.oid::pg_catalog.regclass FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i WHERE c.oid=i.inhrelid AND i.inhparent = '%s' ORDER BY c.relname;", oid);
diff --git a/src/include/catalog/indexing.h b/src/include/catalog/indexing.h
index cadb741..73786c0 100644
--- a/src/include/catalog/indexing.h
+++ b/src/include/catalog/indexing.h
@@ -322,6 +322,9 @@ DECLARE_UNIQUE_INDEX(pg_replication_origin_roname_index, 6002, on pg_replication
DECLARE_UNIQUE_INDEX(pg_partitioned_partedrelid_index, 3351, on pg_partitioned using btree(partedrelid oid_ops));
#define PartitionedRelidIndexId 3351
+DECLARE_UNIQUE_INDEX(pg_partition_partrelid_index, 3354, on pg_partition using btree(partrelid oid_ops));
+#define PartitionRelidIndexId 3354
+
/* last step of initialization script: build the indexes declared above */
BUILD_INDICES
diff --git a/src/include/catalog/partition.h b/src/include/catalog/partition.h
index 31dbe17..6204ad2 100644
--- a/src/include/catalog/partition.h
+++ b/src/include/catalog/partition.h
@@ -42,6 +42,53 @@ typedef struct PartitionKeyData
typedef struct PartitionKeyData *PartitionKey;
+/* Internal representation of a list partition bound */
+typedef struct PartitionListBound
+{
+ bool contains_null;
+ int nvalues; /* number of values in the following array */
+ Datum *values; /* values contained in the list */
+} PartitionListBound;
+
+/* Internal representation of a range partition bound */
+typedef struct
+{
+ Datum *val; /* composite bound value, if any */
+ bool infinite; /* bound is +/- infinity */
+ bool inclusive; /* bound is inclusive (vs exclusive) */
+ bool lower; /* this is the lower (vs upper) bound */
+} PartitionRangeBound;
+
+typedef struct PartitionDescData *PartitionDesc;
+
+/*
+ * Partition tree node
+ *
+ * pkinfo PartitionKey executor state
+ * pdesc info about immediate partitions (see
+ PartitionDescData)
+ * index index within 0..(parent_pdesc->nparts - 1)
+ * num_leaf_partitions number of leaf partitions in the partition
+ * tree rooted at this node
+ * offset index (0-based) of the first leaf partition
+ * in the partition tree rooted at this node
+ * downlink link to the leftmost child node
+ * next link to the right sibling node on a given level
+ */
+typedef struct PartitionDescNodeData
+{
+ PartitionDesc pdesc;
+ Oid relid;
+ int index;
+ int offset;
+ int num_leaf_partitions;
+
+ struct PartitionDescNodeData *downlink;
+ struct PartitionDescNodeData *next;
+} PartitionDescNodeData;
+
+typedef struct PartitionDescNodeData *PartitionDescNode;
+
extern void StorePartitionKey(Relation rel,
char strategy,
int16 partnatts,
@@ -53,4 +100,14 @@ extern void RelationBuildPartitionKey(Relation relation);
extern void FreePartitionKey(PartitionKey key);
extern bool relid_is_partitioned(Oid relid);
+extern void StorePartitionBound(Oid relid, Oid parentId, Node *bound);
+extern void RemovePartitionEntryByRelId(Oid relid);
+extern bool relid_is_partition(Oid relid);
+extern Oid get_partition_parent(Oid relid);
+extern void RelationDropPartitions(Oid relid);
+extern Node *RelationGetPartitionCheckExpr(Relation rel);
+extern Expr *get_check_expr_from_partbound(Relation rel, Relation parent,
+ Node *bound);
+extern List *get_leaf_partition_oids(PartitionDescNode pdnode);
+extern PartitionDescNode RelationGetPartitionDescNode(Relation rel);
#endif /* PARTITION_H */
diff --git a/src/include/catalog/pg_partition.h b/src/include/catalog/pg_partition.h
new file mode 100644
index 0000000..d31a6b7
--- /dev/null
+++ b/src/include/catalog/pg_partition.h
@@ -0,0 +1,54 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_partition.h
+ * definition of the system "partition" relation (pg_partition)
+ * along with the relation's initial contents.
+ *
+ *
+ * Portions Copyright (c) 1996-2015, PostgreSQL Global Development Group
+ *
+ * $PostgreSQL: pgsql/src/include/catalog/pg_partition.h $
+ *
+ * NOTES
+ * the genbki.sh script reads this file and generates .bki
+ * information from the DATA() statements.
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_PARTITION_H
+#define PG_PARTITION_H
+
+#include "catalog/genbki.h"
+
+/* ----------------
+ * pg_partitioned_rel definition. cpp turns this into
+ * typedef struct FormData_pg_partitioned_rel
+ * ----------------
+ */
+#define PartitionRelationId 3353
+
+CATALOG(pg_partition,3353) BKI_WITHOUT_OIDS
+{
+ Oid partrelid; /* partition relation OID */
+
+#ifdef CATALOG_VARLEN /* variable-length fields start here */
+ pg_node_tree partbound; /* node representation of partition bound */
+#endif
+} FormData_pg_partition;
+
+/* ----------------
+ * Form_pg_partition corresponds to a pointer to a tuple with
+ * the format of pg_partition relation.
+ * ----------------
+ */
+typedef FormData_pg_partition *Form_pg_partition;
+
+/* ----------------
+ * compiler constants for pg_partition
+ * ----------------
+ */
+#define Natts_pg_partition 2
+#define Anum_pg_partition_partrelid 1
+#define Anum_pg_partition_partbound 2
+
+#endif /* PG_PARTITION_H */
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index ee4e189..74288e2 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -319,6 +319,8 @@ typedef struct JunkFilter
* projectReturning for computing a RETURNING list
* onConflictSetProj for computing ON CONFLICT DO UPDATE SET
* onConflictSetWhere list of ON CONFLICT DO UPDATE exprs (qual)
+ * PartitionCheck partition check expression
+ * PartitionCheckExpr partition check expression state
* ----------------
*/
typedef struct ResultRelInfo
@@ -343,6 +345,8 @@ typedef struct ResultRelInfo
ProjectionInfo *ri_projectReturning;
ProjectionInfo *ri_onConflictSetProj;
List *ri_onConflictSetWhere;
+ Expr *ri_PartitionCheck;
+ List *ri_PartitionCheckExpr;
} ResultRelInfo;
/* ----------------
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 9bc2a31..c77eb2d 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -91,6 +91,10 @@ typedef struct RelationData
struct RowSecurityDesc *rd_rsdesc; /* row security policies, or NULL */
struct PartitionKeyData *rd_partkey; /* partition key, or NULL */
+ MemoryContext rd_pdcxt; /* private context for partdesc */
+ struct PartitionDescData *rd_partdesc; /* partitions, or NULL */
+ Node *rd_partcheck; /* partition CHECK constraint expression
+ * tree */
/* data managed by RelationGetIndexList: */
List *rd_indexlist; /* list of OIDs of indexes on relation */
diff --git a/src/include/utils/syscache.h b/src/include/utils/syscache.h
index e727842..4e493e0 100644
--- a/src/include/utils/syscache.h
+++ b/src/include/utils/syscache.h
@@ -72,6 +72,7 @@ enum SysCacheIdentifier
OPEROID,
OPFAMILYAMNAMENSP,
OPFAMILYOID,
+ PARTRELID,
PARTEDRELID,
PROCNAMEARGSNSP,
PROCOID,
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index b1e473d..57bde9d 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -118,6 +118,7 @@ pg_namespace|t
pg_opclass|t
pg_operator|t
pg_opfamily|t
+pg_partition|t
pg_partitioned|t
pg_pltemplate|t
pg_policy|t
--
1.7.1
[text/x-diff] 0005-Introduce-tuple-routing-for-partitioned-tables-v4.patch (29.2K, ../../5732984F.6090200@lab.ntt.co.jp/6-0005-Introduce-tuple-routing-for-partitioned-tables-v4.patch)
download | inline diff:
From f9474e1d6756bde2cf45da9c5564678e23907980 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Fri, 18 Mar 2016 19:48:49 +0900
Subject: [PATCH 5/6] Introduce tuple routing for partitioned tables.
A tuple inserted into a partitioned table using insert or copy is now
routed to the appropriate leaf partition, recursively.
State required for the same is built in advance by BeginCopy() and
ExecInitModifyTable(). It consists of N ResultRelInfos and pointers
to TupleConversionMap structs, where N is the number of leaf partitions
in the multi-level partition tree. One needs only to get a pointer to the
root PartitionDescNode node for the tree. A tuple conversion map is only
allocated if the tuple descriptor of a leaf partition differs from that
of the root table. Partition check expression is not initialized for
leaf partition result relations, as they are redundant in this case.
Whether or not to build one is now determined by new parameter
load_partition_check of InitResultRelInfo().
CopyFrom() or ExecInsert() passes the root node (set up initially, as
just mentioned) along with the tuple slot to get_partition_for_tuple()
which traverses the tree to determine the appropriate leaf partition for
the tuple. It returns the index (0..N) of the leaf partition. Caller
then switches the result relation (ResultRelInfo) appropriately and
performs the tuple conversion, if required, before following up with
the row triggers, constraints, actual heap and index insertion.
* Following requires further consideration:
Both CopyFrom() and ExecInsert() switch to the partition before invoking
BR triggers and back after AR trigger are processed. That ignores parent's
triggers completely. Note that those triggers are not required to be present
in each partition. OTOH, constraints are fine (both check and not null),
since parent's constraints are present in partitions.
---
src/backend/catalog/partition.c | 318 ++++++++++++++++++++++++++++++++
src/backend/commands/copy.c | 113 +++++++++++-
src/backend/commands/tablecmds.c | 1 +
src/backend/executor/execMain.c | 44 +++++-
src/backend/executor/nodeModifyTable.c | 96 ++++++++++
src/backend/parser/analyze.c | 8 +
src/include/catalog/partition.h | 24 +++
src/include/executor/executor.h | 6 +
src/include/nodes/execnodes.h | 10 +
9 files changed, 615 insertions(+), 5 deletions(-)
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index 46bbb9e..abec2b8 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -131,6 +131,21 @@ static Node *generate_partition_check_expr(Relation rel);
static PartitionDescNode GetPartitionDescNodeRecurse(Relation rel, int offset);
static int get_leaf_partition_count(PartitionDescNode pdnode);
+static PartitionKeyExecInfo *BuildPartitionKeyExecInfo(Relation rel);
+static void FormPartitionKeyDatum(PartitionKeyExecInfo *pkinfo,
+ TupleTableSlot *slot,
+ EState *estate,
+ Datum *values,
+ bool *isnull);
+static int list_partition_for_tuple(PartitionKey key, PartitionDesc pdesc,
+ Datum value, bool isnull);
+static int range_partition_for_tuple(PartitionKey key, PartitionDesc pdesc,
+ Datum *values);
+static int range_partition_bsearch(PartitionKey key, PartitionDesc pdesc,
+ Datum *values);
+static bool rightof(PartitionKey key, Datum *values, PartitionRangeBound *bound);
+static bool leftof(PartitionKey key, Datum *values, PartitionRangeBound *bound);
+
/*
* StorePartitionKey
* Store the partition keys of rel into pg_partitioned catalog
@@ -1863,6 +1878,7 @@ GetPartitionDescNodeRecurse(Relation rel, int offset)
/* First build our own node */
root = (PartitionDescNode) palloc0(sizeof(PartitionDescNodeData));
+ root->pkinfo = BuildPartitionKeyExecInfo(rel);
root->pdesc = pdesc;
root->relid = RelationGetRelid(rel);
root->offset = offset;
@@ -1980,3 +1996,305 @@ get_leaf_partition_oids(PartitionDescNode pdnode)
return result;
}
+
+/* ----------------
+ * BuildPartitionKeyExecInfo
+ * Construct a list of PartitionKeyExecInfo records for an open
+ * relation
+ *
+ * PartitionKeyExecInfo stores the information about the partition key
+ * that's needed when inserting tuples into a partitioned table; especially,
+ * partition key expression state if there are any expression columns in
+ * the partition key. Normally we build a PartitionKeyExecInfo for a
+ * partitioned table just once per command, and then use it for (potentially)
+ * many tuples.
+ * ----------------
+ */
+static PartitionKeyExecInfo *
+BuildPartitionKeyExecInfo(Relation rel)
+{
+ PartitionKeyExecInfo *pkinfo;
+
+ pkinfo = (PartitionKeyExecInfo *) palloc0(sizeof(PartitionKeyExecInfo));
+ pkinfo->pi_Key = CopyPartitionKey(rel->rd_partkey);
+ pkinfo->pi_ExpressionState = NIL;
+
+ return pkinfo;
+}
+
+/*
+ * get_partition_for_tuple
+ * Recursively finds the "leaf" partition for tuple (slot)
+ *
+ * Returns -1 if no partition is found and sets *failed_at to the OID of
+ * the partitioned table whose partition was not found.
+ */
+int
+get_partition_for_tuple(PartitionDescNode pdnode,
+ TupleTableSlot *slot,
+ EState *estate,
+ Oid *failed_at)
+{
+ PartitionKeyExecInfo *pkinfo = pdnode->pkinfo;
+ PartitionDescNode node;
+ Datum values[PARTITION_MAX_KEYS];
+ bool isnull[PARTITION_MAX_KEYS];
+ int i;
+ int index;
+
+ if (pdnode->pdesc->nparts == 0)
+ return -1;
+
+ /* Extract partition key from tuple */
+ Assert(GetPerTupleExprContext(estate)->ecxt_scantuple == slot);
+ FormPartitionKeyDatum(pkinfo, slot, estate, values, isnull);
+
+ /* Disallow nulls, if range partition key */
+ for (i = 0; i < pkinfo->pi_Key->partnatts; i++)
+ if (isnull[i] && pkinfo->pi_Key->strategy == PARTITION_STRAT_RANGE)
+ ereport(ERROR,
+ (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+ errmsg("range partition key contains null")));
+
+ switch (pkinfo->pi_Key->strategy)
+ {
+ case PARTITION_STRAT_LIST:
+ index = list_partition_for_tuple(pkinfo->pi_Key, pdnode->pdesc,
+ values[0], isnull[0]);
+ break;
+
+ case PARTITION_STRAT_RANGE:
+ index = range_partition_for_tuple(pkinfo->pi_Key, pdnode->pdesc,
+ values);
+ break;
+ }
+
+ /* Don't recurse, if no partition exists for the key at this level... */
+ if (index < 0)
+ {
+ *failed_at = pdnode->relid;
+ return index;
+ }
+
+ /* ... or, if the index'th partition is a leaf partition. */
+ if (!relid_is_partitioned(pdnode->pdesc->oids[index]))
+ {
+ PartitionDescNode prev;
+
+ /* Patiently determine own leaf partition index */
+ prev = node = pdnode->downlink;
+ if (node && node->index < index)
+ {
+ while (node)
+ {
+ if (node->index > index)
+ {
+ node = prev;
+ break;
+ }
+
+ prev = node;
+ node = node->next;
+ }
+
+ if (!node)
+ node = prev;
+
+ /*
+ * Account for siblings on left that are partition trees and those
+ * that are leaf partitions themsleves.
+ */
+ return node->offset + node->num_leaf_partitions +
+ (index - node->index - 1);
+ }
+ else
+ /* Only leaf partitions in pdnode->offset..index */
+ return pdnode->offset + index;
+
+ }
+
+ /* Recurse, by locating the index'th partition's node */
+ node = pdnode->downlink;
+ while (node->next != NULL && node->index != index)
+ node = node->next;
+ Assert (node != NULL);
+
+ return get_partition_for_tuple(node, slot, estate, failed_at);
+}
+
+/*
+ * FormPartitionKeyDatum
+ * Construct values[] and isnull[] arrays for partition key columns
+ */
+static void
+FormPartitionKeyDatum(PartitionKeyExecInfo *pkinfo,
+ TupleTableSlot *slot,
+ EState *estate,
+ Datum *values,
+ bool *isnull)
+{
+ ListCell *partexpr_item;
+ int i;
+
+ if (pkinfo->pi_Key->partexprs != NIL && pkinfo->pi_ExpressionState == NIL)
+ {
+ /* First time through, set up expression evaluation state */
+ pkinfo->pi_ExpressionState = (List *)
+ ExecPrepareExpr((Expr *) pkinfo->pi_Key->partexprs,
+ estate);
+ /* Check caller has set up context correctly */
+ Assert(GetPerTupleExprContext(estate)->ecxt_scantuple == slot);
+ }
+
+ partexpr_item = list_head(pkinfo->pi_ExpressionState);
+ for (i = 0; i < pkinfo->pi_Key->partnatts; i++)
+ {
+ AttrNumber keycol = pkinfo->pi_Key->partattrs[i];
+ Datum pkDatum;
+ bool isNull;
+
+ if (keycol != 0)
+ {
+ /* Plain column; get the value directly from the heap tuple */
+ pkDatum = slot_getattr(slot, keycol, &isNull);
+ }
+ else
+ {
+ /* Expression; need to evaluate it */
+ if (partexpr_item == NULL)
+ elog(ERROR, "wrong number of partition key expressions");
+ pkDatum = ExecEvalExprSwitchContext((ExprState *) lfirst(partexpr_item),
+ GetPerTupleExprContext(estate),
+ &isNull,
+ NULL);
+ partexpr_item = lnext(partexpr_item);
+ }
+ values[i] = pkDatum;
+ isnull[i] = isNull;
+ }
+
+ if (partexpr_item != NULL)
+ elog(ERROR, "wrong number of partition key expressions");
+}
+
+/*
+ * list_partition_for_tuple
+ * Find the list partition for a tuple
+ *
+ * Returns -1 if none found.
+ */
+static int
+list_partition_for_tuple(PartitionKey key, PartitionDesc pdesc,
+ Datum value, bool isnull)
+{
+ int i;
+
+ Assert(pdesc->nparts > 0);
+
+ for (i = 0; i < pdesc->nparts; i++)
+ {
+ int j;
+
+ if (isnull)
+ {
+ if (pdesc->lists[i]->contains_null)
+ return i;
+
+ continue;
+ }
+
+ for (j = 0; j < pdesc->lists[i]->nvalues; j++)
+ {
+ int32 cmpval;
+
+ cmpval = DatumGetInt32(FunctionCall2Coll(&key->partsupfunc[0],
+ key->tcinfo->typcoll[0],
+ pdesc->lists[i]->values[j],
+ value));
+ if (!cmpval)
+ return i;
+ }
+ }
+
+ return -1;
+}
+
+/*
+ * range_partition_for_tuple
+ * Search the range partition for a range key ('values')
+ *
+ * Returns -1 if none found.
+ */
+static int
+range_partition_for_tuple(PartitionKey key, PartitionDesc pdesc, Datum *values)
+{
+ Assert(pdesc->nparts > 0);
+
+ return range_partition_bsearch(key, pdesc, values);
+}
+
+/*
+ * range_partition_bsearch
+ * Workhorse of range_partition_for_tuple
+ */
+static int
+range_partition_bsearch(PartitionKey key, PartitionDesc pdesc,
+ Datum *values)
+{
+ int low, high;
+
+ /* Good ol' bsearch */
+ low = 0;
+ high = pdesc->nparts - 1;
+ while (low <= high)
+ {
+ int idx = (low + high) / 2;
+
+ if (pdesc->rangeuppers[idx]->infinite)
+ {
+ if (rightof(key, values, pdesc->rangelowers[idx]))
+ return idx;
+
+ break;
+ }
+ else if (leftof(key, values, pdesc->rangeuppers[idx]))
+ {
+ if (pdesc->rangelowers[idx]->infinite)
+ return idx;
+
+ if (rightof(key, values, pdesc->rangelowers[idx]))
+ return idx;
+
+ high = idx - 1;
+ continue;
+ }
+
+ low = idx + 1;
+ }
+
+ return -1;
+}
+
+/* Does range key lie to the right of partition bound */
+static bool
+rightof(PartitionKey key, Datum *values, PartitionRangeBound *bound)
+{
+ int32 cmpval = compare_range_keys(key, values, bound->val);
+
+ if (!cmpval)
+ return bound->lower ? bound->inclusive : !bound->inclusive;
+
+ return cmpval > 0;
+}
+
+/* Does range key lie to the left of partition bound */
+static bool
+leftof(PartitionKey key, Datum *values, PartitionRangeBound *bound)
+{
+ int32 cmpval = compare_range_keys(key, values, bound->val);
+
+ if (!cmpval)
+ return !bound->lower ? bound->inclusive : !bound->inclusive;
+
+ return cmpval < 0;
+}
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 14e81de..0b788b1 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -161,6 +161,10 @@ typedef struct CopyStateData
ExprState **defexprs; /* array of default att expressions */
bool volatile_defexprs; /* is any of defexprs volatile? */
List *range_table;
+ PartitionDescNode pdnode; /* partition descriptor node tree */
+ ResultRelInfo *partitions;
+ TupleConversionMap **partition_tupconv_maps;
+ int num_partitions;
/*
* These variables are used to reduce overhead in textual COPY FROM.
@@ -1364,6 +1368,52 @@ BeginCopy(bool is_from,
(errcode(ERRCODE_UNDEFINED_COLUMN),
errmsg("table \"%s\" does not have OIDs",
RelationGetRelationName(cstate->rel))));
+
+ /* Initialize state for CopyFrom tuple routing */
+ if (is_from && rel->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+ {
+ List *leaf_part_oids;
+ ListCell *cell;
+ int i;
+ int num_leaf_parts;
+ ResultRelInfo *leaf_rel_rri;
+
+ cstate->pdnode = RelationGetPartitionDescNode(rel);
+ leaf_part_oids = get_leaf_partition_oids(cstate->pdnode);
+ num_leaf_parts = list_length(leaf_part_oids);
+
+ cstate->num_partitions = num_leaf_parts;
+ cstate->partitions = (ResultRelInfo *)
+ palloc0(num_leaf_parts * sizeof(ResultRelInfo));
+ cstate->partition_tupconv_maps = (TupleConversionMap **)
+ palloc0(num_leaf_parts * sizeof(TupleConversionMap *));
+
+ leaf_rel_rri = cstate->partitions;
+ i = 0;
+ foreach(cell, leaf_part_oids)
+ {
+ Relation leaf_rel;
+
+ leaf_rel = heap_open(lfirst_oid(cell), RowExclusiveLock);
+ InitResultRelInfo(leaf_rel_rri,
+ leaf_rel,
+ 1, /* dummy */
+ false, /* no need for partition check */
+ 0);
+
+ /* Open partition indices */
+ ExecOpenIndices(leaf_rel_rri, false);
+
+ if (!equalTupleDescs(tupDesc, RelationGetDescr(leaf_rel)))
+ cstate->partition_tupconv_maps[i] =
+ convert_tuples_by_name(tupDesc,
+ RelationGetDescr(leaf_rel),
+ gettext_noop("could not convert row type"));
+
+ leaf_rel_rri++;
+ i++;
+ }
+ }
}
else
{
@@ -1659,6 +1709,8 @@ ClosePipeToProgram(CopyState cstate)
static void
EndCopy(CopyState cstate)
{
+ int i;
+
if (cstate->is_program)
{
ClosePipeToProgram(cstate);
@@ -1672,6 +1724,18 @@ EndCopy(CopyState cstate)
cstate->filename)));
}
+ /* Close all partitions and indices thereof */
+ for (i = 0; i < cstate->num_partitions; i++)
+ {
+ ResultRelInfo *resultRelInfo = cstate->partitions + i;
+
+ ExecCloseIndices(resultRelInfo);
+ heap_close(resultRelInfo->ri_RelationDesc, NoLock);
+
+ if (cstate->partition_tupconv_maps[i])
+ pfree(cstate->partition_tupconv_maps[i]);
+ }
+
MemoryContextDelete(cstate->copycontext);
pfree(cstate);
}
@@ -2216,6 +2280,7 @@ CopyFrom(CopyState cstate)
Datum *values;
bool *nulls;
ResultRelInfo *resultRelInfo;
+ ResultRelInfo *saved_resultRelInfo = NULL;
EState *estate = CreateExecutorState(); /* for ExecConstraints() */
ExprContext *econtext;
TupleTableSlot *myslot;
@@ -2236,7 +2301,8 @@ CopyFrom(CopyState cstate)
Assert(cstate->rel);
- if (cstate->rel->rd_rel->relkind != RELKIND_RELATION)
+ if (cstate->rel->rd_rel->relkind != RELKIND_RELATION &&
+ cstate->rel->rd_rel->relkind != RELKIND_PARTITIONED_REL)
{
if (cstate->rel->rd_rel->relkind == RELKIND_VIEW)
ereport(ERROR,
@@ -2344,6 +2410,7 @@ CopyFrom(CopyState cstate)
InitResultRelInfo(resultRelInfo,
cstate->rel,
1, /* dummy rangetable index */
+ true, /* do load partition check expression */
0);
ExecOpenIndices(resultRelInfo, false);
@@ -2371,6 +2438,7 @@ CopyFrom(CopyState cstate)
if ((resultRelInfo->ri_TrigDesc != NULL &&
(resultRelInfo->ri_TrigDesc->trig_insert_before_row ||
resultRelInfo->ri_TrigDesc->trig_insert_instead_row)) ||
+ cstate->pdnode != NULL ||
cstate->volatile_defexprs)
{
useHeapMultiInsert = false;
@@ -2395,7 +2463,11 @@ CopyFrom(CopyState cstate)
values = (Datum *) palloc(tupDesc->natts * sizeof(Datum));
nulls = (bool *) palloc(tupDesc->natts * sizeof(bool));
- bistate = GetBulkInsertState();
+ if (useHeapMultiInsert)
+ bistate = GetBulkInsertState();
+ else
+ bistate = NULL;
+
econtext = GetPerTupleExprContext(estate);
/* Set up callback to identify error line number */
@@ -2447,6 +2519,31 @@ CopyFrom(CopyState cstate)
slot = myslot;
ExecStoreTuple(tuple, slot, InvalidBuffer, false);
+ /* Determine the partition */
+ saved_resultRelInfo = resultRelInfo;
+ if (cstate->pdnode)
+ {
+ int i_leaf_partition;
+ TupleConversionMap *map;
+
+ econtext->ecxt_scantuple = slot;
+ i_leaf_partition = ExecFindPartition(resultRelInfo,
+ cstate->pdnode,
+ slot,
+ estate);
+ Assert(i_leaf_partition >= 0 &&
+ i_leaf_partition < cstate->num_partitions);
+
+ resultRelInfo = cstate->partitions + i_leaf_partition;
+ estate->es_result_relation_info = resultRelInfo;
+
+ map = cstate->partition_tupconv_maps[i_leaf_partition];
+ if (map)
+ tuple = do_convert_tuple(tuple, map);
+
+ tuple->t_tableOid = RelationGetRelid(resultRelInfo->ri_RelationDesc);
+ }
+
skip_tuple = false;
/* BEFORE ROW INSERT Triggers */
@@ -2497,7 +2594,8 @@ CopyFrom(CopyState cstate)
List *recheckIndexes = NIL;
/* OK, store the tuple and create index entries for it */
- heap_insert(cstate->rel, tuple, mycid, hi_options, bistate);
+ heap_insert(resultRelInfo->ri_RelationDesc,
+ tuple, mycid, hi_options, bistate);
if (resultRelInfo->ri_NumIndices > 0)
recheckIndexes = ExecInsertIndexTuples(slot, &(tuple->t_self),
@@ -2517,6 +2615,12 @@ CopyFrom(CopyState cstate)
* tuples inserted by an INSERT command.
*/
processed++;
+
+ if (saved_resultRelInfo)
+ {
+ resultRelInfo = saved_resultRelInfo;
+ estate->es_result_relation_info = resultRelInfo;
+ }
}
}
@@ -2530,7 +2634,8 @@ CopyFrom(CopyState cstate)
/* Done, clean up */
error_context_stack = errcallback.previous;
- FreeBulkInsertState(bistate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
MemoryContextSwitchTo(oldcontext);
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index c894d11..1fd847d 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -1242,6 +1242,7 @@ ExecuteTruncate(TruncateStmt *stmt)
InitResultRelInfo(resultRelInfo,
rel,
0, /* dummy rangetable index */
+ false,
0);
resultRelInfo++;
}
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 23ed0b6..b151fb5 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -826,6 +826,7 @@ InitPlan(QueryDesc *queryDesc, int eflags)
InitResultRelInfo(resultRelInfo,
resultRelation,
resultRelationIndex,
+ true,
estate->es_instrument);
resultRelInfo++;
}
@@ -1214,6 +1215,7 @@ void
InitResultRelInfo(ResultRelInfo *resultRelInfo,
Relation resultRelationDesc,
Index resultRelationIndex,
+ bool load_partition_check,
int instrument_options)
{
MemSet(resultRelInfo, 0, sizeof(ResultRelInfo));
@@ -1251,7 +1253,8 @@ InitResultRelInfo(ResultRelInfo *resultRelInfo,
resultRelInfo->ri_ConstraintExprs = NULL;
resultRelInfo->ri_junkFilter = NULL;
resultRelInfo->ri_projectReturning = NULL;
- resultRelInfo->ri_PartitionCheck = (Expr *)
+ if (load_partition_check)
+ resultRelInfo->ri_PartitionCheck = (Expr *)
RelationGetPartitionCheckExpr(resultRelationDesc);
}
@@ -1315,6 +1318,7 @@ ExecGetTriggerResultRel(EState *estate, Oid relid)
InitResultRelInfo(rInfo,
rel,
0, /* dummy rangetable index */
+ true,
estate->es_instrument);
estate->es_trig_target_relations =
lappend(estate->es_trig_target_relations, rInfo);
@@ -2987,3 +2991,41 @@ EvalPlanQualEnd(EPQState *epqstate)
epqstate->planstate = NULL;
epqstate->origslot = NULL;
}
+
+int
+ExecFindPartition(ResultRelInfo *resultRelInfo, PartitionDescNode pdnode,
+ TupleTableSlot *slot, EState *estate)
+{
+ int i_leaf_partition;
+ Oid failed_at;
+
+ i_leaf_partition = get_partition_for_tuple(pdnode, slot, estate,
+ &failed_at);
+
+ if (i_leaf_partition < 0)
+ {
+ Relation rel = resultRelInfo->ri_RelationDesc;
+ char *val_desc;
+ Bitmapset *insertedCols,
+ *updatedCols,
+ *modifiedCols;
+ TupleDesc tupDesc = RelationGetDescr(rel);
+
+ insertedCols = GetInsertedColumns(resultRelInfo, estate);
+ updatedCols = GetUpdatedColumns(resultRelInfo, estate);
+ modifiedCols = bms_union(insertedCols, updatedCols);
+ val_desc = ExecBuildSlotValueDescription(RelationGetRelid(rel),
+ slot,
+ tupDesc,
+ modifiedCols,
+ 64);
+ Assert(OidIsValid(failed_at));
+ ereport(ERROR,
+ (errcode(ERRCODE_CHECK_VIOLATION),
+ errmsg("no partition of relation \"%s\" found for row",
+ get_rel_name(failed_at)),
+ val_desc ? errdetail("Failing row contains %s.", val_desc) : 0));
+ }
+
+ return i_leaf_partition;
+}
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 6ecb6be..581112d 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -243,6 +243,7 @@ ExecInsert(ModifyTableState *mtstate,
{
HeapTuple tuple;
ResultRelInfo *resultRelInfo;
+ ResultRelInfo *saved_resultRelInfo = NULL;
Relation resultRelationDesc;
Oid newId;
List *recheckIndexes = NIL;
@@ -257,6 +258,31 @@ ExecInsert(ModifyTableState *mtstate,
* get information on the (current) result relation
*/
resultRelInfo = estate->es_result_relation_info;
+
+ saved_resultRelInfo = resultRelInfo;
+
+ if (mtstate->mt_partition_node)
+ {
+ int i_leaf_partition;
+ ExprContext *econtext = GetPerTupleExprContext(estate);
+ TupleConversionMap *map;
+
+ econtext->ecxt_scantuple = slot;
+ i_leaf_partition = ExecFindPartition(resultRelInfo,
+ mtstate->mt_partition_node,
+ slot,
+ estate);
+ Assert(i_leaf_partition >= 0 &&
+ i_leaf_partition < mtstate->mt_num_partitions);
+
+ resultRelInfo = mtstate->mt_partitions + i_leaf_partition;
+ estate->es_result_relation_info = resultRelInfo;
+
+ map = mtstate->mt_partition_tupconv_maps[i_leaf_partition];
+ if (map)
+ tuple = do_convert_tuple(tuple, map);
+ }
+
resultRelationDesc = resultRelInfo->ri_RelationDesc;
/*
@@ -496,6 +522,12 @@ ExecInsert(ModifyTableState *mtstate,
list_free(recheckIndexes);
+ if (saved_resultRelInfo)
+ {
+ resultRelInfo = saved_resultRelInfo;
+ estate->es_result_relation_info = resultRelInfo;
+ }
+
/*
* Check any WITH CHECK OPTION constraints from parent views. We are
* required to do this after testing all constraints and uniqueness
@@ -1550,6 +1582,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
Plan *subplan;
ListCell *l;
int i;
+ Relation rel;
/* check for unsupported flags */
Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));
@@ -1640,6 +1673,57 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
estate->es_result_relation_info = saved_resultRelInfo;
+ /* Build state for INSERT tuple routing */
+ rel = mtstate->resultRelInfo->ri_RelationDesc;
+ if (operation == CMD_INSERT && rel->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+ {
+ int i,
+ num_leaf_parts;
+ List *leaf_part_oids;
+ ListCell *cell;
+ ResultRelInfo *leaf_rel_rri;
+
+ mtstate->mt_partition_node = RelationGetPartitionDescNode(rel);
+ leaf_part_oids = get_leaf_partition_oids(mtstate->mt_partition_node);
+ num_leaf_parts = list_length(leaf_part_oids);
+
+ mtstate->mt_num_partitions = num_leaf_parts;
+ mtstate->mt_partitions = (ResultRelInfo *)
+ palloc0(num_leaf_parts * sizeof(ResultRelInfo));
+ mtstate->mt_partition_tupconv_maps = (TupleConversionMap **)
+ palloc0(num_leaf_parts * sizeof(TupleConversionMap *));
+
+ leaf_rel_rri = mtstate->mt_partitions;
+ i = 0;
+ foreach(cell, leaf_part_oids)
+ {
+ Relation leaf_rel;
+
+ leaf_rel = heap_open(lfirst_oid(cell), RowExclusiveLock);
+ InitResultRelInfo(leaf_rel_rri,
+ leaf_rel,
+ 1, /* dummy */
+ false, /* no need for partition checks */
+ eflags);
+
+ /* Open partition indices (note: ON CONFLICT unsupported)*/
+ if (leaf_rel_rri->ri_RelationDesc->rd_rel->relhasindex &&
+ operation != CMD_DELETE &&
+ leaf_rel_rri->ri_IndexRelationDescs == NULL)
+ ExecOpenIndices(leaf_rel_rri, false);
+
+ if (!equalTupleDescs(RelationGetDescr(rel),
+ RelationGetDescr(leaf_rel)))
+ mtstate->mt_partition_tupconv_maps[i] =
+ convert_tuples_by_name(RelationGetDescr(rel),
+ RelationGetDescr(leaf_rel),
+ gettext_noop("could not convert row type"));
+
+ leaf_rel_rri++;
+ i++;
+ }
+ }
+
/*
* Initialize any WITH CHECK OPTION constraints if needed.
*/
@@ -1957,6 +2041,18 @@ ExecEndModifyTable(ModifyTableState *node)
resultRelInfo);
}
+ /* Close all partitions and indices thereof */
+ for (i = 0; i < node->mt_num_partitions; i++)
+ {
+ ResultRelInfo *resultRelInfo = node->mt_partitions + i;
+
+ ExecCloseIndices(resultRelInfo);
+ heap_close(resultRelInfo->ri_RelationDesc, NoLock);
+
+ if (node->mt_partition_tupconv_maps[i])
+ pfree(node->mt_partition_tupconv_maps[i]);
+ }
+
/*
* Free the exprcontext
*/
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index f55129a..802cd68 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -755,8 +755,16 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
/* Process ON CONFLICT, if any. */
if (stmt->onConflictClause)
+ {
+ /* Bail out if target relation is partitioned table */
+ if (relid_is_partitioned(pstate->p_target_rangetblentry->relid))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("ON CONFLICT clause is not supported with partitioned tables")));
+
qry->onConflict = transformOnConflictClause(pstate,
stmt->onConflictClause);
+ }
/*
* If we have a RETURNING clause, we need to add the target relation to
diff --git a/src/include/catalog/partition.h b/src/include/catalog/partition.h
index 6204ad2..f4840e4 100644
--- a/src/include/catalog/partition.h
+++ b/src/include/catalog/partition.h
@@ -14,6 +14,8 @@
#define PARTITION_H
#include "fmgr.h"
+#include "executor/tuptable.h"
+#include "nodes/execnodes.h"
#include "parser/parse_node.h"
#include "utils/relcache.h"
@@ -62,6 +64,22 @@ typedef struct
typedef struct PartitionDescData *PartitionDesc;
/*
+ * PartitionKeyExecInfo
+ *
+ * This struct holds the information needed to extract partition
+ * column values from a heap tuple.
+ *
+ * Key copy of the rd_partkey of rel
+ * ExpressionState exec state for expressions, or NIL if none
+ */
+typedef struct PartitionKeyExecInfo
+{
+ NodeTag type;
+ PartitionKey pi_Key;
+ List *pi_ExpressionState; /* list of ExprState */
+} PartitionKeyExecInfo;
+
+/*
* Partition tree node
*
* pkinfo PartitionKey executor state
@@ -77,6 +95,7 @@ typedef struct PartitionDescData *PartitionDesc;
*/
typedef struct PartitionDescNodeData
{
+ PartitionKeyExecInfo *pkinfo;
PartitionDesc pdesc;
Oid relid;
int index;
@@ -110,4 +129,9 @@ extern Expr *get_check_expr_from_partbound(Relation rel, Relation parent,
Node *bound);
extern List *get_leaf_partition_oids(PartitionDescNode pdnode);
extern PartitionDescNode RelationGetPartitionDescNode(Relation rel);
+
+extern int get_partition_for_tuple(PartitionDescNode pdnode,
+ TupleTableSlot *slot,
+ EState *estate,
+ Oid *failed_at);
#endif /* PARTITION_H */
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index 44fac27..d4e8b35 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -14,6 +14,7 @@
#ifndef EXECUTOR_H
#define EXECUTOR_H
+#include "catalog/partition.h"
#include "executor/execdesc.h"
#include "nodes/parsenodes.h"
@@ -188,6 +189,7 @@ extern void CheckValidResultRel(Relation resultRel, CmdType operation);
extern void InitResultRelInfo(ResultRelInfo *resultRelInfo,
Relation resultRelationDesc,
Index resultRelationIndex,
+ bool load_partition_check,
int instrument_options);
extern ResultRelInfo *ExecGetTriggerResultRel(EState *estate, Oid relid);
extern bool ExecContextForcesOids(PlanState *planstate, bool *hasoids);
@@ -211,6 +213,10 @@ extern void EvalPlanQualSetPlan(EPQState *epqstate,
extern void EvalPlanQualSetTuple(EPQState *epqstate, Index rti,
HeapTuple tuple);
extern HeapTuple EvalPlanQualGetTuple(EPQState *epqstate, Index rti);
+extern int ExecFindPartition(ResultRelInfo *resultRelInfo,
+ PartitionDescNode pdnode,
+ TupleTableSlot *slot,
+ EState *estate);
#define EvalPlanQualSetSlot(epqstate, slot) ((epqstate)->origslot = (slot))
extern void EvalPlanQualFetchRowMarks(EPQState *epqstate);
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 74288e2..f2f535e 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -16,6 +16,7 @@
#include "access/genam.h"
#include "access/heapam.h"
+#include "access/tupconvert.h"
#include "executor/instrument.h"
#include "lib/pairingheap.h"
#include "nodes/params.h"
@@ -1140,6 +1141,15 @@ typedef struct ModifyTableState
* tlist */
TupleTableSlot *mt_conflproj; /* CONFLICT ... SET ... projection
* target */
+ struct PartitionDescNodeData *mt_partition_node;
+ /* Partition descriptor node tree */
+ ResultRelInfo *mt_partitions; /* Per leaf partition target
+ * relations */
+ TupleConversionMap **mt_partition_tupconv_maps;
+ /* Per leaf partition
+ * tuple conversion map */
+ int mt_num_partitions; /* Number of leaf partition target
+ * relations in the above array */
} ModifyTableState;
/* ----------------
--
1.7.1
^ permalink raw reply [nested|flat] 161+ messages in thread
* Re: Declarative partitioning
@ 2016-05-12 08:42 Sameer Thakur-2 <Sameer.Thakur@nttdata.com>
parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
0 siblings, 1 reply; 161+ messages in thread
From: Sameer Thakur-2 @ 2016-05-12 08:42 UTC (permalink / raw)
To: pgsql-hackers
Hello Amit,
In the example
>create table part201606week4 partition of parted
>for values start (2016, 6, 2) end (2016, 6, 29);
seems to be a typo
regards
Sameer
--
View this message in context: http://postgresql.nabble.com/Declarative-partitioning-tp5862462p5903204.html
Sent from the PostgreSQL - hackers mailing list archive at Nabble.com.
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 161+ messages in thread
* Re: Declarative partitioning
@ 2016-05-12 08:55 Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
parent: Sameer Thakur-2 <Sameer.Thakur@nttdata.com>
0 siblings, 0 replies; 161+ messages in thread
From: Amit Langote @ 2016-05-12 08:55 UTC (permalink / raw)
To: Sameer Thakur-2 <Sameer.Thakur@nttdata.com>; pgsql-hackers
Hi,
On 2016/05/12 17:42, Sameer Thakur-2 wrote:
> Hello Amit,
> In the example
>> create table part201606week4 partition of parted
>> for values start (2016, 6, 2) end (2016, 6, 29);
>
> seems to be a typo
Oops, there indeed is.
create table part201606week4 partition of parted
for values start (2016, 6, 2) end (2016, 6, 29);
create table part201606week4 partition of parted
for values start (2016, 6, 22) end (2016, 6, 29);
While copy-pasting part201606week4's definition from a previous line,
ended up pasting twice and then edited the second one to the correct
definition.
Thanks,
Amit
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 161+ messages in thread
* Re: Declarative partitioning
@ 2016-05-16 13:12 Ildar Musin <i.musin@postgrespro.ru>
parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
1 sibling, 2 replies; 161+ messages in thread
From: Ildar Musin @ 2016-05-16 13:12 UTC (permalink / raw)
To: pgsql-hackers
Hi Amit,
I'm running some experiments based on your infrastructure trying to
optimize SELECT queries. At some point I need to get PartitionDesc for
relation and to do it I'm using RelationGetPartitionDesc() function.
Problem is that this function copies relcache data and it can be quite
slow for large amounts (thousands) of partitions. The comment to the
function says that we cannot use relation->rd_partdesc pointer to
relcache because of possibility of relcache invalidation. Could you
please tell is it possible that relcache invalidation occurs during
SELECT/UPDATE/DELETE query?
Thanks!
--
Ildar Musin
i.musin@postgrespro.ru
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 161+ messages in thread
* Re: Declarative partitioning
@ 2016-05-17 07:56 Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
parent: Ildar Musin <i.musin@postgrespro.ru>
1 sibling, 0 replies; 161+ messages in thread
From: Amit Langote @ 2016-05-17 07:56 UTC (permalink / raw)
To: Ildar Musin <i.musin@postgrespro.ru>; pgsql-hackers
Hi Ildar,
On 2016/05/16 22:12, Ildar Musin wrote:
> Hi Amit,
>
> I'm running some experiments based on your infrastructure trying to
> optimize SELECT queries. At some point I need to get PartitionDesc for
> relation and to do it I'm using RelationGetPartitionDesc() function.
> Problem is that this function copies relcache data and it can be quite
> slow for large amounts (thousands) of partitions. The comment to the
> function says that we cannot use relation->rd_partdesc pointer to relcache
> because of possibility of relcache invalidation. Could you please tell is
> it possible that relcache invalidation occurs during SELECT/UPDATE/DELETE
> query?
Hmm, I think invalidation would not occur mid-query since it would have
acquired a lock on the table. So the copying may be unnecessary though I
may be wrong. I will study other similar code and see if that is so.
Thanks,
Amit
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 161+ messages in thread
* Re: Declarative partitioning
@ 2016-05-17 17:22 Tom Lane <tgl@sss.pgh.pa.us>
parent: Ildar Musin <i.musin@postgrespro.ru>
1 sibling, 1 reply; 161+ messages in thread
From: Tom Lane @ 2016-05-17 17:22 UTC (permalink / raw)
To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; +Cc: Ildar Musin <i.musin@postgrespro.ru>; pgsql-hackers
Amit Langote <Langote_Amit_f8@lab.ntt.co.jp> writes:
> On 2016/05/16 22:12, Ildar Musin wrote:
>> Could you please tell is
>> it possible that relcache invalidation occurs during SELECT/UPDATE/DELETE
>> query?
> Hmm, I think invalidation would not occur mid-query since it would have
> acquired a lock on the table.
This is incorrect: invalidation can occur anyway (for example, if
autoanalyze updates the relation's statistics). If you are holding
a lock, you can expect that the relation's schema will not change more
than your lock would allow --- but a cache flush and rebuild could happen
underneath you, so keeping a pointer to any subsidiary relcache data
structure is very dangerous.
The two ways that we've dealt with this type of hazard are to copy data
out of the relcache before using it; or to give the relcache the
responsibility of not moving a particular portion of data if it did not
change. From memory, the latter applies to the tuple descriptor and
trigger data, but we've done most other things the first way.
regards, tom lane
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 161+ messages in thread
* Re: Declarative partitioning
@ 2016-05-18 01:26 Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
parent: Tom Lane <tgl@sss.pgh.pa.us>
0 siblings, 2 replies; 161+ messages in thread
From: Amit Langote @ 2016-05-18 01:26 UTC (permalink / raw)
To: Tom Lane <tgl@sss.pgh.pa.us>; +Cc: Ildar Musin <i.musin@postgrespro.ru>; pgsql-hackers
On 2016/05/18 2:22, Tom Lane wrote:
> Amit Langote <Langote_Amit_f8@lab.ntt.co.jp> writes:
>> On 2016/05/16 22:12, Ildar Musin wrote:
>>> Could you please tell is
>>> it possible that relcache invalidation occurs during SELECT/UPDATE/DELETE
>>> query?
>
>> Hmm, I think invalidation would not occur mid-query since it would have
>> acquired a lock on the table.
>
> This is incorrect: invalidation can occur anyway (for example, if
> autoanalyze updates the relation's statistics). If you are holding
> a lock, you can expect that the relation's schema will not change more
> than your lock would allow --- but a cache flush and rebuild could happen
> underneath you, so keeping a pointer to any subsidiary relcache data
> structure is very dangerous.
I see. Thanks for clarifying.
> The two ways that we've dealt with this type of hazard are to copy data
> out of the relcache before using it; or to give the relcache the
> responsibility of not moving a particular portion of data if it did not
> change. From memory, the latter applies to the tuple descriptor and
> trigger data, but we've done most other things the first way.
It seems that tuple descriptor is reference-counted; however trigger data
is copied. The former seems to have been done on performance grounds (I
found 06e10abc).
So for a performance-sensitive relcache data structure, refcounting is the
way to go (although done quite rarely)? In this particular case, it is a
"partition descriptor" that could get big for a partitioned table with
partitions in hundreds or thousands.
Thanks,
Amit
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 161+ messages in thread
* Re: Declarative partitioning
@ 2016-05-18 15:36 Ildar Musin <i.musin@postgrespro.ru>
parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
1 sibling, 1 reply; 161+ messages in thread
From: Ildar Musin @ 2016-05-18 15:36 UTC (permalink / raw)
To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; Tom Lane <tgl@sss.pgh.pa.us>; +Cc: pgsql-hackers
Hi Amit and all,
On 18.05.2016 04:26, Amit Langote wrote:
> On 2016/05/18 2:22, Tom Lane wrote:
>> The two ways that we've dealt with this type of hazard are to copy data
>> out of the relcache before using it; or to give the relcache the
>> responsibility of not moving a particular portion of data if it did not
>> change. From memory, the latter applies to the tuple descriptor and
>> trigger data, but we've done most other things the first way.
> It seems that tuple descriptor is reference-counted; however trigger data
> is copied. The former seems to have been done on performance grounds (I
> found 06e10abc).
>
> So for a performance-sensitive relcache data structure, refcounting is the
> way to go (although done quite rarely)? In this particular case, it is a
> "partition descriptor" that could get big for a partitioned table with
> partitions in hundreds or thousands.
>
> Thanks,
> Amit
Here is an experimental patch that optimizes planning time for range
partitioned tables (it could be considered as a "proof of concept").
Patch should be applied on top of Amit's declarative partitioning patch.
It handles only a very special case (often used though) where
partitioning key consists of just a single attribute and doesn't contain
expressions.
The main idea is the following:
* we are looking for clauses like 'VAR OP CONST' (where VAR is
partitioning key attribute, OP is a comparison operator);
* using binary search find a partition (X) that fits CONST value;
* based on OP operator determine which partitions are also covered by
clause. There are possible cases:
1. If OP is '<' or '<=' then we need partitions standing left from X
(including)
2. If OP is '>' or '>=' then we need partitions standing right from
X (including)
3. If OP is '=' the we need only X partition
(for '<' and '>' operators we also check if CONST value is equal to a
lower or upper boundary (accordingly) and if it's true then exclude X).
For boolean expressions we evaluate left and right sides accordingly to
algorithm above and then based on boolean operator find intersection
(for AND) or union (for OR).
I run some benchmarks on:
1. original constraint exclusion mechanism,
2. optimized version (this patch) and
3. optimized version using relation->rd_partdesc pointer instead of
RelationGetPartitionDesc() function (see previous discussion).
Initial conditions:
CREATE TABLE abc (id SERIAL NOT NULL, a INT, dt TIMESTAMP) PARTITION BY
RANGE (a);
CREATE TABLE abc_1 PARTITION OF abc FOR VALUES START (0) END (1000);
CREATE TABLE abc_2 PARTITION OF abc FOR VALUES START (1000) END (2000);
...
etc
INSERT INTO %s (a) SELECT generate_series(0, <partitions_count> * 1000);
pgbench scripts:
https://gist.github.com/zilder/872e634a8eeb405bd045465fc9527e53 (where
:partitions is a number of partitions).
The first script tests fetching a single row from the partitioned table.
Results (tps):
# of partitions | constraint excl. | optimized | optimized (using
pointer)
----------------+------------------+---------------+----------------------------
100 | 658 | 2906 | 3079
1000 | 45 | 2174 | 3021
2000 | 22 | 1667 | 2919
The second script tests fetching all data from a single partition.
Results (tps):
# of partitions | constraint excl. | optimized | optimized (using
pointer)
----------------+------------------+---------------+----------------------------
100 | 317 | 1001 | 1051
1000 | 34 | 941 | 1023
2000 | 15 | 813 | 1016
Optimized version works much faster on large amount of partitions and
degradates slower than constraint exclusion. But still there is a
noticeable performance degradation from copying PartitionDesc structure:
with 2000 partitions RelationGetPartitionDesc() function spent more than
40% of all execution time on copying in first benchmark (measured with
`perf`). Using reference counting as Amit suggests will allow to
significantily decrease performance degradation.
Any comments and suggestions are very welcome. Thanks!
--
Ildar Musin
i.musin@postgrespro.ru
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Attachments:
[text/x-patch] 20160518_decl_part_opt.patch (19.7K, ../../573C8BFB.7050707@postgrespro.ru/3-20160518_decl_part_opt.patch)
download | inline diff:
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index abec2b8..cd4c727 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -47,6 +47,7 @@
#include "utils/inval.h"
#include "utils/lsyscache.h"
#include "utils/syscache.h"
+// #include "utils/rangeset.h"
/*
* Information about a single partition
@@ -77,6 +78,29 @@ typedef struct PartitionDescData
PartitionRangeBound **rangeuppers; /* range upper bounds */
} PartitionDescData;
+/*
+ * Context
+ */
+typedef struct walk_expr_tree_context
+{
+ Oid parentId;
+ Index index;
+ PartitionKey key;
+ PartitionDesc desc;
+} walk_expr_tree_context;
+
+typedef struct var_const_pair
+{
+ Var *pvar;
+ Const *pconst;
+} var_const_pair;
+
+typedef struct IndexRange
+{
+ uint32 lower;
+ uint32 upper;
+} IndexRange;
+
static PartitionKey CopyPartitionKey(PartitionKey fromkey);
static KeyTypeCollInfo *copy_key_type_coll_info(int nkeycols,
KeyTypeCollInfo *tcinfo);
@@ -142,10 +166,27 @@ static int list_partition_for_tuple(PartitionKey key, PartitionDesc pdesc,
static int range_partition_for_tuple(PartitionKey key, PartitionDesc pdesc,
Datum *values);
static int range_partition_bsearch(PartitionKey key, PartitionDesc pdesc,
- Datum *values);
+ Datum *values, int *nearest_pos);
static bool rightof(PartitionKey key, Datum *values, PartitionRangeBound *bound);
static bool leftof(PartitionKey key, Datum *values, PartitionRangeBound *bound);
+static List *make_all_partitions_rangeset(PartitionDesc desc);
+static bool lock_partition(Oid part_relid, LOCKMODE lockmode);
+static List *get_partitions_for_expr(const Expr *expr, walk_expr_tree_context *ctx);
+static List *get_partitions_for_boolexpr(const BoolExpr *expr, walk_expr_tree_context *ctx);
+static List *get_partitions_for_opexpr(const OpExpr *expr, walk_expr_tree_context *ctx);
+static List *get_range_partitions_for_opexpr(const OpExpr *expr, walk_expr_tree_context *ctx);
+static List *get_range_partitions_for_arropexpr(const ScalarArrayOpExpr *expr, walk_expr_tree_context *ctx);
+static bool pull_var_const(const OpExpr *expr, var_const_pair *pair);
+
+static IndexRange *make_range(uint32 lower, uint32 upper);
+static bool ranges_intersect(const IndexRange *a, const IndexRange *b);
+static bool ranges_conjuncted(const IndexRange *a, const IndexRange *b);
+static IndexRange *make_range_intersection(const IndexRange *a, const IndexRange *b);
+static IndexRange *make_range_union(const IndexRange *a, const IndexRange *b);
+static List *rangeset_union(List *a, List *b);
+static List *rangeset_intersection(List *a, List *b);
+
/*
* StorePartitionKey
* Store the partition keys of rel into pg_partitioned catalog
@@ -2230,7 +2271,7 @@ range_partition_for_tuple(PartitionKey key, PartitionDesc pdesc, Datum *values)
{
Assert(pdesc->nparts > 0);
- return range_partition_bsearch(key, pdesc, values);
+ return range_partition_bsearch(key, pdesc, values, NULL);
}
/*
@@ -2239,16 +2280,17 @@ range_partition_for_tuple(PartitionKey key, PartitionDesc pdesc, Datum *values)
*/
static int
range_partition_bsearch(PartitionKey key, PartitionDesc pdesc,
- Datum *values)
+ Datum *values, int *nearest_pos)
{
int low, high;
+ int idx = -1;
/* Good ol' bsearch */
low = 0;
high = pdesc->nparts - 1;
while (low <= high)
{
- int idx = (low + high) / 2;
+ idx = (low + high) / 2;
if (pdesc->rangeuppers[idx]->infinite)
{
@@ -2272,6 +2314,9 @@ range_partition_bsearch(PartitionKey key, PartitionDesc pdesc,
low = idx + 1;
}
+ if (nearest_pos)
+ *nearest_pos = idx;
+
return -1;
}
@@ -2298,3 +2343,603 @@ leftof(PartitionKey key, Datum *values, PartitionRangeBound *bound)
return cmpval < 0;
}
+
+/*
+ * get_partitions_by_quals
+ * Returns partitions OIDs according to qual clauses
+ */
+List *
+get_partitions_by_quals(Expr *quals, Oid relationId, Index rti, LOCKMODE lockmode)
+{
+ ListCell *lc;
+ walk_expr_tree_context ctx;
+ Relation relation;
+ List *partitions = NIL;
+ List *ranges;
+ int i;
+
+ relation = RelationIdGetRelation(relationId);
+ ctx.parentId = relationId;
+ ctx.index = rti;
+ ctx.key = relation->rd_partkey;
+ ctx.desc = RelationGetPartitionDesc(relation);
+
+ /* If there is an empty quals list then just return all partitions */
+ if (!quals)
+ {
+ ranges = list_make1(make_range(0, ctx.desc->nparts - 1));
+ goto build_partitions_list;
+ }
+
+ /*
+ * TODO: at the moment we do not determine partitions by quals for
+ * multicolumn partitioning and partitioning by expressions
+ */
+ if (ctx.key->partnatts > 1 || list_length(ctx.key->partexprs) > 0)
+ {
+ ranges = make_all_partitions_rangeset(ctx.desc);
+ goto build_partitions_list;
+ }
+
+ ranges = get_partitions_for_expr(quals, &ctx);
+
+build_partitions_list:
+ /*
+ * Acquire locks and build the result list.
+ */
+ partitions = lappend_oid(partitions, relationId);
+
+ /* Transform rangeset to actual OIDs and lock partitions */
+ foreach (lc, ranges)
+ {
+ IndexRange *range = (IndexRange *) lfirst(lc);
+
+ for (i=range->lower; i <= range->upper; i++)
+ if (lock_partition(ctx.desc->oids[i], lockmode))
+ partitions = lappend_oid(partitions, ctx.desc->oids[i]);
+ }
+
+ RelationClose(relation);
+
+ return partitions;
+}
+
+static List *
+make_all_partitions_rangeset(PartitionDesc desc)
+{
+ return list_make1(make_range(0, desc->nparts - 1));
+}
+
+/*
+ * lock_partition
+ * Set lock on partition
+ */
+static bool
+lock_partition(Oid part_relid, LOCKMODE lockmode)
+{
+ if (lockmode != NoLock)
+ {
+ /* Get the lock to synchronize against concurrent drop */
+ LockRelationOid(part_relid, lockmode);
+
+ /*
+ * Now that we have the lock, double-check to see if the relation
+ * really exists or not. If not, assume it was dropped while we
+ * waited to acquire lock, and ignore it.
+ */
+ if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(part_relid)))
+ {
+ /* Release useless lock */
+ UnlockRelationOid(part_relid, lockmode);
+ /* And ignore this relation */
+ return false;
+ }
+ }
+
+ return true;
+}
+
+/*
+ * get_partitions_for_expr
+ * Determine rangeset of partitions corresponding to an expression
+ */
+static List *
+get_partitions_for_expr(const Expr *expr, walk_expr_tree_context *ctx)
+{
+ BoolExpr *boolexpr;
+ OpExpr *opexpr;
+ ScalarArrayOpExpr *arrexpr;
+
+ switch (expr->type)
+ {
+ /* AND, OR, NOT expressions */
+ case T_BoolExpr:
+ boolexpr = (BoolExpr *) expr;
+ return get_partitions_for_boolexpr(boolexpr, ctx);
+
+ /* =, !=, <, > etc. */
+ case T_OpExpr:
+ opexpr = (OpExpr *) expr;
+ return get_partitions_for_opexpr(opexpr, ctx);
+
+ /* IN expression */
+ case T_ScalarArrayOpExpr:
+ arrexpr = (ScalarArrayOpExpr *) expr;
+ return get_range_partitions_for_arropexpr(arrexpr, ctx);
+ default:
+ return make_all_partitions_rangeset(ctx->desc);
+ }
+ return NIL;
+}
+
+/*
+ * get_partitions_for_boolexpr
+ * Determine rangeset of partitions for BoolExpr
+ *
+ * Function calculates rangesets for every operand and then depending on boolean
+ * operator it intersects (AND) or unites (OR) them
+ */
+static List *
+get_partitions_for_boolexpr(const BoolExpr *expr, walk_expr_tree_context *ctx)
+{
+ ListCell *lc;
+ Expr *arg;
+ List *result = NIL,
+ *ranges;
+
+ if (expr->boolop == AND_EXPR)
+ result = make_all_partitions_rangeset(ctx->desc);
+
+ foreach (lc, expr->args)
+ {
+ arg = (Expr *) lfirst(lc);
+
+ ranges = get_partitions_for_expr(arg, ctx);
+ switch (expr->boolop)
+ {
+ case AND_EXPR:
+ result = rangeset_intersection(result, ranges);
+ break;
+ case OR_EXPR:
+ result = rangeset_union(result, ranges);
+ break;
+ default:
+ break;
+ }
+ }
+
+ return result;
+}
+
+/*
+ * get_partitions_for_opexpr
+ * Determine rangeset of partitions for a given OpExpr
+ */
+static List *
+get_partitions_for_opexpr(const OpExpr *expr, walk_expr_tree_context *ctx)
+{
+ switch (ctx->key->strategy)
+ {
+ case PARTITION_STRAT_RANGE:
+ return get_range_partitions_for_opexpr(expr, ctx);
+
+ /* TODO: */
+ case PARTITION_STRAT_LIST:
+ default:
+ return make_all_partitions_rangeset(ctx->desc);
+ }
+}
+
+/*
+ * get_range_partitions_for_opexpr
+ * Determine partitions rangeset of the RANGE partitioned relation for the
+ * expressions like 'VAR OP CONST'
+ *
+ * It uses binary search to find position (pos) of partition in PartitionDesc
+ * corresponding to the CONST value. Depending on operator there are cases:
+ *
+ * * <, <= -- we need partitions located left from the pos (including pos);
+ * * = -- we need the exact found partition;
+ * * >, >= -- we need partitions located right from the pos (including pos).
+ *
+ * If operator is '<' and CONST value is on the lower boundary of partition then
+ * exclude pos from rangeset. Similarly for '>' operator.
+ */
+static List *
+get_range_partitions_for_opexpr(const OpExpr *expr, walk_expr_tree_context *ctx)
+{
+ var_const_pair pair;
+ int strategy;
+ int pos = -1;
+ int nearest_pos = -1;
+ uint32 lower=0,
+ upper=ctx->desc->nparts - 1;
+
+ /*
+ * Get Var and Const from OpEpxr. If Var belong to another relation
+ * then just return all partitions.
+ */
+ if (!pull_var_const(expr, &pair) || pair.pvar->varno != ctx->index ||
+ pair.pvar->varattno != ctx->key->partattrs[0])
+ return list_make1(make_range(lower, upper));
+
+ /* Use binary search to find appropriate partition */
+ pos = range_partition_bsearch(ctx->key, ctx->desc, &pair.pconst->constvalue, &nearest_pos);
+ strategy = get_op_opfamily_strategy(expr->opno, ctx->key->partopfamily[0]);
+
+ /* If partition wasn't found then determine the nearest one */
+ if (pos < 0)
+ {
+ pos = nearest_pos;
+ switch (strategy)
+ {
+ case BTEqualStrategyNumber:
+ return NIL;
+ case BTLessStrategyNumber:
+ case BTLessEqualStrategyNumber:
+ upper = pos;
+ if (!ctx->desc->rangelowers[pos]->infinite &&
+ leftof(ctx->key, &pair.pconst->constvalue, ctx->desc->rangelowers[pos]))
+ {
+ if (pos > 0)
+ upper = pos - 1;
+ }
+ break;
+ case BTGreaterStrategyNumber:
+ case BTGreaterEqualStrategyNumber:
+ lower = pos;
+ if (!ctx->desc->rangeuppers[pos]->infinite &&
+ rightof(ctx->key, &pair.pconst->constvalue, ctx->desc->rangeuppers[pos]))
+ {
+ if (pos < ctx->desc->nparts - 1)
+ lower = pos + 1;
+ }
+ break;
+ }
+ return list_make1(make_range(lower, upper));
+ }
+
+ /*
+ * Based on partition position and operator determine which partitions we
+ * should return. For example if it is '<=' operator then we should return
+ * this partition and all the partition that stand left from it, etc.
+ */
+ switch (strategy)
+ {
+ case BTEqualStrategyNumber:
+ upper = lower = pos;
+ break;
+
+ case BTLessStrategyNumber:
+ upper = pos;
+ /*
+ * If operator is '<' and CONST value is a lower boundary then
+ * exclude current partition from rangeset
+ */
+ if (!ctx->desc->rangelowers[pos]->infinite &&
+ !compare_range_keys(ctx->key, &pair.pconst->constvalue,
+ ctx->desc->rangelowers[pos]->val))
+ {
+ if (pos > 0)
+ upper = pos - 1;
+ else
+ return NIL;
+ }
+ break;
+
+ case BTLessEqualStrategyNumber:
+ upper = pos;
+ break;
+
+ case BTGreaterStrategyNumber:
+ lower = pos;
+ /*
+ * If operator is '>' and CONST value is an upper boundary then
+ * exclude current partition from rangeset
+ */
+ if (!ctx->desc->rangeuppers[pos]->infinite &&
+ !compare_range_keys(ctx->key, &pair.pconst->constvalue,
+ ctx->desc->rangeuppers[pos]->val))
+ {
+ if (pos < ctx->desc->nparts - 1)
+ lower = pos + 1;
+ else
+ return NIL;
+ }
+
+ case BTGreaterEqualStrategyNumber:
+ lower = pos;
+ break;
+ }
+
+ return list_make1(make_range(lower, upper));
+}
+
+/*
+ * pull_var_const
+ * Check if given expression has 'VAR OP CONST' structure. If it's true
+ * then store Var and Const nodes into pair object
+ */
+static bool
+pull_var_const(const OpExpr *expr, var_const_pair *pair)
+{
+ Node *firstarg = NULL,
+ *secondarg = NULL;
+
+ if (!IsA(expr, OpExpr))
+ return false;
+
+ if (list_length(expr->args) == 2)
+ {
+ firstarg = (Node *) linitial(expr->args);
+ secondarg = (Node *) lsecond(expr->args);
+
+ if (IsA(firstarg, Var) && IsA(secondarg, Const))
+ {
+ pair->pvar = (Var *) firstarg;
+ pair->pconst = (Const *) secondarg;
+ return true;
+ }
+ else if (IsA(secondarg, Var) && IsA(firstarg, Const))
+ {
+ pair->pvar = (Var *) secondarg;
+ pair->pconst = (Const *) firstarg;
+ return true;
+ }
+ }
+
+ return false;
+}
+
+/*
+ * get_range_partitions_for_arropexpr
+ * Returns rangeset for scalar array expression
+ */
+static List *
+get_range_partitions_for_arropexpr(const ScalarArrayOpExpr *expr, walk_expr_tree_context *ctx)
+{
+ Node *left = (Node *) linitial(expr->args),
+ *right = (Node *) lsecond(expr->args);
+ Var *var;
+ ArrayExpr *array;
+ List *result = NIL;
+ ListCell *l;
+ int pos;
+
+ /*
+ * If leftside isn't a variable or rightside isn't an array expression
+ * then just return all whole partitions range
+ */
+ if (!IsA(left, Var) || !IsA(right, ArrayExpr))
+ return list_make1(make_range(0, ctx->desc->nparts - 1));
+
+ var = (Var *) left;
+ array = (ArrayExpr *) right;
+
+ /* If variable isn't a partition key */
+ if (var->varno != ctx->index || var->varattno != ctx->key->partattrs[0])
+ return list_make1(make_range(0, ctx->desc->nparts - 1));
+
+ if (array)
+ {
+ int16 elmlen;
+ bool elmbyval;
+
+ get_typlenbyval(array->element_typeid,
+ &elmlen, &elmbyval);
+
+ /* Construct ranges list */
+ foreach(l, array->elements)
+ {
+ Node *c = (Node *) lfirst (l);
+
+ if (!IsA(c, Const))
+ return list_make1(make_range(0, ctx->desc->nparts - 1));
+
+ /* Use binary search to find appropriate partition */
+ pos = range_partition_bsearch(ctx->key, ctx->desc, &((Const *) c)->constvalue, NULL);
+ result = rangeset_union(result, list_make1(make_range(pos, pos)));
+ }
+
+ return result;
+ }
+
+ result = list_make1(make_range(0, ctx->desc->nparts - 1));
+ return result;
+}
+
+/*
+ * make_range
+ * Creates partitions range
+ */
+static IndexRange *
+make_range(uint32 lower, uint32 upper)
+{
+ IndexRange *r = palloc0(sizeof(IndexRange));
+
+ r->lower = lower;
+ r->upper = upper;
+ return r;
+}
+
+/*
+ * make_range_intersection
+ * Returns ranges intersection
+ *
+ * Note: caller must ensure that ranges are intersect
+ */
+static IndexRange *
+make_range_intersection(const IndexRange *a, const IndexRange *b)
+{
+ return make_range(Max(a->lower, b->lower),
+ Min(a->upper, b->upper));
+}
+
+/*
+ * make_range_union
+ * Returns ranges union
+ *
+ * Note: caller must ensure that ranges are conjucted
+ */
+static IndexRange *
+make_range_union(const IndexRange *a, const IndexRange *b)
+{
+ return make_range(Min(a->lower, b->lower),
+ Max(a->upper, b->upper));
+}
+
+/*
+ * ranges_intersect
+ * Returns true if ranges are intersect and false otherwise
+ */
+static bool
+ranges_intersect(const IndexRange *a, const IndexRange *b)
+{
+ return (a->lower <= b->upper) &&
+ (b->lower <= a->upper);
+}
+
+/*
+ * ranges_conjuncted
+ * Returns true if ranges are conjucted and false otherwise
+ */
+static bool
+ranges_conjuncted(const IndexRange *a, const IndexRange *b)
+{
+ return (a->lower <= b->upper+1) &&
+ (b->lower <= a->upper+1);
+}
+
+/*
+ * rangeset_union
+ * Builds a union for two given rangesets
+ */
+static List *
+rangeset_union(List *a, List *b)
+{
+ ListCell *ca,
+ *cb;
+ List *result = NIL;
+ IndexRange *range_a,
+ *range_b;
+ IndexRange *cur;
+ IndexRange *next;
+ bool have_cur = false;
+
+ ca = list_head(a);
+ cb = list_head(b);
+
+ while (ca || cb)
+ {
+ /* Fetch next range with lesser lower bound */
+ if (ca && cb)
+ {
+ range_a = (IndexRange *) lfirst(ca);
+ range_b = (IndexRange *) lfirst(cb);
+
+ if (range_a->lower <= range_b->lower)
+ {
+ next = range_a;
+ ca = lnext(ca);
+ }
+ else
+ {
+ next = range_b;
+ cb = lnext(cb);
+ }
+ }
+ else if (ca)
+ {
+ next = (IndexRange *) lfirst(ca);
+ ca = lnext(ca);
+ }
+ else if (cb)
+ {
+ next = (IndexRange *) lfirst(cb);
+ cb = lnext(cb);
+ }
+
+ if (!have_cur)
+ {
+ /* Put this range as current value if don't have it yet */
+ cur = next;
+ have_cur = true;
+ }
+ else
+ {
+ if (ranges_conjuncted(next, cur))
+ {
+ cur = make_range_union(next, cur);
+ }
+ else
+ {
+ /*
+ * Next range is not conjuncted with current. Put current to the
+ * result list and put next as current.
+ */
+ result = lappend(result, cur);
+ cur = next;
+ }
+ }
+ }
+
+ /* Put current value into result list if any */
+ if (have_cur)
+ result = lappend(result, cur);
+
+ return result;
+}
+
+/*
+ * rangeset_intersection
+ * Builds an intersection for two given rangesets
+ */
+static List *
+rangeset_intersection(List *a, List *b)
+{
+ ListCell *ca,
+ *cb;
+ List *result = NIL;
+ IndexRange *ra, *rb;
+
+ ca = list_head(a);
+ cb = list_head(b);
+
+ while (ca && cb)
+ {
+ ra = lfirst(ca);
+ rb = lfirst(cb);
+
+ /* Only care about intersecting ranges */
+ if (ranges_intersect(ra, rb))
+ {
+ IndexRange *intersect, *last;
+
+ intersect = make_range_intersection(ra, rb);
+ if (result != NIL)
+ {
+ last = llast(result);
+ if (ranges_conjuncted(last, intersect))
+ *last = *make_range_union(last, intersect);
+ else
+ result = lappend(result, intersect);
+ }
+ else
+ {
+ result = lappend(result, intersect);
+ }
+ }
+
+ /*
+ * Fetch next ranges. We use upper bound of current range to determine
+ * which lists to fetch, since lower bound of next range is greater (or
+ * equal) to upper bound of current.
+ */
+ if (ra->upper <= rb->upper)
+ ca = lnext(ca);
+ if (ra->upper >= rb->upper)
+ cb = lnext(cb);
+ }
+
+ return result;
+}
diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c
index 552b756..9bb8ead 100644
--- a/src/backend/optimizer/prep/prepunion.c
+++ b/src/backend/optimizer/prep/prepunion.c
@@ -35,6 +35,7 @@
#include "access/sysattr.h"
#include "catalog/pg_inherits_fn.h"
#include "catalog/pg_type.h"
+#include "catalog/partition.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "nodes/nodeFuncs.h"
@@ -45,6 +46,7 @@
#include "optimizer/planner.h"
#include "optimizer/prep.h"
#include "optimizer/tlist.h"
+#include "optimizer/clauses.h"
#include "parser/parse_coerce.h"
#include "parser/parsetree.h"
#include "utils/lsyscache.h"
@@ -1360,7 +1362,7 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti)
PlanRowMark *oldrc;
Relation oldrelation;
LOCKMODE lockmode;
- List *inhOIDs;
+ List *inhOIDs = NIL;
List *appinfos;
ListCell *l;
@@ -1403,8 +1405,17 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti)
else
lockmode = AccessShareLock;
- /* Scan for all members of inheritance set, acquire needed locks */
- inhOIDs = find_all_inheritors(parentOID, lockmode, NULL);
+ /* Determine partitions for partitioned relation */
+ if (relid_is_partitioned(parentOID) && root->parse->jointree)
+ {
+ Node *quals = eval_const_expressions(root, root->parse->jointree->quals);
+
+ inhOIDs = get_partitions_by_quals((Expr *) quals, parentOID, rti, lockmode);
+ }
+
+ if (!inhOIDs)
+ /* Scan for all members of inheritance set, acquire needed locks */
+ inhOIDs = find_all_inheritors(parentOID, lockmode, NULL);
/*
* Check that there's at least one descendant, else treat as no-child
diff --git a/src/include/catalog/partition.h b/src/include/catalog/partition.h
index f4840e4..5eb9971 100644
--- a/src/include/catalog/partition.h
+++ b/src/include/catalog/partition.h
@@ -134,4 +134,5 @@ extern int get_partition_for_tuple(PartitionDescNode pdnode,
TupleTableSlot *slot,
EState *estate,
Oid *failed_at);
+extern List *get_partitions_by_quals(Expr *quals, Oid relationId, Index rti, LOCKMODE lockmode);
#endif /* PARTITION_H */
^ permalink raw reply [nested|flat] 161+ messages in thread
* Re: Declarative partitioning
@ 2016-05-18 17:48 Tom Lane <tgl@sss.pgh.pa.us>
parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
1 sibling, 1 reply; 161+ messages in thread
From: Tom Lane @ 2016-05-18 17:48 UTC (permalink / raw)
To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; +Cc: Ildar Musin <i.musin@postgrespro.ru>; pgsql-hackers
Amit Langote <Langote_Amit_f8@lab.ntt.co.jp> writes:
> On 2016/05/18 2:22, Tom Lane wrote:
>> The two ways that we've dealt with this type of hazard are to copy data
>> out of the relcache before using it; or to give the relcache the
>> responsibility of not moving a particular portion of data if it did not
>> change. From memory, the latter applies to the tuple descriptor and
>> trigger data, but we've done most other things the first way.
After actually looking at the code, we do things that way for the
tupledesc, the relation's rules if any, and RLS policies --- see
RelationClearRelation().
> It seems that tuple descriptor is reference-counted; however trigger data
> is copied. The former seems to have been done on performance grounds (I
> found 06e10abc).
We do refcount tuple descriptors, but we've been afraid to try to rely
completely on that; there are too many places that assume a relcache
entry's tupdesc is safe to reference. It's not that easy to go over to
a fully refcounted approach, because that creates a new problem of being
sure that refcounts are decremented when necessary --- that's a pain,
particularly when a query is abandoned due to an error.
> So for a performance-sensitive relcache data structure, refcounting is the
> way to go (although done quite rarely)?
I'd be suspicious of this because of the cleanup problem. The
don't-replace-unless-changed approach is the one that's actually battle
tested.
regards, tom lane
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 161+ messages in thread
* Re: Declarative partitioning
@ 2016-05-19 10:29 Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
parent: Tom Lane <tgl@sss.pgh.pa.us>
0 siblings, 0 replies; 161+ messages in thread
From: Amit Langote @ 2016-05-19 10:29 UTC (permalink / raw)
To: Tom Lane <tgl@sss.pgh.pa.us>; +Cc: Ildar Musin <i.musin@postgrespro.ru>; pgsql-hackers
On 2016/05/19 2:48, Tom Lane wrote:
> Amit Langote <Langote_Amit_f8@lab.ntt.co.jp> writes:
>> On 2016/05/18 2:22, Tom Lane wrote:
>>> The two ways that we've dealt with this type of hazard are to copy data
>>> out of the relcache before using it; or to give the relcache the
>>> responsibility of not moving a particular portion of data if it did not
>>> change. From memory, the latter applies to the tuple descriptor and
>>> trigger data, but we've done most other things the first way.
>
> After actually looking at the code, we do things that way for the
> tupledesc, the relation's rules if any, and RLS policies --- see
> RelationClearRelation().
I think I confused refcounting method of keeping things around with the
RelationClearRelation()'s method. I now understand that you meant the
latter in your original message.
>> It seems that tuple descriptor is reference-counted; however trigger data
>> is copied. The former seems to have been done on performance grounds (I
>> found 06e10abc).
>
> We do refcount tuple descriptors, but we've been afraid to try to rely
> completely on that; there are too many places that assume a relcache
> entry's tupdesc is safe to reference. It's not that easy to go over to
> a fully refcounted approach, because that creates a new problem of being
> sure that refcounts are decremented when necessary --- that's a pain,
> particularly when a query is abandoned due to an error.
I see.
>> So for a performance-sensitive relcache data structure, refcounting is the
>> way to go (although done quite rarely)?
>
> I'd be suspicious of this because of the cleanup problem. The
> don't-replace-unless-changed approach is the one that's actually battle
> tested.
OK, I will try the RelationClearRelation()'s method of keeping partition
descriptor data around so that no repeated copying is necessary.
Thanks,
Amit
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 161+ messages in thread
* Re: Declarative partitioning
@ 2016-05-20 08:37 Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
parent: Ildar Musin <i.musin@postgrespro.ru>
0 siblings, 1 reply; 161+ messages in thread
From: Amit Langote @ 2016-05-20 08:37 UTC (permalink / raw)
To: Ildar Musin <i.musin@postgrespro.ru>; Tom Lane <tgl@sss.pgh.pa.us>; +Cc: pgsql-hackers
Hi Ildar,
On 2016/05/19 0:36, Ildar Musin wrote:
>
> Here is an experimental patch that optimizes planning time for range
> partitioned tables (it could be considered as a "proof of concept"). Patch
> should be applied on top of Amit's declarative partitioning patch. It
> handles only a very special case (often used though) where partitioning
> key consists of just a single attribute and doesn't contain expressions.
Great, thanks!
I understand that it's still PoC and the point may be just to consider
performance implications of excessive partdesc copying but I'm wondering
about a few things about the patch in general. See below.
> The main idea is the following:
> * we are looking for clauses like 'VAR OP CONST' (where VAR is
> partitioning key attribute, OP is a comparison operator);
> * using binary search find a partition (X) that fits CONST value;
> * based on OP operator determine which partitions are also covered by
> clause. There are possible cases:
> 1. If OP is '<' or '<=' then we need partitions standing left from X
> (including)
> 2. If OP is '>' or '>=' then we need partitions standing right from X
> (including)
> 3. If OP is '=' the we need only X partition
> (for '<' and '>' operators we also check if CONST value is equal to a
> lower or upper boundary (accordingly) and if it's true then exclude X).
>
> For boolean expressions we evaluate left and right sides accordingly to
> algorithm above and then based on boolean operator find intersection (for
> AND) or union (for OR).
Perhaps you're already aware but may I also suggest looking at how clauses
are matched to indexes? For example, consider how
match_clauses_to_index() in src/backend/optimizer/path/indxpath.c works.
Moreover, instead of pruning partitions in planner prep phase, might it
not be better to do that when considering paths for the (partitioned) rel?
IOW, instead of looking at parse->jointree, we should rather be working
with rel->baserestrictinfo. Although, that would require some revisions
to how append_rel_list, simple_rel_list, etc. are constructed and
manipulated in a given planner invocation. Maybe it's time for that...
Again, you may have already considered these things.
> I run some benchmarks on:
> 1. original constraint exclusion mechanism,
> 2. optimized version (this patch) and
> 3. optimized version using relation->rd_partdesc pointer instead of
> RelationGetPartitionDesc() function (see previous discussion).
>
> Initial conditions:
>
> CREATE TABLE abc (id SERIAL NOT NULL, a INT, dt TIMESTAMP) PARTITION BY
> RANGE (a);
> CREATE TABLE abc_1 PARTITION OF abc FOR VALUES START (0) END (1000);
> CREATE TABLE abc_2 PARTITION OF abc FOR VALUES START (1000) END (2000);
> ...
> etc
> INSERT INTO %s (a) SELECT generate_series(0, <partitions_count> * 1000);
>
> pgbench scripts:
> https://gist.github.com/zilder/872e634a8eeb405bd045465fc9527e53 (where
> :partitions is a number of partitions).
> The first script tests fetching a single row from the partitioned table.
> Results (tps):
>
> # of partitions | constraint excl. | optimized | optimized (using
> pointer)
> ----------------+------------------+---------------+----------------------------
>
> 100 | 658 | 2906 | 3079
> 1000 | 45 | 2174 | 3021
> 2000 | 22 | 1667 | 2919
>
>
> The second script tests fetching all data from a single partition. Results
> (tps):
>
> # of partitions | constraint excl. | optimized | optimized (using
> pointer)
> ----------------+------------------+---------------+----------------------------
>
> 100 | 317 | 1001 | 1051
> 1000 | 34 | 941 | 1023
> 2000 | 15 | 813 | 1016
>
> Optimized version works much faster on large amount of partitions and
> degradates slower than constraint exclusion. But still there is a
> noticeable performance degradation from copying PartitionDesc structure:
> with 2000 partitions RelationGetPartitionDesc() function spent more than
> 40% of all execution time on copying in first benchmark (measured with
> `perf`). Using reference counting as Amit suggests will allow to
> significantily decrease performance degradation.
Could you try with the attached updated set of patches? I changed
partition descriptor relcache code to eliminate excessive copying in
previous versions.
Thanks,
Amit
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Attachments:
[text/x-diff] 0001-Add-syntax-to-specify-partition-key-v5.patch (41.5K, ../../573ECCC3.7080104@lab.ntt.co.jp/2-0001-Add-syntax-to-specify-partition-key-v5.patch)
download | inline diff:
From 936e1c538b22a1d5bc33a5f144bb0c60c180ee42 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Mon, 22 Feb 2016 14:06:33 +0900
Subject: [PATCH 1/7] Add syntax to specify partition key.
This adds the following clause to CREATE TABLE:
PARTITION BY method (col [USING opclass] [, ...])
where 'method' is either RANGE or LIST. 'col' could be either a simple
column reference or a parenthesized expression. 'opclass' is name
of btree operator class to use with the column. If method is LIST,
only one column is allowed, whereas, for RANGE method up to 32 columns
can be specified. If this clause is specified, the following constraints
viz. PARTITION KEY, UNIQUE, FOREIGN KEY, EXCLUSION are not allowed
because they cannot be supported on partitioned tables.
This adds 2 new parser nodes viz. PartitionElem and PartitionBy. The former
is similar to IndexElem and the latter consists of details of partition
key such as the method and list of PartitionElems. The new member partby
of CreateStmt is a pointer to the PartitionBy struct created when PARTITION
BY clause is present.
---
doc/src/sgml/ref/create_table.sgml | 35 +++++
src/backend/commands/indexcmds.c | 4 +-
src/backend/commands/tablecmds.c | 215 ++++++++++++++++++++++++++++
src/backend/nodes/copyfuncs.c | 33 +++++
src/backend/nodes/equalfuncs.c | 28 ++++
src/backend/nodes/outfuncs.c | 27 ++++
src/backend/parser/gram.y | 110 ++++++++++++---
src/backend/parser/parse_agg.c | 11 ++
src/backend/parser/parse_expr.c | 5 +
src/backend/parser/parse_utilcmd.c | 63 ++++++++
src/include/commands/defrem.h | 2 +
src/include/nodes/nodes.h | 2 +
src/include/nodes/parsenodes.h | 35 +++++
src/include/parser/kwlist.h | 1 +
src/include/parser/parse_node.h | 3 +-
src/include/pg_config_manual.h | 5 +
src/test/regress/expected/create_table.out | 79 ++++++++++
src/test/regress/sql/create_table.sql | 68 +++++++++
18 files changed, 704 insertions(+), 22 deletions(-)
diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml
index d1807ed..70a7579 100644
--- a/doc/src/sgml/ref/create_table.sgml
+++ b/doc/src/sgml/ref/create_table.sgml
@@ -28,6 +28,7 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
[, ... ]
] )
[ INHERITS ( <replaceable>parent_table</replaceable> [, ... ] ) ]
+[ PARTITION BY {RANGE | LIST} ( { <replaceable class="parameter">column_name</replaceable> | ( <replaceable class="parameter">expression</replaceable> ) } [ <replaceable class="parameter">opclass</replaceable> ] [, ...] )
[ WITH ( <replaceable class="PARAMETER">storage_parameter</replaceable> [= <replaceable class="PARAMETER">value</replaceable>] [, ... ] ) | WITH OIDS | WITHOUT OIDS ]
[ ON COMMIT { PRESERVE ROWS | DELETE ROWS | DROP } ]
[ TABLESPACE <replaceable class="PARAMETER">tablespace_name</replaceable> ]
@@ -38,6 +39,7 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
| <replaceable>table_constraint</replaceable> }
[, ... ]
) ]
+[ PARTITION BY {RANGE | LIST} ( { <replaceable class="parameter">column_name</replaceable> | ( <replaceable class="parameter">expression</replaceable> ) } [ <replaceable class="parameter">opclass</replaceable> ] [, ...] )
[ WITH ( <replaceable class="PARAMETER">storage_parameter</replaceable> [= <replaceable class="PARAMETER">value</replaceable>] [, ... ] ) | WITH OIDS | WITHOUT OIDS ]
[ ON COMMIT { PRESERVE ROWS | DELETE ROWS | DROP } ]
[ TABLESPACE <replaceable class="PARAMETER">tablespace_name</replaceable> ]
@@ -314,6 +316,39 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
</varlistentry>
<varlistentry>
+ <term><literal>PARTITION BY {RANGE | LIST} ( { <replaceable class="parameter">column_name</replaceable> | ( <replaceable class="parameter">expression</replaceable> ) } [ <replaceable class="parameter">opclass</replaceable> ] [, ...] ) </literal></term>
+ <listitem>
+ <para>
+ The optional <literal>PARTITION BY</> clause specifies a method of
+ partitioning the table and the corresponding partition key. Table
+ thus created is called <firstterm>partitioned</firstterm> table. Key
+ consists of an ordered list of column names and/or expressions when
+ using the <literal>RANGE</> method, whereas only a single column or
+ expression can be specified when using the <literal>LIST</> method.
+ The type of a key column or an expresion must have an associated
+ btree operator class or one must be specified along with the column
+ or the expression.
+ </para>
+
+ <para>
+ A partitioned table is divided into sub-tables (called partitions), which
+ in turn, are created using separate <literal>CREATE TABLE</> commands.
+ The table itself is empty. A data row inserted into the table is mapped
+ to and stored in one of the partitions (if one exists) based on the
+ values of columns and/or expressions in the partition key and partition
+ rules associated with the partitions.
+ </para>
+
+ <para>
+ Currently, there are following limitations on definition of partitioned
+ tables: one cannot specify any UNIQUE, PRIMARY KEY, EXCLUDE and/or
+ FOREIGN KEY constraints.
+ </para>
+
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><literal>LIKE <replaceable>source_table</replaceable> [ <replaceable>like_option</replaceable> ... ]</literal></term>
<listitem>
<para>
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index d14d540..4ef6b80 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -69,8 +69,6 @@ static void ComputeIndexAttrs(IndexInfo *indexInfo,
char *accessMethodName, Oid accessMethodId,
bool amcanorder,
bool isconstraint);
-static Oid GetIndexOpClass(List *opclass, Oid attrType,
- char *accessMethodName, Oid accessMethodId);
static char *ChooseIndexName(const char *tabname, Oid namespaceId,
List *colnames, List *exclusionOpNames,
bool primary, bool isconstraint);
@@ -1256,7 +1254,7 @@ ComputeIndexAttrs(IndexInfo *indexInfo,
/*
* Resolve possibly-defaulted operator class specification
*/
-static Oid
+Oid
GetIndexOpClass(List *opclass, Oid attrType,
char *accessMethodName, Oid accessMethodId)
{
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 86e9814..d03147d 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -433,6 +433,11 @@ static void RangeVarCallbackForDropRelation(const RangeVar *rel, Oid relOid,
Oid oldRelOid, void *arg);
static void RangeVarCallbackForAlterRelation(const RangeVar *rv, Oid relid,
Oid oldrelid, void *arg);
+static PartitionBy *transformPartitionBy(Relation rel, PartitionBy *partitionby);
+static void ComputePartitionAttrs(Oid relid, List *partParams,
+ AttrNumber *partattrs,
+ List **partexprs,
+ Oid *partoplass);
/* ----------------------------------------------------------------
@@ -710,6 +715,21 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
AddRelationNewConstraints(rel, rawDefaults, stmt->constraints,
true, true, false);
+ /* Process partition key, if any */
+ if (stmt->partby)
+ {
+ PartitionBy *partby;
+ int16 partnatts;
+ AttrNumber partattrs[PARTITION_MAX_KEYS];
+ Oid partopclass[PARTITION_MAX_KEYS];
+ List *partexprs = NIL;
+
+ partnatts = list_length(stmt->partby->partParams);
+ partby = transformPartitionBy(rel, stmt->partby);
+ ComputePartitionAttrs(relationId, stmt->partby->partParams,
+ partattrs, &partexprs, partopclass);
+ }
+
ObjectAddressSet(address, RelationRelationId, relationId);
/*
@@ -12059,3 +12079,198 @@ RangeVarCallbackForAlterRelation(const RangeVar *rv, Oid relid, Oid oldrelid,
ReleaseSysCache(tuple);
}
+
+/*
+ * transformPartitionBy
+ * Transform any expressions present in the partition key
+ */
+static PartitionBy *
+transformPartitionBy(Relation rel, PartitionBy *partitionby)
+{
+ PartitionBy *partby;
+ ParseState *pstate;
+ RangeTblEntry *rte;
+ ListCell *l;
+
+ partby = (PartitionBy *) makeNode(PartitionBy);
+
+ partby->strategy = partitionby->strategy;
+ partby->location = partitionby->location;
+ partby->partParams = NIL;
+
+ /*
+ * Create a dummy ParseState and insert the target relation as its sole
+ * rangetable entry. We need a ParseState for transformExpr.
+ */
+ pstate = make_parsestate(NULL);
+ rte = addRangeTableEntryForRelation(pstate, rel, NULL, false, true);
+ addRTEtoQuery(pstate, rte, true, true, true);
+
+ /* take care of any partition expressions */
+ foreach(l, partitionby->partParams)
+ {
+ ListCell *column;
+ PartitionElem *pelem = (PartitionElem *) lfirst(l);
+
+ /* Check for PARTITION BY ... ON (foo, foo) */
+ foreach(column, partby->partParams)
+ {
+ PartitionElem *pparam = (PartitionElem *) lfirst(column);
+
+ if (pelem->name && pparam->name &&
+ !strcmp(pelem->name, pparam->name))
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_COLUMN),
+ errmsg("column \"%s\" appears twice in partition key", pelem->name),
+ parser_errposition(pstate, pelem->location)));
+ }
+
+ if (pelem->expr)
+ {
+ /* Now do parse transformation of the expression */
+ pelem->expr = transformExpr(pstate, pelem->expr,
+ EXPR_KIND_PARTITION_KEY);
+
+ /* we have to fix its collations too */
+ assign_expr_collations(pstate, pelem->expr);
+
+ /*
+ * transformExpr() should have already rejected subqueries,
+ * aggregates, and window functions, based on the EXPR_KIND_ for
+ * a partition key expression.
+ *
+ * Also reject expressions returning sets; this is for consistency
+ * DefineRelation() will make more checks.
+ */
+ if (expression_returns_set(pelem->expr))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("partition key expression cannot return a set"),
+ parser_errposition(pstate, pelem->location)));
+ }
+
+ partby->partParams = lappend(partby->partParams, pelem);
+ }
+
+ return partby;
+}
+
+/*
+ * ComputePartitionAttrs
+ * Compute per-partition-column information from partParams
+ */
+static void
+ComputePartitionAttrs(Oid relid, List *partParams, AttrNumber *partattrs,
+ List **partexprs, Oid *partopclass)
+{
+ int attn;
+ ListCell *lc;
+
+ attn = 0;
+ foreach(lc, partParams)
+ {
+ PartitionElem *pelem = (PartitionElem *) lfirst(lc);
+ Oid atttype;
+ Oid opclassOid;
+
+ if (pelem->name != NULL)
+ {
+ HeapTuple atttuple;
+ Form_pg_attribute attform;
+
+ atttuple = SearchSysCacheAttName(relid, pelem->name);
+ if (!HeapTupleIsValid(atttuple))
+ {
+ /* difference in error message spellings is historical */
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_COLUMN),
+ errmsg("column \"%s\" named in partition key does not exist",
+ pelem->name)));
+ }
+ attform = (Form_pg_attribute) GETSTRUCT(atttuple);
+
+ if (attform->attnum <= 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_COLUMN),
+ errmsg("cannot use system column \"%s\" in partition key",
+ pelem->name)));
+
+ partattrs[attn] = attform->attnum;
+ atttype = attform->atttypid;
+ ReleaseSysCache(atttuple);
+ }
+ else
+ {
+ /* Partition key expression */
+ Node *expr = pelem->expr;
+
+ Assert(expr != NULL);
+ atttype = exprType(expr);
+
+ if (IsA(expr, CollateExpr))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("cannot use COLLATE in partition key expression")));
+
+ if (IsA(expr, Var) &&
+ ((Var *) expr)->varattno != InvalidAttrNumber)
+ {
+ /*
+ * User wrote "(column)" or "(column COLLATE something)".
+ * Treat it like simple attribute anyway.
+ */
+ partattrs[attn] = ((Var *) expr)->varattno;
+ }
+ else
+ {
+ partattrs[attn] = 0; /* marks expression */
+ *partexprs = lappend(*partexprs, expr);
+
+ /*
+ * transformExpr() should have already rejected subqueries,
+ * aggregates, and window functions, based on the EXPR_KIND_
+ * for a partition key expression.
+ */
+
+ /*
+ * An expression using mutable functions is probably wrong even
+ * even to use in a partition key
+ */
+ expr = (Node *) expression_planner((Expr *) expr);
+
+ if (IsA(expr, Const))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("cannot use a constant expression as partition key")));
+
+ if (contain_mutable_functions(expr))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("functions in partition key expression must be marked IMMUTABLE")));
+ }
+ }
+
+ /*
+ * Identify the opclass to use. At the moment, we use "btree" operators
+ * that seems enough for list and range partitioning.
+ */
+ if (!pelem->opclass)
+ {
+ opclassOid = GetDefaultOpClass(atttype, BTREE_AM_OID);
+
+ if (!OidIsValid(opclassOid))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("data type %s has no default btree operator class",
+ format_type_be(atttype)),
+ errhint("You must specify an existing btree operator class or define one for the type.")));
+ }
+ else
+ opclassOid = GetIndexOpClass(pelem->opclass,
+ atttype,
+ "btree",
+ BTREE_AM_OID);
+
+ partopclass[attn++] = opclassOid;
+ }
+}
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 20e38f0..968a3f4 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3001,6 +3001,7 @@ CopyCreateStmtFields(const CreateStmt *from, CreateStmt *newnode)
COPY_NODE_FIELD(relation);
COPY_NODE_FIELD(tableElts);
COPY_NODE_FIELD(inhRelations);
+ COPY_NODE_FIELD(partby);
COPY_NODE_FIELD(ofTypename);
COPY_NODE_FIELD(constraints);
COPY_NODE_FIELD(options);
@@ -4156,6 +4157,32 @@ _copyAlterPolicyStmt(const AlterPolicyStmt *from)
return newnode;
}
+static PartitionBy *
+_copyPartitionBy(const PartitionBy *from)
+{
+
+ PartitionBy *newnode = makeNode(PartitionBy);
+
+ COPY_SCALAR_FIELD(strategy);
+ COPY_NODE_FIELD(partParams);
+ COPY_LOCATION_FIELD(location);
+
+ return newnode;
+}
+
+static PartitionElem *
+_copyPartitionElem(const PartitionElem *from)
+{
+ PartitionElem *newnode = makeNode(PartitionElem);
+
+ COPY_STRING_FIELD(name);
+ COPY_NODE_FIELD(expr);
+ COPY_NODE_FIELD(opclass);
+ COPY_LOCATION_FIELD(location);
+
+ return newnode;
+}
+
/* ****************************************************************
* pg_list.h copy functions
* ****************************************************************
@@ -5049,6 +5076,12 @@ copyObject(const void *from)
case T_RoleSpec:
retval = _copyRoleSpec(from);
break;
+ case T_PartitionBy:
+ retval = _copyPartitionBy(from);
+ break;
+ case T_PartitionElem:
+ retval = _copyPartitionElem(from);
+ break;
default:
elog(ERROR, "unrecognized node type: %d", (int) nodeTag(from));
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index c5ccc42..56325fe 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1156,6 +1156,7 @@ _equalCreateStmt(const CreateStmt *a, const CreateStmt *b)
COMPARE_NODE_FIELD(relation);
COMPARE_NODE_FIELD(tableElts);
COMPARE_NODE_FIELD(inhRelations);
+ COMPARE_NODE_FIELD(partby);
COMPARE_NODE_FIELD(ofTypename);
COMPARE_NODE_FIELD(constraints);
COMPARE_NODE_FIELD(options);
@@ -2619,6 +2620,27 @@ _equalRoleSpec(const RoleSpec *a, const RoleSpec *b)
return true;
}
+static bool
+_equalPartitionBy(const PartitionBy *a, const PartitionBy *b)
+{
+ COMPARE_SCALAR_FIELD(strategy);
+ COMPARE_NODE_FIELD(partParams);
+ COMPARE_LOCATION_FIELD(location);
+
+ return true;
+}
+
+static bool
+_equalPartitionElem(const PartitionElem *a, const PartitionElem *b)
+{
+ COMPARE_STRING_FIELD(name);
+ COMPARE_NODE_FIELD(expr);
+ COMPARE_NODE_FIELD(opclass);
+ COMPARE_LOCATION_FIELD(location);
+
+ return true;
+}
+
/*
* Stuff from pg_list.h
*/
@@ -3369,6 +3391,12 @@ equal(const void *a, const void *b)
case T_RoleSpec:
retval = _equalRoleSpec(a, b);
break;
+ case T_PartitionBy:
+ retval = _equalPartitionBy(a, b);
+ break;
+ case T_PartitionElem:
+ retval = _equalPartitionElem(a, b);
+ break;
default:
elog(ERROR, "unrecognized node type: %d",
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index c2f0e0f..5623471 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2369,6 +2369,7 @@ _outCreateStmtInfo(StringInfo str, const CreateStmt *node)
WRITE_NODE_FIELD(relation);
WRITE_NODE_FIELD(tableElts);
WRITE_NODE_FIELD(inhRelations);
+ WRITE_NODE_FIELD(partby);
WRITE_NODE_FIELD(ofTypename);
WRITE_NODE_FIELD(constraints);
WRITE_NODE_FIELD(options);
@@ -3217,6 +3218,26 @@ _outConstraint(StringInfo str, const Constraint *node)
}
}
+static void
+_outPartitionBy(StringInfo str, const PartitionBy *node)
+{
+ WRITE_NODE_TYPE("PARTITIONBY");
+
+ WRITE_CHAR_FIELD(strategy);
+ WRITE_NODE_FIELD(partParams);
+ WRITE_LOCATION_FIELD(location);
+}
+
+static void
+_outPartitionElem(StringInfo str, const PartitionElem *node)
+{
+ WRITE_NODE_TYPE("PARTITIONELEM");
+
+ WRITE_STRING_FIELD(name);
+ WRITE_NODE_FIELD(expr);
+ WRITE_NODE_FIELD(opclass);
+ WRITE_LOCATION_FIELD(location);
+}
/*
* outNode -
@@ -3796,6 +3817,12 @@ outNode(StringInfo str, const void *obj)
case T_XmlSerialize:
_outXmlSerialize(str, obj);
break;
+ case T_PartitionBy:
+ _outPartitionBy(str, obj);
+ break;
+ case T_PartitionElem:
+ _outPartitionElem(str, obj);
+ break;
default:
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 18ec5f0..0607439 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -227,6 +227,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
struct ImportQual *importqual;
InsertStmt *istmt;
VariableSetStmt *vsetstmt;
+ PartitionElem *partelem;
+ PartitionBy *partby;
}
%type <node> stmt schema_stmt
@@ -539,6 +541,9 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
opt_frame_clause frame_extent frame_bound
%type <str> opt_existing_window_name
%type <boolean> opt_if_not_exists
+%type <partby> PartitionBy OptPartitionBy
+%type <partelem> part_elem
+%type <list> part_params
/*
* Non-keyword token types. These are hard-wired into the "flex" lexer.
@@ -603,7 +608,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
KEY
LABEL LANGUAGE LARGE_P LAST_P LATERAL_P
- LEADING LEAKPROOF LEAST LEFT LEVEL LIKE LIMIT LISTEN LOAD LOCAL
+ LEADING LEAKPROOF LEAST LEFT LEVEL LIKE LIMIT LIST LISTEN LOAD LOCAL
LOCALTIME LOCALTIMESTAMP LOCATION LOCK_P LOCKED LOGGED
MAPPING MATCH MATERIALIZED MAXVALUE METHOD MINUTE_P MINVALUE MODE MONTH_P MOVE
@@ -2808,69 +2813,75 @@ copy_generic_opt_arg_list_item:
*****************************************************************************/
CreateStmt: CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
- OptInherit OptWith OnCommitOption OptTableSpace
+ OptInherit OptPartitionBy OptWith OnCommitOption OptTableSpace
{
CreateStmt *n = makeNode(CreateStmt);
$4->relpersistence = $2;
n->relation = $4;
n->tableElts = $6;
n->inhRelations = $8;
+ n->partby = $9;
n->ofTypename = NULL;
n->constraints = NIL;
- n->options = $9;
- n->oncommit = $10;
- n->tablespacename = $11;
+ n->options = $10;
+ n->oncommit = $11;
+ n->tablespacename = $12;
n->if_not_exists = false;
$$ = (Node *)n;
}
| CREATE OptTemp TABLE IF_P NOT EXISTS qualified_name '('
- OptTableElementList ')' OptInherit OptWith OnCommitOption
- OptTableSpace
+ OptTableElementList ')' OptInherit OptPartitionBy OptWith
+ OnCommitOption OptTableSpace
{
CreateStmt *n = makeNode(CreateStmt);
$7->relpersistence = $2;
n->relation = $7;
n->tableElts = $9;
n->inhRelations = $11;
+ n->partby = $12;
n->ofTypename = NULL;
n->constraints = NIL;
- n->options = $12;
- n->oncommit = $13;
- n->tablespacename = $14;
+ n->options = $13;
+ n->oncommit = $14;
+ n->tablespacename = $15;
n->if_not_exists = true;
$$ = (Node *)n;
}
| CREATE OptTemp TABLE qualified_name OF any_name
- OptTypedTableElementList OptWith OnCommitOption OptTableSpace
+ OptTypedTableElementList OptPartitionBy OptWith OnCommitOption
+ OptTableSpace
{
CreateStmt *n = makeNode(CreateStmt);
$4->relpersistence = $2;
n->relation = $4;
n->tableElts = $7;
n->inhRelations = NIL;
+ n->partby = $8;
n->ofTypename = makeTypeNameFromNameList($6);
n->ofTypename->location = @6;
n->constraints = NIL;
- n->options = $8;
- n->oncommit = $9;
- n->tablespacename = $10;
+ n->options = $9;
+ n->oncommit = $10;
+ n->tablespacename = $11;
n->if_not_exists = false;
$$ = (Node *)n;
}
| CREATE OptTemp TABLE IF_P NOT EXISTS qualified_name OF any_name
- OptTypedTableElementList OptWith OnCommitOption OptTableSpace
+ OptTypedTableElementList OptPartitionBy OptWith OnCommitOption
+ OptTableSpace
{
CreateStmt *n = makeNode(CreateStmt);
$7->relpersistence = $2;
n->relation = $7;
n->tableElts = $10;
n->inhRelations = NIL;
+ n->partby = $11;
n->ofTypename = makeTypeNameFromNameList($9);
n->ofTypename->location = @9;
n->constraints = NIL;
- n->options = $11;
- n->oncommit = $12;
- n->tablespacename = $13;
+ n->options = $12;
+ n->oncommit = $13;
+ n->tablespacename = $14;
n->if_not_exists = true;
$$ = (Node *)n;
}
@@ -3415,6 +3426,68 @@ OptInherit: INHERITS '(' qualified_name_list ')' { $$ = $3; }
| /*EMPTY*/ { $$ = NIL; }
;
+/* Optional partition key definition */
+OptPartitionBy: PartitionBy { $$ = $1; }
+ | /*EMPTY*/ { $$ = NULL; }
+ ;
+
+PartitionBy: PARTITION BY RANGE '(' part_params ')'
+ {
+ PartitionBy *n = makeNode(PartitionBy);
+
+ n->strategy = PARTITION_STRAT_RANGE;
+ n->partParams = $5;
+ n->location = @1;
+
+ $$ = n;
+ }
+ | PARTITION BY LIST '(' part_params ')'
+ {
+ PartitionBy *n = makeNode(PartitionBy);
+
+ n->strategy = PARTITION_STRAT_LIST;
+ n->partParams = $5;
+ n->location = @1;
+
+ $$ = n;
+ }
+ ;
+
+part_params: part_elem { $$ = list_make1($1); }
+ | part_params ',' part_elem { $$ = lappend($1, $3); }
+ ;
+
+part_elem: ColId opt_class
+ {
+ PartitionElem *n = makeNode(PartitionElem);
+
+ n->name = $1;
+ n->expr = NULL;
+ n->opclass = $2;
+ n->location = @1;
+ $$ = n;
+ }
+ | func_expr_windowless opt_class
+ {
+ PartitionElem *n = makeNode(PartitionElem);
+
+ n->name = NULL;
+ n->expr = $1;
+ n->opclass = $2;
+ n->location = @1;
+ $$ = n;
+ }
+ | '(' a_expr ')' opt_class
+ {
+ PartitionElem *n = makeNode(PartitionElem);
+
+ n->name = NULL;
+ n->expr = $2;
+ n->opclass = $4;
+ n->location = @1;
+ $$ = n;
+ }
+ ;
/* WITH (options) is preferred, WITH OIDS and WITHOUT OIDS are legacy forms */
OptWith:
WITH reloptions { $$ = $2; }
@@ -13839,6 +13912,7 @@ unreserved_keyword:
| LAST_P
| LEAKPROOF
| LEVEL
+ | LIST
| LISTEN
| LOAD
| LOCAL
diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c
index 91bfe66..b56fd4f 100644
--- a/src/backend/parser/parse_agg.c
+++ b/src/backend/parser/parse_agg.c
@@ -486,6 +486,14 @@ check_agglevels_and_constraints(ParseState *pstate, Node *expr)
err = _("grouping operations are not allowed in trigger WHEN conditions");
break;
+ case EXPR_KIND_PARTITION_KEY:
+ if (isAgg)
+ err = _("aggregate functions are not allowed in partition key expressions");
+ else
+ err = _("grouping operations are not allowed in partition key expressions");
+
+ break;
+
/*
* There is intentionally no default: case here, so that the
@@ -843,6 +851,9 @@ transformWindowFuncCall(ParseState *pstate, WindowFunc *wfunc,
case EXPR_KIND_TRIGGER_WHEN:
err = _("window functions are not allowed in trigger WHEN conditions");
break;
+ case EXPR_KIND_PARTITION_KEY:
+ err = _("window functions are not allowed in partition key expressions");
+ break;
/*
* There is intentionally no default: case here, so that the
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 8b28516..b53b7f1 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -1723,6 +1723,9 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
case EXPR_KIND_TRIGGER_WHEN:
err = _("cannot use subquery in trigger WHEN condition");
break;
+ case EXPR_KIND_PARTITION_KEY:
+ err = _("cannot use subquery in partition key expressions");
+ break;
/*
* There is intentionally no default: case here, so that the
@@ -3249,6 +3252,8 @@ ParseExprKindName(ParseExprKind exprKind)
return "EXECUTE";
case EXPR_KIND_TRIGGER_WHEN:
return "WHEN";
+ case EXPR_KIND_PARTITION_KEY:
+ return "partition key expression";
/*
* There is intentionally no default: case here, so that the
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 6528494..e267f74 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -87,6 +87,7 @@ typedef struct
List *alist; /* "after list" of things to do after creating
* the table */
IndexStmt *pkey; /* PRIMARY KEY index, if any */
+ bool ispartitioned; /* true if table is partitioned */
} CreateStmtContext;
/* State shared by transformCreateSchemaStmt and its subroutines */
@@ -229,6 +230,7 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
cxt.blist = NIL;
cxt.alist = NIL;
cxt.pkey = NULL;
+ cxt.ispartitioned = stmt->partby != NULL;
/*
* Notice that we allow OIDs here only for plain tables, even though
@@ -247,6 +249,24 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
if (stmt->ofTypename)
transformOfType(&cxt, stmt->ofTypename);
+ if (stmt->partby)
+ {
+ int partnatts = list_length(stmt->partby->partParams);
+
+ if (partnatts > PARTITION_MAX_KEYS)
+ ereport(ERROR,
+ (errcode(ERRCODE_TOO_MANY_COLUMNS),
+ errmsg("cannot use more than %d columns in partition key",
+ PARTITION_MAX_KEYS)));
+
+ if (stmt->partby->strategy == PARTITION_STRAT_LIST &&
+ partnatts > 1)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("cannot use more than one column in partition key"),
+ errdetail("Only one column allowed with list partitioning.")));
+ }
+
/*
* Run through each primary element in the table creation clause. Separate
* column defs from constraints, and do preliminary analysis. We have to
@@ -582,6 +602,12 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
errmsg("primary key constraints are not supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
+ if (cxt->ispartitioned)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("primary key constraints are not supported on partitioned tables"),
+ parser_errposition(cxt->pstate,
+ constraint->location)));
/* FALL THRU */
case CONSTR_UNIQUE:
@@ -591,6 +617,12 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
errmsg("unique constraints are not supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
+ if (cxt->ispartitioned)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("unique constraints are not supported on partitioned tables"),
+ parser_errposition(cxt->pstate,
+ constraint->location)));
if (constraint->keys == NIL)
constraint->keys = list_make1(makeString(column->colname));
cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
@@ -608,6 +640,12 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
errmsg("foreign key constraints are not supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
+ if (cxt->ispartitioned)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("foreign key constraints are not supported on partitioned tables"),
+ parser_errposition(cxt->pstate,
+ constraint->location)));
/*
* Fill in the current attribute's name and throw it into the
@@ -673,6 +711,12 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
errmsg("primary key constraints are not supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
+ if (cxt->ispartitioned)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("primary key constraints are not supported on partitioned tables"),
+ parser_errposition(cxt->pstate,
+ constraint->location)));
cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
break;
@@ -683,6 +727,12 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
errmsg("unique constraints are not supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
+ if (cxt->ispartitioned)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("unique constraints are not supported on partitioned tables"),
+ parser_errposition(cxt->pstate,
+ constraint->location)));
cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
break;
@@ -693,6 +743,12 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
errmsg("exclusion constraints are not supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
+ if (cxt->ispartitioned)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("exclusion constraints are not supported on partitioned tables"),
+ parser_errposition(cxt->pstate,
+ constraint->location)));
cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
break;
@@ -707,6 +763,12 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
errmsg("foreign key constraints are not supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
+ if (cxt->ispartitioned)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("foreign key constraints are not supported on partitioned tables"),
+ parser_errposition(cxt->pstate,
+ constraint->location)));
cxt->fkconstraints = lappend(cxt->fkconstraints, constraint);
break;
@@ -2515,6 +2577,7 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
cxt.blist = NIL;
cxt.alist = NIL;
cxt.pkey = NULL;
+ cxt.ispartitioned = false;
/*
* The only subtypes that currently require parse transformation handling
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index b064eb4..28024f8 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -42,6 +42,8 @@ extern bool CheckIndexCompatible(Oid oldId,
List *attributeList,
List *exclusionOpNames);
extern Oid GetDefaultOpClass(Oid type_id, Oid am_id);
+extern Oid GetIndexOpClass(List *opclass, Oid attrType,
+ char *accessMethodName, Oid accessMethodId);
/* commands/functioncmds.c */
extern ObjectAddress CreateFunction(CreateFunctionStmt *stmt, const char *queryString);
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index ea65f2e..9e582a5 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -452,6 +452,8 @@ typedef enum NodeTag
T_OnConflictClause,
T_CommonTableExpr,
T_RoleSpec,
+ T_PartitionElem,
+ T_PartitionBy,
/*
* TAGS FOR REPLICATION GRAMMAR PARSE NODES (replnodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 714cf15..4f697b8 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -696,6 +696,40 @@ typedef struct XmlSerialize
int location; /* token location, or -1 if unknown */
} XmlSerialize;
+/* Partitioning related definitions */
+
+/*
+ * PartitionElem - a partition key column
+ *
+ * 'name' Name of the table column included in the key
+ * 'expr' Expression node tree of expressional key column
+ * 'opclass' Operator class name associated with the column
+ */
+typedef struct PartitionElem
+{
+ NodeTag type;
+ char *name; /* name of column to partition on, or NULL */
+ Node *expr; /* expression to partition on, or NULL */
+ List *opclass; /* name of desired opclass; NIL = default */
+ int location; /* token location, or -1 if unknown */
+} PartitionElem;
+
+/*
+ * PartitionBy - partition key definition including the strategy
+ *
+ * 'strategy' partition strategy to use (one of the below defined)
+ * 'partParams' List of PartitionElems, one for each key column
+ */
+#define PARTITION_STRAT_LIST 'l'
+#define PARTITION_STRAT_RANGE 'r'
+
+typedef struct PartitionBy
+{
+ NodeTag type;
+ char strategy;
+ List *partParams;
+ int location; /* token location, or -1 if unknown */
+} PartitionBy;
/****************************************************************************
* Nodes for a Query tree
@@ -1750,6 +1784,7 @@ typedef struct CreateStmt
List *tableElts; /* column definitions (list of ColumnDef) */
List *inhRelations; /* relations to inherit from (list of
* inhRelation) */
+ PartitionBy *partby; /* PARTITION BY clause */
TypeName *ofTypename; /* OF typename */
List *constraints; /* constraints (list of Constraint nodes) */
List *options; /* options from WITH clause */
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 17ffef5..40da67a 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -227,6 +227,7 @@ PG_KEYWORD("left", LEFT, TYPE_FUNC_NAME_KEYWORD)
PG_KEYWORD("level", LEVEL, UNRESERVED_KEYWORD)
PG_KEYWORD("like", LIKE, TYPE_FUNC_NAME_KEYWORD)
PG_KEYWORD("limit", LIMIT, RESERVED_KEYWORD)
+PG_KEYWORD("list", LIST, UNRESERVED_KEYWORD)
PG_KEYWORD("listen", LISTEN, UNRESERVED_KEYWORD)
PG_KEYWORD("load", LOAD, UNRESERVED_KEYWORD)
PG_KEYWORD("local", LOCAL, UNRESERVED_KEYWORD)
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index e3e359c..a13c6fb 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -64,7 +64,8 @@ typedef enum ParseExprKind
EXPR_KIND_ALTER_COL_TRANSFORM, /* transform expr in ALTER COLUMN TYPE */
EXPR_KIND_EXECUTE_PARAMETER, /* parameter value in EXECUTE */
EXPR_KIND_TRIGGER_WHEN, /* WHEN condition in CREATE TRIGGER */
- EXPR_KIND_POLICY /* USING or WITH CHECK expr in policy */
+ EXPR_KIND_POLICY, /* USING or WITH CHECK expr in policy */
+ EXPR_KIND_PARTITION_KEY /* partition key expression */
} ParseExprKind;
diff --git a/src/include/pg_config_manual.h b/src/include/pg_config_manual.h
index 8d5a378..850251b 100644
--- a/src/include/pg_config_manual.h
+++ b/src/include/pg_config_manual.h
@@ -46,6 +46,11 @@
#define INDEX_MAX_KEYS 32
/*
+ * Maximum number of columns in a partition key
+ */
+#define PARTITION_MAX_KEYS 32
+
+/*
* Set the upper and lower bounds of sequence values.
*/
#define SEQ_MAXVALUE PG_INT64_MAX
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 41ceb87..aaae9f9 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -253,3 +253,82 @@ DROP TABLE as_select1;
-- check that the oid column is added before the primary key is checked
CREATE TABLE oid_pk (f1 INT, PRIMARY KEY(oid)) WITH OIDS;
DROP TABLE oid_pk;
+--
+-- partitioned tables
+--
+-- cannot use system columns in partition key
+CREATE TABLE fail_oid_pk (
+ a int
+) PARTITION BY RANGE (oid) WITH OIDS; -- fail
+ERROR: cannot use system column "oid" in partition key
+-- cannot use more than 1 column as partition key for list partitioned table
+CREATE TABLE two_col_tab (
+ a1 int,
+ a2 int
+) PARTITION BY LIST (a1, a2); -- fail
+ERROR: cannot use more than one column in partition key
+DETAIL: Only one column allowed with list partitioning.
+-- cannot use more than 32 columns as partition key for range partitioned table
+CREATE TABLE wide_tab (
+ a01 int, a02 int, a03 int, a04 int, a05 int, a06 int, a07 int, a08 int,
+ a09 int, a10 int, a11 int, a12 int, a13 int, a14 int, a15 int, a16 int,
+ a17 int, a18 int, a19 int, a20 int, a21 int, a22 int, a23 int, a24 int,
+ a25 int, a26 int, a27 int, a28 int, a29 int, a30 int, a31 int, a32 int,
+ a33 int
+) PARTITION BY RANGE (a01, a02, a03, a04, a05, a06, a07, a08,
+ a09, a10, a11, a12, a13, a14, a15, a16,
+ a17, a18, a19, a20, a21, a22, a23, a24,
+ a25, a26, a27, a28, a29, a30, a31, a32, a33); -- fail
+ERROR: cannot use more than 32 columns in partition key
+-- Partitioning requires to use columns with data type having a suitable
+-- btree operator class
+CREATE TABLE point_tab (
+ a point
+) PARTITION BY LIST (a); -- fail
+ERROR: data type point has no default btree operator class
+HINT: You must specify an existing btree operator class or define one for the type.
+CREATE TABLE point_tab (
+ a point
+) PARTITION BY LIST (a USING point_ops); -- fail
+ERROR: operator class "point_ops" does not exist for access method "btree"
+CREATE TABLE point_tab (
+ a point
+) PARTITION BY RANGE (a); -- fail
+ERROR: data type point has no default btree operator class
+HINT: You must specify an existing btree operator class or define one for the type.
+CREATE TABLE point_tab (
+ a point
+) PARTITION BY RANGE (a USING point_ops); -- fail
+ERROR: operator class "point_ops" does not exist for access method "btree"
+-- fail to create a partitioned table with PRIMARY KEY constraint
+CREATE TABLE pk_fail (
+ a int PRIMARY KEY
+) PARTITION BY RANGE (a);
+ERROR: primary key constraints are not supported on partitioned tables
+LINE 2: a int PRIMARY KEY
+ ^
+-- fail to create a partitioned table with foreign key constraint
+CREATE TABLE pk_rel(
+ a int PRIMARY KEY
+);
+CREATE TABLE fk_fail (
+ a int REFERENCES pk_rel(a)
+) PARTITION BY RANGE (a);
+ERROR: foreign key constraints are not supported on partitioned tables
+LINE 2: a int REFERENCES pk_rel(a)
+ ^
+DROP TABLE pk_rel;
+-- fail to create a partitioned table with prohibited expressions in key
+CREATE TABLE fail_const_key (
+ a int
+) PARTITION BY RANGE (('a'));
+ERROR: cannot use a constant expression as partition key
+CREATE TABLE fail_agg_in_key (
+ a int
+) PARTITION BY RANGE ((avg(a)));
+ERROR: aggregate functions are not allowed in partition key expressions
+CREATE TABLE fail_window_fun_in_key (
+ a int,
+ b int
+) PARTITION BY RANGE ((avg(a) OVER (PARTITION BY b)));
+ERROR: window functions are not allowed in partition key expressions
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 78bdc8b..df47656 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -269,3 +269,71 @@ DROP TABLE as_select1;
-- check that the oid column is added before the primary key is checked
CREATE TABLE oid_pk (f1 INT, PRIMARY KEY(oid)) WITH OIDS;
DROP TABLE oid_pk;
+
+--
+-- partitioned tables
+--
+
+-- cannot use system columns in partition key
+CREATE TABLE fail_oid_pk (
+ a int
+) PARTITION BY RANGE (oid) WITH OIDS; -- fail
+
+-- cannot use more than 1 column as partition key for list partitioned table
+CREATE TABLE two_col_tab (
+ a1 int,
+ a2 int
+) PARTITION BY LIST (a1, a2); -- fail
+
+-- cannot use more than 32 columns as partition key for range partitioned table
+CREATE TABLE wide_tab (
+ a01 int, a02 int, a03 int, a04 int, a05 int, a06 int, a07 int, a08 int,
+ a09 int, a10 int, a11 int, a12 int, a13 int, a14 int, a15 int, a16 int,
+ a17 int, a18 int, a19 int, a20 int, a21 int, a22 int, a23 int, a24 int,
+ a25 int, a26 int, a27 int, a28 int, a29 int, a30 int, a31 int, a32 int,
+ a33 int
+) PARTITION BY RANGE (a01, a02, a03, a04, a05, a06, a07, a08,
+ a09, a10, a11, a12, a13, a14, a15, a16,
+ a17, a18, a19, a20, a21, a22, a23, a24,
+ a25, a26, a27, a28, a29, a30, a31, a32, a33); -- fail
+
+-- Partitioning requires to use columns with data type having a suitable
+-- btree operator class
+CREATE TABLE point_tab (
+ a point
+) PARTITION BY LIST (a); -- fail
+CREATE TABLE point_tab (
+ a point
+) PARTITION BY LIST (a USING point_ops); -- fail
+CREATE TABLE point_tab (
+ a point
+) PARTITION BY RANGE (a); -- fail
+CREATE TABLE point_tab (
+ a point
+) PARTITION BY RANGE (a USING point_ops); -- fail
+
+-- fail to create a partitioned table with PRIMARY KEY constraint
+CREATE TABLE pk_fail (
+ a int PRIMARY KEY
+) PARTITION BY RANGE (a);
+
+-- fail to create a partitioned table with foreign key constraint
+CREATE TABLE pk_rel(
+ a int PRIMARY KEY
+);
+CREATE TABLE fk_fail (
+ a int REFERENCES pk_rel(a)
+) PARTITION BY RANGE (a);
+DROP TABLE pk_rel;
+
+-- fail to create a partitioned table with prohibited expressions in key
+CREATE TABLE fail_const_key (
+ a int
+) PARTITION BY RANGE (('a'));
+CREATE TABLE fail_agg_in_key (
+ a int
+) PARTITION BY RANGE ((avg(a)));
+CREATE TABLE fail_window_fun_in_key (
+ a int,
+ b int
+) PARTITION BY RANGE ((avg(a) OVER (PARTITION BY b)));
--
1.7.1
[text/x-diff] 0002-Add-a-IGNORE-dependency-type-v5.patch (3.0K, ../../573ECCC3.7080104@lab.ntt.co.jp/3-0002-Add-a-IGNORE-dependency-type-v5.patch)
download | inline diff:
From 2efcd65f5dd1ddb8b06565871661175023092584 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Fri, 13 May 2016 12:10:10 +0900
Subject: [PATCH 2/7] Add a IGNORE dependency type.
This new type of dependency is mainly to be used internally as a signal
that no dependency should be created between the objects in question.
Use this, for example, when there is a possibility that redundant self-
dependencies could be created in certain situations - imagine creation
of dependencies for objects referenced in expressions in the partition
key of a table; it is inevitable that vars of the table in question
would be encountered which must be ignored.
---
src/backend/catalog/dependency.c | 2 ++
src/backend/catalog/pg_depend.c | 3 +++
src/include/catalog/dependency.h | 8 +++++++-
3 files changed, 12 insertions(+), 1 deletions(-)
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index 04d7840..607274d 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -717,6 +717,7 @@ findDependentObjects(const ObjectAddress *object,
getObjectDescription(object));
break;
default:
+ Assert(foundDep->deptype != DEPENDENCY_IGNORE);
elog(ERROR, "unrecognized dependency type '%c' for %s",
foundDep->deptype, getObjectDescription(object));
break;
@@ -813,6 +814,7 @@ findDependentObjects(const ObjectAddress *object,
subflags = 0; /* keep compiler quiet */
break;
default:
+ Assert(foundDep->deptype != DEPENDENCY_IGNORE);
elog(ERROR, "unrecognized dependency type '%c' for %s",
foundDep->deptype, getObjectDescription(object));
subflags = 0; /* keep compiler quiet */
diff --git a/src/backend/catalog/pg_depend.c b/src/backend/catalog/pg_depend.c
index 7a0713e..6e71b44 100644
--- a/src/backend/catalog/pg_depend.c
+++ b/src/backend/catalog/pg_depend.c
@@ -65,6 +65,9 @@ recordMultipleDependencies(const ObjectAddress *depender,
bool nulls[Natts_pg_depend];
Datum values[Natts_pg_depend];
+ if (behavior == DEPENDENCY_IGNORE)
+ return; /* nothing to do */
+
if (nreferenced <= 0)
return; /* nothing to do */
diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h
index 09b36c5..d44edc6 100644
--- a/src/include/catalog/dependency.h
+++ b/src/include/catalog/dependency.h
@@ -67,6 +67,11 @@
* created only during initdb. The fields for the dependent object
* contain zeroes.
*
+ * DEPENDENCY_IGNORE ('g'): like DEPENDENCY_PIN, there is no dependent
+ * object; this type of entry is a signal that no dependency should be
+ * created between the objects in question. However, unlike pin,
+ * dependencies, these never make it to pg_depend.
+ *
* Other dependency flavors may be needed in future.
*/
@@ -77,7 +82,8 @@ typedef enum DependencyType
DEPENDENCY_INTERNAL = 'i',
DEPENDENCY_EXTENSION = 'e',
DEPENDENCY_AUTO_EXTENSION = 'x',
- DEPENDENCY_PIN = 'p'
+ DEPENDENCY_PIN = 'p',
+ DEPENDENCY_IGNORE = 'g'
} DependencyType;
/*
--
1.7.1
[text/x-diff] 0003-Infrastructure-for-creation-of-partitioned-tables-v5.patch (86.8K, ../../573ECCC3.7080104@lab.ntt.co.jp/4-0003-Infrastructure-for-creation-of-partitioned-tables-v5.patch)
download | inline diff:
From e0592095c0f054ecc938129176c25e74a3afa88e Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Mon, 22 Feb 2016 17:38:55 +0900
Subject: [PATCH 3/7] Infrastructure for creation of partitioned tables.
First, this adds a new system catalog pg_partitioned with:
partedrelid oid
partstrat char
partnatts int16
partattrs int2vector
partclass oidvector
partexprs pg_node_tree
There is a unique index on partedrelid and associated syscache PARTEDRELID.
Second, a new relkind RELKIND_PARTITIONED_REL is added to pg_class. Tables
created with CREATE TABLE PARTITION BY are of this relkind. Some special
properties:
* Has a pg_partitioned entry
* Access to them unconditionally overrides specified inheritance option
* Dropping or altering type of columns that are part of the partition key
is disallowed
* Changing inheritance (ALTER TABLE table INHERIT parent) and linking to
a type (ALTER TABLE table OF type) is disallowed
Partition key is represented using struct PartitionKey and built during the
first relcache build of a partitioned relation. It is stored in rd_partkey
using the cache context.
---
doc/src/sgml/catalogs.sgml | 99 ++++++
src/backend/access/common/reloptions.c | 2 +
src/backend/catalog/Makefile | 4 +-
src/backend/catalog/aclchk.c | 2 +
src/backend/catalog/heap.c | 19 +-
src/backend/catalog/objectaddress.c | 10 +-
src/backend/catalog/partition.c | 450 ++++++++++++++++++++++++++++
src/backend/commands/analyze.c | 2 +
src/backend/commands/copy.c | 6 +
src/backend/commands/indexcmds.c | 3 +-
src/backend/commands/lockcmds.c | 2 +-
src/backend/commands/policy.c | 2 +-
src/backend/commands/seclabel.c | 1 +
src/backend/commands/sequence.c | 1 +
src/backend/commands/tablecmds.c | 168 ++++++++++-
src/backend/commands/trigger.c | 7 +-
src/backend/commands/vacuum.c | 1 +
src/backend/executor/execMain.c | 2 +
src/backend/executor/nodeModifyTable.c | 1 +
src/backend/parser/parse_agg.c | 6 +-
src/backend/parser/parse_clause.c | 8 +
src/backend/parser/parse_expr.c | 2 +-
src/backend/parser/parse_relation.c | 6 +-
src/backend/parser/parse_utilcmd.c | 4 +-
src/backend/rewrite/rewriteDefine.c | 1 +
src/backend/rewrite/rewriteHandler.c | 1 +
src/backend/tcop/utility.c | 5 +-
src/backend/utils/adt/ruleutils.c | 147 +++++++++
src/backend/utils/cache/relcache.c | 14 +-
src/backend/utils/cache/syscache.c | 12 +
src/bin/pg_dump/pg_dump.c | 36 ++-
src/bin/pg_dump/pg_dump.h | 1 +
src/bin/psql/describe.c | 61 +++-
src/bin/psql/tab-complete.c | 6 +-
src/include/catalog/indexing.h | 3 +
src/include/catalog/partition.h | 55 ++++
src/include/catalog/pg_class.h | 1 +
src/include/catalog/pg_partitioned.h | 67 ++++
src/include/catalog/pg_proc.h | 2 +
src/include/commands/defrem.h | 4 +-
src/include/utils/builtins.h | 1 +
src/include/utils/rel.h | 8 +
src/include/utils/syscache.h | 1 +
src/test/regress/expected/create_table.out | 30 ++-
src/test/regress/expected/sanity_check.out | 1 +
src/test/regress/sql/create_table.sql | 21 ++
46 files changed, 1228 insertions(+), 58 deletions(-)
create mode 100644 src/backend/catalog/partition.c
create mode 100644 src/include/catalog/partition.h
create mode 100644 src/include/catalog/pg_partitioned.h
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index eac6671..652f779 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -224,6 +224,11 @@
</row>
<row>
+ <entry><link linkend="catalog-pg-partitioned"><structname>pg_partitioned</structname></link></entry>
+ <entry>information about partitioned tables, including the partition key</entry>
+ </row>
+
+ <row>
<entry><link linkend="catalog-pg-policy"><structname>pg_policy</structname></link></entry>
<entry>row-security policies</entry>
</row>
@@ -4672,6 +4677,100 @@
</sect1>
+ <sect1 id="catalog-pg-partitioned">
+ <title><structname>pg_partitioned</structname></title>
+
+ <indexterm zone="catalog-pg-partitioned">
+ <primary>pg_partitioned</primary>
+ </indexterm>
+
+ <para>
+ The catalog <structname>pg_partitioned</structname> stores information
+ about the partition key of partitioned tables.
+ </para>
+
+ <table>
+ <title><structname>pg_partitioned</> Columns</title>
+
+ <tgroup cols="4">
+ <thead>
+ <row>
+ <entry>Name</entry>
+ <entry>Type</entry>
+ <entry>References</entry>
+ <entry>Description</entry>
+ </row>
+ </thead>
+
+ <tbody>
+
+ <row>
+ <entry><structfield>partedrelid</structfield></entry>
+ <entry><type>oid</type></entry>
+ <entry><literal><link linkend="catalog-pg-class"><structname>pg_class</structname></link>.oid</literal></entry>
+ <entry>The OID of the <structname>pg_class</> entry for this partitioned table</entry>
+ </row>
+
+ <row>
+ <entry><structfield>partstrat</structfield></entry>
+ <entry><type>char</type></entry>
+ <entry></entry>
+ <entry>
+ Partitioning strategy (or method); <literal>l</> = list partitioned table,
+ <literal>r</> = range partitioned table
+ </entry>
+ </row>
+
+ <row>
+ <entry><structfield>partnatts</structfield></entry>
+ <entry><type>int2</type></entry>
+ <entry></entry>
+ <entry>The number of columns in partition key</entry>
+ </row>
+
+ <row>
+ <entry><structfield>partattrs</structfield></entry>
+ <entry><type>int2vector</type></entry>
+ <entry><literal><link linkend="catalog-pg-attribute"><structname>pg_attribute</structname></link>.attnum</literal></entry>
+ <entry>
+ This is an array of <structfield>partnatts</structfield> values that
+ indicate which table columns are used as partition key. For example,
+ a value of <literal>1 3</literal> would mean that the first and the
+ third table columns make up the partition key. A zero in this array
+ indicates that the corresponding partition key column is an expression
+ over the table columns, rather than a simple column reference.
+ </entry>
+ </row>
+
+ <row>
+ <entry><structfield>partclass</structfield></entry>
+ <entry><type>oidvector</type></entry>
+ <entry><literal><link linkend="catalog-pg-opclass"><structname>pg_opclass</structname></link>.oid</literal></entry>
+ <entry>
+ For each column in the partition key, this contains the OID of
+ the operator class to use. See
+ <link linkend="catalog-pg-opclass"><structname>pg_opclass</structname></link> for details.
+ </entry>
+ </row>
+
+ <row>
+ <entry><structfield>partexprs</structfield></entry>
+ <entry><type>pg_node_tree</type></entry>
+ <entry></entry>
+ <entry>
+ Expression trees (in <function>nodeToString()</function>
+ representation) for partition key columns that are not simple column
+ references. This is a list with one element for each zero
+ entry in <structfield>partkey</>. Null if all partition key columns
+ are simple references.
+ </entry>
+ </row>
+
+ </tbody>
+ </tgroup>
+ </table>
+ </sect1>
+
<sect1 id="catalog-pg-policy">
<title><structname>pg_policy</structname></title>
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 7448c7f..5a6777a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -925,6 +925,7 @@ extractRelOptions(HeapTuple tuple, TupleDesc tupdesc,
case RELKIND_RELATION:
case RELKIND_TOASTVALUE:
case RELKIND_MATVIEW:
+ case RELKIND_PARTITIONED_REL:
options = heap_reloptions(classForm->relkind, datum, false);
break;
case RELKIND_VIEW:
@@ -1376,6 +1377,7 @@ heap_reloptions(char relkind, Datum reloptions, bool validate)
return (bytea *) rdopts;
case RELKIND_RELATION:
case RELKIND_MATVIEW:
+ case RELKIND_PARTITIONED_REL:
return default_reloptions(reloptions, validate, RELOPT_KIND_HEAP);
default:
/* other relkinds are not supported */
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index 1ce7610..4dc6300 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -11,7 +11,7 @@ top_builddir = ../../..
include $(top_builddir)/src/Makefile.global
OBJS = catalog.o dependency.o heap.o index.o indexing.o namespace.o aclchk.o \
- objectaccess.o objectaddress.o pg_aggregate.o pg_collation.o \
+ objectaccess.o objectaddress.o partition.o pg_aggregate.o pg_collation.o \
pg_constraint.o pg_conversion.o \
pg_depend.o pg_enum.o pg_inherits.o pg_largeobject.o pg_namespace.o \
pg_operator.o pg_proc.o pg_range.o pg_db_role_setting.o pg_shdepend.o \
@@ -41,7 +41,7 @@ POSTGRES_BKI_SRCS = $(addprefix $(top_srcdir)/src/include/catalog/,\
pg_foreign_data_wrapper.h pg_foreign_server.h pg_user_mapping.h \
pg_foreign_table.h pg_policy.h pg_replication_origin.h \
pg_default_acl.h pg_init_privs.h pg_seclabel.h pg_shseclabel.h \
- pg_collation.h pg_range.h pg_transform.h \
+ pg_collation.h pg_range.h pg_transform.h pg_partitioned.h\
toasting.h indexing.h \
)
diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
index d074e85..1818506 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -762,6 +762,8 @@ objectsInSchemaToOids(GrantObjectType objtype, List *nspnames)
case ACL_OBJECT_RELATION:
objs = getRelationsInNamespace(namespaceId, RELKIND_RELATION);
objects = list_concat(objects, objs);
+ objs = getRelationsInNamespace(namespaceId, RELKIND_PARTITIONED_REL);
+ objects = list_concat(objects, objs);
objs = getRelationsInNamespace(namespaceId, RELKIND_VIEW);
objects = list_concat(objects, objs);
objs = getRelationsInNamespace(namespaceId, RELKIND_MATVIEW);
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index e997b57..932a48b 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -41,6 +41,7 @@
#include "catalog/heap.h"
#include "catalog/index.h"
#include "catalog/objectaccess.h"
+#include "catalog/partition.h"
#include "catalog/pg_attrdef.h"
#include "catalog/pg_collation.h"
#include "catalog/pg_constraint.h"
@@ -1101,9 +1102,10 @@ heap_create_with_catalog(const char *relname,
{
/* Use binary-upgrade override for pg_class.oid/relfilenode? */
if (IsBinaryUpgrade &&
- (relkind == RELKIND_RELATION || relkind == RELKIND_SEQUENCE ||
- relkind == RELKIND_VIEW || relkind == RELKIND_MATVIEW ||
- relkind == RELKIND_COMPOSITE_TYPE || relkind == RELKIND_FOREIGN_TABLE))
+ (relkind == RELKIND_RELATION || relkind == RELKIND_PARTITIONED_REL ||
+ relkind == RELKIND_SEQUENCE || relkind == RELKIND_VIEW ||
+ relkind == RELKIND_MATVIEW || relkind == RELKIND_COMPOSITE_TYPE ||
+ relkind == RELKIND_FOREIGN_TABLE))
{
if (!OidIsValid(binary_upgrade_next_heap_pg_class_oid))
ereport(ERROR,
@@ -1134,6 +1136,7 @@ heap_create_with_catalog(const char *relname,
switch (relkind)
{
case RELKIND_RELATION:
+ case RELKIND_PARTITIONED_REL:
case RELKIND_VIEW:
case RELKIND_MATVIEW:
case RELKIND_FOREIGN_TABLE:
@@ -1178,6 +1181,7 @@ heap_create_with_catalog(const char *relname,
* such is an implementation detail: toast tables, sequences and indexes.
*/
if (IsUnderPostmaster && (relkind == RELKIND_RELATION ||
+ relkind == RELKIND_PARTITIONED_REL ||
relkind == RELKIND_VIEW ||
relkind == RELKIND_MATVIEW ||
relkind == RELKIND_FOREIGN_TABLE ||
@@ -1353,7 +1357,8 @@ heap_create_with_catalog(const char *relname,
if (relpersistence == RELPERSISTENCE_UNLOGGED)
{
Assert(relkind == RELKIND_RELATION || relkind == RELKIND_MATVIEW ||
- relkind == RELKIND_TOASTVALUE);
+ relkind == RELKIND_TOASTVALUE || relkind == RELKIND_PARTITIONED_REL);
+
heap_create_init_fork(new_rel_desc);
}
@@ -1800,6 +1805,12 @@ heap_drop_with_catalog(Oid relid)
}
/*
+ * If a partitioned table, delete the pg_partitioned tuples.
+ */
+ if (rel->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+ RemovePartitionKeyByRelId(relid);
+
+ /*
* Schedule unlinking of the relation's physical files at commit.
*/
if (rel->rd_rel->relkind != RELKIND_VIEW &&
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index 1324461..215375e 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -1204,7 +1204,8 @@ get_relation_by_qualified_name(ObjectType objtype, List *objname,
RelationGetRelationName(relation))));
break;
case OBJECT_TABLE:
- if (relation->rd_rel->relkind != RELKIND_RELATION)
+ if (relation->rd_rel->relkind != RELKIND_RELATION &&
+ relation->rd_rel->relkind != RELKIND_PARTITIONED_REL)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not a table",
@@ -3252,6 +3253,10 @@ getRelationDescription(StringInfo buffer, Oid relid)
appendStringInfo(buffer, _("table %s"),
relname);
break;
+ case RELKIND_PARTITIONED_REL:
+ appendStringInfo(buffer, _("partitioned table %s"),
+ relname);
+ break;
case RELKIND_INDEX:
appendStringInfo(buffer, _("index %s"),
relname);
@@ -3708,6 +3713,9 @@ getRelationTypeDescription(StringInfo buffer, Oid relid, int32 objectSubId)
case RELKIND_RELATION:
appendStringInfoString(buffer, "table");
break;
+ case RELKIND_PARTITIONED_REL:
+ appendStringInfoString(buffer, "partitioned table");
+ break;
case RELKIND_INDEX:
appendStringInfoString(buffer, "index");
break;
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
new file mode 100644
index 0000000..c6c8dad
--- /dev/null
+++ b/src/backend/catalog/partition.c
@@ -0,0 +1,450 @@
+/*-------------------------------------------------------------------------
+ *
+ * partition.c
+ * Partitioning related utility functions.
+ *
+ * Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ * src/backend/utils/misc/partition.c
+ *
+ *-------------------------------------------------------------------------
+*/
+
+#include "postgres.h"
+
+#include "access/heapam.h"
+#include "access/htup_details.h"
+#include "access/nbtree.h"
+#include "catalog/dependency.h"
+#include "catalog/indexing.h"
+#include "catalog/objectaddress.h"
+#include "catalog/partition.h"
+#include "catalog/pg_collation.h"
+#include "catalog/pg_opclass.h"
+#include "catalog/pg_partitioned.h"
+#include "catalog/pg_type.h"
+#include "executor/executor.h"
+#include "miscadmin.h"
+#include "nodes/nodeFuncs.h"
+#include "optimizer/clauses.h"
+#include "optimizer/planmain.h"
+#include "storage/lmgr.h"
+#include "utils/array.h"
+#include "utils/builtins.h"
+#include "utils/datum.h"
+#include "utils/memutils.h"
+#include "utils/fmgroids.h"
+#include "utils/inval.h"
+#include "utils/lsyscache.h"
+#include "utils/syscache.h"
+
+static PartitionKey CopyPartitionKey(PartitionKey fromkey);
+static KeyTypeCollInfo *copy_key_type_coll_info(int nkeycols,
+ KeyTypeCollInfo *tcinfo);
+static void free_key_type_coll_info(KeyTypeCollInfo *tcinfo);
+
+/*
+ * StorePartitionKey
+ * Store the partition keys of rel into pg_partitioned catalog
+ */
+void
+StorePartitionKey(Relation rel,
+ char strategy,
+ int16 partnatts,
+ AttrNumber *partattrs,
+ List *partexprs,
+ Oid *partopclass)
+{
+ int i;
+ int2vector *partattrs_vec;
+ oidvector *partopclass_vec;
+ Datum partexprsDatum;
+ Relation pg_partitioned;
+ HeapTuple tuple;
+ Datum values[Natts_pg_partitioned];
+ bool nulls[Natts_pg_partitioned];
+ ObjectAddress myself;
+ ObjectAddress referenced;
+
+ Assert(rel->rd_rel->relkind == RELKIND_PARTITIONED_REL);
+
+ tuple = SearchSysCache1(PARTEDRELID,
+ ObjectIdGetDatum(RelationGetRelid(rel)));
+ /* Cannot already exist */
+ Assert(!HeapTupleIsValid(tuple));
+
+ /*
+ * Copy the partition key, opclass info into arrays (should we
+ * make the caller pass them like this to start with?)
+ */
+ partattrs_vec = buildint2vector(partattrs, partnatts);
+ partopclass_vec = buildoidvector(partopclass, partnatts);
+
+ /* Convert the partition key expressions (if any) to a text datum */
+ if (partexprs)
+ {
+ char *exprsString;
+
+ exprsString = nodeToString(partexprs);
+ partexprsDatum = CStringGetTextDatum(exprsString);
+ pfree(exprsString);
+ }
+ else
+ partexprsDatum = (Datum) 0;
+
+ pg_partitioned = heap_open(PartitionedRelationId, RowExclusiveLock);
+
+ MemSet(nulls, false, sizeof(nulls));
+
+ /* Only this can ever be NULL */
+ if (!partexprsDatum)
+ nulls[Anum_pg_partitioned_partexprs - 1] = true;
+
+ values[Anum_pg_partitioned_partedrelid - 1] = ObjectIdGetDatum(RelationGetRelid(rel));
+ values[Anum_pg_partitioned_partstrat - 1] = CharGetDatum(strategy);
+ values[Anum_pg_partitioned_partnatts - 1] = Int16GetDatum(partnatts);
+ values[Anum_pg_partitioned_partattrs - 1] = PointerGetDatum(partattrs_vec);
+ values[Anum_pg_partitioned_partclass - 1] = PointerGetDatum(partopclass_vec);
+ values[Anum_pg_partitioned_partexprs - 1] = partexprsDatum;
+
+ tuple = heap_form_tuple(RelationGetDescr(pg_partitioned), values, nulls);
+
+ simple_heap_insert(pg_partitioned, tuple);
+
+ /* Update the indexes on pg_partitioned */
+ CatalogUpdateIndexes(pg_partitioned, tuple);
+
+ /* Make this relation dependent on a few things: */
+ myself.classId = RelationRelationId;
+ myself.objectId = RelationGetRelid(rel);;
+ myself.objectSubId = 0;
+
+ /* Operator class per key column */
+ for (i = 0; i < partnatts; i++)
+ {
+ referenced.classId = OperatorClassRelationId;
+ referenced.objectId = partopclass[i];
+ referenced.objectSubId = 0;
+
+ recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+ }
+
+ /*
+ * Things inside key expressions ignoring the vars of the table which
+ * is achieved by asking the dependency-tracking system to ignore any
+ * dependencies to self (table) or more appropriately own attributes.
+ */
+ if (partexprs)
+ recordDependencyOnSingleRelExpr(&myself,
+ (Node *) partexprs,
+ RelationGetRelid(rel),
+ DEPENDENCY_NORMAL,
+ DEPENDENCY_IGNORE);
+ /* Tell world about the key */
+ CacheInvalidateRelcache(rel);
+
+ heap_close(pg_partitioned, RowExclusiveLock);
+ heap_freetuple(tuple);
+}
+
+/*
+ * RemovePartitionKeyByRelId
+ * Remove pg_partitioned entry for a relation
+ */
+void
+RemovePartitionKeyByRelId(Oid relid)
+{
+ Relation rel;
+ HeapTuple tuple;
+
+ rel = heap_open(PartitionedRelationId, RowExclusiveLock);
+
+ tuple = SearchSysCache1(PARTEDRELID, ObjectIdGetDatum(relid));
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "cache lookup failed for partition key of relation %u", relid);
+
+ simple_heap_delete(rel, &tuple->t_self);
+
+ /* Update the indexes on pg_partitioned */
+ CatalogUpdateIndexes(rel, tuple);
+
+ ReleaseSysCache(tuple);
+ heap_close(rel, RowExclusiveLock);
+}
+
+/*
+ * RelationBuildPartitionKey
+ * Build and attach to relcache partition key data of relation
+ *
+ * Note that the partition key data attached to a relcache entry must be
+ * stored CacheMemoryContext to ensure it survives as long as the relcache
+ * entry. But we should be running in a less long-lived working context.
+ * To avoid leaking cache memory if this routine fails partway through,
+ * we build in working memory and then copy the completed structure into
+ * cache memory.
+ */
+void
+RelationBuildPartitionKey(Relation relation)
+{
+ Form_pg_partitioned form;
+ Relation catalog;
+ HeapTuple tuple;
+ bool isnull;
+ int i;
+ PartitionKey key;
+ int2vector *partattrs;
+ oidvector *opclass;
+ KeyTypeCollInfo *tcinfo;
+ ListCell *partexprs_item;
+ Datum datum;
+ MemoryContext oldcxt;
+
+ tuple = SearchSysCache1(PARTEDRELID,
+ ObjectIdGetDatum(RelationGetRelid(relation)));
+ /*
+ * Happend when relcache is built before the partition key is stored in
+ * system catalog
+ */
+ if (!HeapTupleIsValid(tuple))
+ return;
+
+ form = (Form_pg_partitioned) GETSTRUCT(tuple);
+
+ /* Allocate in the hopefully short-lived working context */
+ key = (PartitionKey) palloc0(sizeof(PartitionKeyData));
+ key->strategy = form->partstrat;
+ key->partnatts = form->partnatts;
+
+ /* Open the catalog for its tuple descriptor */
+ catalog = heap_open(PartitionedRelationId, AccessShareLock);
+ datum = fastgetattr(tuple, Anum_pg_partitioned_partattrs,
+ RelationGetDescr(catalog),
+ &isnull);
+ Assert(!isnull);
+ partattrs = (int2vector *) DatumGetPointer(datum);
+
+ datum = fastgetattr(tuple, Anum_pg_partitioned_partclass,
+ RelationGetDescr(catalog),
+ &isnull);
+ Assert(!isnull);
+ opclass = (oidvector *) DatumGetPointer(datum);
+
+ datum = heap_getattr(tuple,
+ Anum_pg_partitioned_partexprs,
+ RelationGetDescr(catalog),
+ &isnull);
+
+ if (!isnull)
+ {
+ char *exprsString;
+ Node *exprs;
+
+ exprsString = TextDatumGetCString(datum);
+ exprs = stringToNode(exprsString);
+ pfree(exprsString);
+
+ /*
+ * Run the expressions through eval_const_expressions. This is
+ * not just an optimization, but is necessary, because eventually
+ * the planner will be comparing them to similarly-processed qual
+ * clauses, and may fail to detect valid matches without this.
+ * We don't bother with canonicalize_qual, however.
+ */
+ exprs = eval_const_expressions(NULL, (Node *) exprs);
+
+ /* May as well fix opfuncids too */
+ fix_opfuncids((Node *) exprs);
+ key->partexprs = (List *) exprs;
+ }
+
+ key->partattrs = (AttrNumber *) palloc0(key->partnatts * sizeof(AttrNumber));
+ key->partopfamily = (Oid *) palloc0(key->partnatts * sizeof(Oid));
+ key->partsupfunc = (FmgrInfo *) palloc0(key->partnatts * sizeof(FmgrInfo));
+
+ /* Gather type and collation info as well */
+ key->tcinfo = tcinfo = (KeyTypeCollInfo *) palloc0(sizeof(KeyTypeCollInfo));
+ tcinfo->typid = (Oid *) palloc0(key->partnatts * sizeof(Oid));
+ tcinfo->typmod = (int32 *) palloc0(key->partnatts * sizeof(int32));
+ tcinfo->typlen = (int16 *) palloc0(key->partnatts * sizeof(int16));
+ tcinfo->typbyval = (bool *) palloc0(key->partnatts * sizeof(bool));
+ tcinfo->typalign = (char *) palloc0(key->partnatts * sizeof(char));
+ tcinfo->typcoll = (Oid *) palloc0(key->partnatts * sizeof(Oid));
+
+ /*
+ * Copy partattrs. Further, determine and store the opfamily and
+ * btree support function per attribute.
+ */
+ partexprs_item = list_head(key->partexprs);
+ for (i = 0; i < key->partnatts; i++)
+ {
+ HeapTuple tuple;
+ AttrNumber attno;
+ Form_pg_opclass form;
+ Oid funcid;
+
+ key->partattrs[i] = attno = partattrs->values[i];
+
+ /* Collect type information */
+ if (attno != InvalidAttrNumber)
+ {
+ tcinfo->typid[i] = relation->rd_att->attrs[attno - 1]->atttypid;
+ tcinfo->typmod[i] = relation->rd_att->attrs[attno - 1]->atttypmod;
+ tcinfo->typcoll[i] = relation->rd_att->attrs[attno - 1]->attcollation;
+ }
+ else
+ {
+ tcinfo->typid[i] = exprType(lfirst(partexprs_item));
+ tcinfo->typmod[i] = exprTypmod(lfirst(partexprs_item));
+ tcinfo->typcoll[i] = exprCollation(lfirst(partexprs_item));
+ partexprs_item = lnext(partexprs_item);
+ }
+ get_typlenbyvalalign(tcinfo->typid[i],
+ &tcinfo->typlen[i],
+ &tcinfo->typbyval[i],
+ &tcinfo->typalign[i]);
+
+ tuple = SearchSysCache1(CLAOID, ObjectIdGetDatum(opclass->values[i]));
+
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "cache lookup failed for opclass %u", opclass->values[i]);
+
+ form = (Form_pg_opclass) GETSTRUCT(tuple);
+ key->partopfamily[i] = form->opcfamily;
+
+ /*
+ * Btree support function is good to cover the list and range
+ * partitionig related needs.
+ */
+ funcid = get_opfamily_proc(form->opcfamily,
+ form->opcintype, form->opcintype,
+ BTORDER_PROC);
+
+ fmgr_info(funcid, &key->partsupfunc[i]);
+ ReleaseSysCache(tuple);
+ }
+
+ ReleaseSysCache(tuple);
+ heap_close(catalog, AccessShareLock);
+
+ /* Success --- now copy to the cache memory */
+ oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
+ relation->rd_partkey = CopyPartitionKey(key);
+ MemoryContextSwitchTo(oldcxt);
+
+ FreePartitionKey(key);
+}
+
+/*
+ * CopyPartitionKey
+ *
+ * The copy is allocated in the current memory context.
+ */
+static PartitionKey
+CopyPartitionKey(PartitionKey fromkey)
+{
+ PartitionKey newkey;
+
+ newkey = (PartitionKey) palloc0(sizeof(PartitionKeyData));
+
+ newkey->strategy = fromkey->strategy;
+ newkey->partnatts = fromkey->partnatts;
+
+ newkey->partattrs = (AttrNumber *)
+ palloc(newkey->partnatts * sizeof(AttrNumber));
+ memcpy(newkey->partattrs, fromkey->partattrs,
+ newkey->partnatts * sizeof(AttrNumber));
+
+ newkey->partopfamily = (Oid *) palloc(newkey->partnatts * sizeof(Oid));
+ memcpy(newkey->partopfamily, fromkey->partopfamily,
+ newkey->partnatts * sizeof(Oid));
+
+ newkey->partsupfunc = (FmgrInfo *)
+ palloc(newkey->partnatts * sizeof(FmgrInfo));
+ memcpy(newkey->partsupfunc, fromkey->partsupfunc,
+ newkey->partnatts * sizeof(FmgrInfo));
+
+ newkey->partexprs = copyObject(fromkey->partexprs);
+ newkey->tcinfo = copy_key_type_coll_info(newkey->partnatts,
+ fromkey->tcinfo);
+
+ return newkey;
+}
+
+/*
+ * copy_key_type_coll_info
+ *
+ * The copy is allocated in the current memory context.
+ */
+static KeyTypeCollInfo *
+copy_key_type_coll_info(int nkeycols, KeyTypeCollInfo *tcinfo)
+{
+ KeyTypeCollInfo *result = (KeyTypeCollInfo *)
+ palloc0(sizeof(KeyTypeCollInfo));
+
+ result->typid = (Oid *) palloc0(nkeycols * sizeof(Oid));
+ memcpy(result->typid, tcinfo->typid, nkeycols * sizeof(Oid));
+
+ result->typmod = (int32 *) palloc0(nkeycols * sizeof(int32));
+ memcpy(result->typmod, tcinfo->typmod, nkeycols * sizeof(int32));
+
+ result->typlen = (int16 *) palloc0(nkeycols * sizeof(int16));
+ memcpy(result->typlen, tcinfo->typlen, nkeycols * sizeof(int16));
+
+ result->typbyval = (bool *) palloc0(nkeycols * sizeof(bool));
+ memcpy(result->typbyval, tcinfo->typbyval, nkeycols * sizeof(bool));
+
+ result->typalign = (char *) palloc0(nkeycols * sizeof(bool));
+ memcpy(result->typalign, tcinfo->typalign, nkeycols * sizeof(char));
+
+ result->typcoll = (Oid *) palloc0(nkeycols * sizeof(Oid));
+ memcpy(result->typcoll, tcinfo->typcoll, nkeycols * sizeof(Oid));
+
+ return result;
+}
+
+/*
+ * free_key_type_info
+ */
+static void
+free_key_type_coll_info(KeyTypeCollInfo *tcinfo)
+{
+ Assert(tcinfo != NULL);
+
+ pfree(tcinfo->typid);
+ pfree(tcinfo->typmod);
+ pfree(tcinfo->typlen);
+ pfree(tcinfo->typbyval);
+ pfree(tcinfo->typalign);
+ pfree(tcinfo->typcoll);
+ pfree(tcinfo);
+}
+
+/*
+ * Does relid have a pg_partitioned entry?
+ */
+bool
+relid_is_partitioned(Oid relid)
+{
+ return SearchSysCacheExists1(PARTEDRELID, ObjectIdGetDatum(relid));
+}
+
+/*
+ * FreePartitionKeys
+ */
+void
+FreePartitionKey(PartitionKey key)
+{
+ if (key == NULL)
+ return;
+
+ pfree(key->partattrs);
+ pfree(key->partopfamily);
+ pfree(key->partsupfunc);
+ if (key->partexprs)
+ pfree(key->partexprs);
+ free_key_type_coll_info(key->tcinfo);
+ pfree(key);
+}
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index cf8c816..8b6b570 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -201,6 +201,7 @@ analyze_rel(Oid relid, RangeVar *relation, int options,
* locked the relation.
*/
if (onerel->rd_rel->relkind == RELKIND_RELATION ||
+ onerel->rd_rel->relkind == RELKIND_PARTITIONED_REL ||
onerel->rd_rel->relkind == RELKIND_MATVIEW)
{
/* Regular table, so we'll use the regular row acquisition function */
@@ -1320,6 +1321,7 @@ acquire_inherited_sample_rows(Relation onerel, int elevel,
/* Check table type (MATVIEW can't happen, but might as well allow) */
if (childrel->rd_rel->relkind == RELKIND_RELATION ||
+ childrel->rd_rel->relkind == RELKIND_PARTITIONED_REL ||
childrel->rd_rel->relkind == RELKIND_MATVIEW)
{
/* Regular table, so use the regular row acquisition function */
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 3201476..403738d 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -1718,6 +1718,12 @@ BeginCopyTo(Relation rel,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot copy from sequence \"%s\"",
RelationGetRelationName(rel))));
+ else if (rel->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot copy from partitioned table \"%s\"",
+ RelationGetRelationName(rel)),
+ errhint("Try the COPY (SELECT ...) TO variant.")));
else
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 4ef6b80..e8d25e8 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -369,7 +369,8 @@ DefineIndex(Oid relationId,
namespaceId = RelationGetNamespace(rel);
if (rel->rd_rel->relkind != RELKIND_RELATION &&
- rel->rd_rel->relkind != RELKIND_MATVIEW)
+ rel->rd_rel->relkind != RELKIND_MATVIEW &&
+ rel->rd_rel->relkind != RELKIND_PARTITIONED_REL)
{
if (rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
diff --git a/src/backend/commands/lockcmds.c b/src/backend/commands/lockcmds.c
index 175d1f3..62671a8 100644
--- a/src/backend/commands/lockcmds.c
+++ b/src/backend/commands/lockcmds.c
@@ -88,7 +88,7 @@ RangeVarCallbackForLockTable(const RangeVar *rv, Oid relid, Oid oldrelid,
* check */
/* Currently, we only allow plain tables to be locked */
- if (relkind != RELKIND_RELATION)
+ if (relkind != RELKIND_RELATION && relkind != RELKIND_PARTITIONED_REL)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not a table",
diff --git a/src/backend/commands/policy.c b/src/backend/commands/policy.c
index 93d15e4..de75da1 100644
--- a/src/backend/commands/policy.c
+++ b/src/backend/commands/policy.c
@@ -88,7 +88,7 @@ RangeVarCallbackForPolicy(const RangeVar *rv, Oid relid, Oid oldrelid,
rv->relname)));
/* Relation type MUST be a table. */
- if (relkind != RELKIND_RELATION)
+ if (relkind != RELKIND_RELATION && relkind != RELKIND_PARTITIONED_REL)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not a table", rv->relname)));
diff --git a/src/backend/commands/seclabel.c b/src/backend/commands/seclabel.c
index 5bd7e12..8bc8638 100644
--- a/src/backend/commands/seclabel.c
+++ b/src/backend/commands/seclabel.c
@@ -107,6 +107,7 @@ ExecSecLabelStmt(SecLabelStmt *stmt)
* are the only relkinds for which pg_dump will dump labels).
*/
if (relation->rd_rel->relkind != RELKIND_RELATION &&
+ relation->rd_rel->relkind != RELKIND_PARTITIONED_REL &&
relation->rd_rel->relkind != RELKIND_VIEW &&
relation->rd_rel->relkind != RELKIND_MATVIEW &&
relation->rd_rel->relkind != RELKIND_COMPOSITE_TYPE &&
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index c98f981..5dcd4cf 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -1467,6 +1467,7 @@ process_owned_by(Relation seqrel, List *owned_by)
/* Must be a regular or foreign table */
if (!(tablerel->rd_rel->relkind == RELKIND_RELATION ||
+ tablerel->rd_rel->relkind == RELKIND_PARTITIONED_REL ||
tablerel->rd_rel->relkind == RELKIND_FOREIGN_TABLE))
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index d03147d..87bf466 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -29,6 +29,7 @@
#include "catalog/indexing.h"
#include "catalog/namespace.h"
#include "catalog/objectaccess.h"
+#include "catalog/partition.h"
#include "catalog/pg_am.h"
#include "catalog/pg_collation.h"
#include "catalog/pg_constraint.h"
@@ -262,6 +263,12 @@ struct DropRelationCallbackState
bool concurrent;
};
+/* for find_attr_reference_walker */
+typedef struct
+{
+ AttrNumber attnum;
+} find_attr_reference_context;
+
/* Alter table target-type flags for ATSimplePermissions */
#define ATT_TABLE 0x0001
#define ATT_VIEW 0x0002
@@ -438,6 +445,9 @@ static void ComputePartitionAttrs(Oid relid, List *partParams,
AttrNumber *partattrs,
List **partexprs,
Oid *partoplass);
+static bool find_attr_reference_walker(Node *node,
+ find_attr_reference_context *context);
+static bool is_partition_attr(Relation rel, AttrNumber attnum, bool *is_expr);
/* ----------------------------------------------------------------
@@ -601,7 +611,8 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
* affect other relkinds, but it would complicate interpretOidsOption().
*/
localHasOids = interpretOidsOption(stmt->options,
- (relkind == RELKIND_RELATION));
+ (relkind == RELKIND_RELATION ||
+ relkind == RELKIND_PARTITIONED_REL));
descriptor->tdhasoid = (localHasOids || parentOidCount > 0);
/*
@@ -715,7 +726,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
AddRelationNewConstraints(rel, rawDefaults, stmt->constraints,
true, true, false);
- /* Process partition key, if any */
+ /* Process and store partition key, if any */
if (stmt->partby)
{
PartitionBy *partby;
@@ -728,6 +739,8 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
partby = transformPartitionBy(rel, stmt->partby);
ComputePartitionAttrs(relationId, stmt->partby->partParams,
partattrs, &partexprs, partopclass);
+ StorePartitionKey(rel, partby->strategy, partnatts, partattrs,
+ partexprs, partopclass);
}
ObjectAddressSet(address, RelationRelationId, relationId);
@@ -975,7 +988,15 @@ RangeVarCallbackForDropRelation(const RangeVar *rel, Oid relOid, Oid oldRelOid,
return; /* concurrently dropped, so nothing to do */
classform = (Form_pg_class) GETSTRUCT(tuple);
- if (classform->relkind != relkind)
+ /*
+ * Both normal and partitioned tables are dropped using DROP TABLE.
+ * RemoveRelations however never passes RELKIND_PARTITIONED_REL as
+ * relkind for OBJECT_TABLE relations. A mismatch below may have
+ * to do with that. So, check.
+ */
+ if (classform->relkind != relkind &&
+ (relkind == RELKIND_RELATION &&
+ classform->relkind != RELKIND_PARTITIONED_REL))
DropErrorMsgWrongType(rel->relname, classform->relkind, relkind);
/* Allow DROP to either table owner or schema owner */
@@ -1313,7 +1334,8 @@ truncate_check_rel(Relation rel)
AclResult aclresult;
/* Only allow truncate on regular tables */
- if (rel->rd_rel->relkind != RELKIND_RELATION)
+ if (rel->rd_rel->relkind != RELKIND_RELATION &&
+ rel->rd_rel->relkind != RELKIND_PARTITIONED_REL)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not a table",
@@ -1541,8 +1563,16 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
*/
relation = heap_openrv(parent, ShareUpdateExclusiveLock);
+ /* Cannot inherit from partitioned tables */
+ if (relation->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot inherit from partitioned table \"%s\"",
+ parent->relname)));
+
if (relation->rd_rel->relkind != RELKIND_RELATION &&
- relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
+ relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE &&
+ relation->rd_rel->relkind != RELKIND_PARTITIONED_REL)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("inherited relation \"%s\" is not a table or foreign table",
@@ -2182,6 +2212,7 @@ renameatt_check(Oid myrelid, Form_pg_class classform, bool recursing)
* restriction.
*/
if (relkind != RELKIND_RELATION &&
+ relkind != RELKIND_PARTITIONED_REL &&
relkind != RELKIND_VIEW &&
relkind != RELKIND_MATVIEW &&
relkind != RELKIND_COMPOSITE_TYPE &&
@@ -4311,6 +4342,7 @@ ATSimplePermissions(Relation rel, int allowed_targets)
switch (rel->rd_rel->relkind)
{
case RELKIND_RELATION:
+ case RELKIND_PARTITIONED_REL:
actual_target = ATT_TABLE;
break;
case RELKIND_VIEW:
@@ -4547,6 +4579,7 @@ find_composite_type_dependencies(Oid typeOid, Relation origRelation,
att = rel->rd_att->attrs[pg_depend->objsubid - 1];
if (rel->rd_rel->relkind == RELKIND_RELATION ||
+ rel->rd_rel->relkind == RELKIND_PARTITIONED_REL ||
rel->rd_rel->relkind == RELKIND_MATVIEW)
{
if (origTypeName)
@@ -5437,6 +5470,7 @@ ATPrepSetStatistics(Relation rel, const char *colName, Node *newValue, LOCKMODE
* allowSystemTableMods to be turned on.
*/
if (rel->rd_rel->relkind != RELKIND_RELATION &&
+ rel->rd_rel->relkind != RELKIND_PARTITIONED_REL &&
rel->rd_rel->relkind != RELKIND_MATVIEW &&
rel->rd_rel->relkind != RELKIND_INDEX &&
rel->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
@@ -5711,6 +5745,73 @@ ATPrepDropColumn(List **wqueue, Relation rel, bool recurse, bool recursing,
cmd->subtype = AT_DropColumnRecurse;
}
+/* Checks if a Var node is for a given attnum */
+static bool
+find_attr_reference_walker(Node *node, find_attr_reference_context *context)
+{
+ if (node == NULL)
+ return false;
+
+ if (IsA(node, Var))
+ {
+ Var *variable = (Var *) node;
+ AttrNumber attnum = variable->varattno;
+
+ if (attnum == context->attnum)
+ return true;
+ }
+
+ return expression_tree_walker(node, find_attr_reference_walker, context);
+}
+
+/*
+ * Checks if attnum is a partition attribute for rel
+ *
+ * Sets *is_expr if attnum is found to be referenced in some partition key
+ * expression.
+ */
+static bool
+is_partition_attr(Relation rel, AttrNumber attnum, bool *is_expr)
+{
+ PartitionKey key;
+ ListCell *partexpr_item;
+ int i;
+
+ if (rel->rd_rel->relkind != RELKIND_PARTITIONED_REL)
+ {
+ *is_expr = false;
+ return false;
+ }
+
+ key = RelationGetPartitionKey(rel);
+
+ partexpr_item = list_head(key->partexprs);
+ for (i = 0; i < key->partnatts; i++)
+ {
+ AttrNumber partatt = key->partattrs[i];
+
+ if(partatt != 0)
+ {
+ *is_expr = false;
+ if (attnum == partatt)
+ return true;
+ }
+ else
+ {
+ find_attr_reference_context context;
+
+ *is_expr = true;
+ context.attnum = attnum;
+ if (find_attr_reference_walker(lfirst(partexpr_item), &context))
+ return true;
+
+ partexpr_item = lnext(partexpr_item);
+ }
+ }
+
+ return false;
+}
+
/*
* Return value is the address of the dropped column.
*/
@@ -5725,6 +5826,7 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
AttrNumber attnum;
List *children;
ObjectAddress object;
+ bool is_expr;
/* At top level, permission check was done in ATPrepCmd, else do it */
if (recursing)
@@ -5769,6 +5871,19 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
errmsg("cannot drop inherited column \"%s\"",
colName)));
+ /* Don't drop columns used in partition key */
+ if (is_partition_attr(rel, attnum, &is_expr))
+ {
+ if (!is_expr)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("cannot drop column named in partition key")));
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("cannot drop column referenced in partition key expression")));
+ }
+
ReleaseSysCache(tuple);
/*
@@ -6287,6 +6402,12 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
* Validity checks (permission checks wait till we have the column
* numbers)
*/
+ if (pkrel->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot reference relation \"%s\"", RelationGetRelationName(pkrel)),
+ errdetail("Referencing partitioned tables in foreign key constraints is not supported.")));
+
if (pkrel->rd_rel->relkind != RELKIND_RELATION)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
@@ -7881,6 +8002,7 @@ ATPrepAlterColumnType(List **wqueue,
NewColumnValue *newval;
ParseState *pstate = make_parsestate(NULL);
AclResult aclresult;
+ bool is_expr;
if (rel->rd_rel->reloftype && !recursing)
ereport(ERROR,
@@ -7911,6 +8033,19 @@ ATPrepAlterColumnType(List **wqueue,
errmsg("cannot alter inherited column \"%s\"",
colName)));
+ /* Don't alter columns used in partition key */
+ if (is_partition_attr(rel, attnum, &is_expr))
+ {
+ if (!is_expr)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("cannot alter column named in partition key")));
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("cannot alter column referenced in partition key expression")));
+ }
+
/* Look up the target type */
typenameTypeIdAndMod(NULL, typeName, &targettype, &targettypmod);
@@ -7926,7 +8061,8 @@ ATPrepAlterColumnType(List **wqueue,
list_make1_oid(rel->rd_rel->reltype),
false);
- if (tab->relkind == RELKIND_RELATION)
+ if (tab->relkind == RELKIND_RELATION ||
+ tab->relkind == RELKIND_PARTITIONED_REL)
{
/*
* Set up an expression to transform the old data value to the new
@@ -8953,6 +9089,7 @@ ATExecChangeOwner(Oid relationOid, Oid newOwnerId, bool recursing, LOCKMODE lock
switch (tuple_class->relkind)
{
case RELKIND_RELATION:
+ case RELKIND_PARTITIONED_REL:
case RELKIND_VIEW:
case RELKIND_MATVIEW:
case RELKIND_FOREIGN_TABLE:
@@ -9415,6 +9552,7 @@ ATExecSetRelOptions(Relation rel, List *defList, AlterTableType operation,
switch (rel->rd_rel->relkind)
{
case RELKIND_RELATION:
+ case RELKIND_PARTITIONED_REL:
case RELKIND_TOASTVALUE:
case RELKIND_MATVIEW:
(void) heap_reloptions(rel->rd_rel->relkind, newOptions, true);
@@ -9837,7 +9975,8 @@ AlterTableMoveAll(AlterTableMoveAllStmt *stmt)
/* Only move the object type requested */
if ((stmt->objtype == OBJECT_TABLE &&
- relForm->relkind != RELKIND_RELATION) ||
+ relForm->relkind != RELKIND_RELATION &&
+ relForm->relkind != RELKIND_PARTITIONED_REL) ||
(stmt->objtype == OBJECT_INDEX &&
relForm->relkind != RELKIND_INDEX) ||
(stmt->objtype == OBJECT_MATVIEW &&
@@ -10036,6 +10175,11 @@ ATPrepAddInherit(Relation child_rel)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot change inheritance of typed table")));
+
+ if (child_rel->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot change inheritance of partitioned table")));
}
/*
@@ -10087,6 +10231,12 @@ ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode)
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot inherit to temporary relation of another session")));
+ /* Prevent partitioned tables from becoming inheritance parents */
+ if (parent_rel->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot inherit from partitioned table \"%s\"", parent->relname)));
+
/*
* Check for duplicates in the list of parents, and determine the highest
* inhseqno already present; we'll use the next one for the new parent.
@@ -11465,6 +11615,7 @@ AlterTableNamespaceInternal(Relation rel, Oid oldNspOid, Oid nspOid,
/* Fix other dependent stuff */
if (rel->rd_rel->relkind == RELKIND_RELATION ||
+ rel->rd_rel->relkind == RELKIND_PARTITIONED_REL ||
rel->rd_rel->relkind == RELKIND_MATVIEW)
{
AlterIndexNamespaces(classRel, rel, oldNspOid, nspOid, objsMoved);
@@ -11914,7 +12065,7 @@ RangeVarCallbackOwnsTable(const RangeVar *relation,
if (!relkind)
return;
if (relkind != RELKIND_RELATION && relkind != RELKIND_TOASTVALUE &&
- relkind != RELKIND_MATVIEW)
+ relkind != RELKIND_MATVIEW && relkind != RELKIND_PARTITIONED_REL)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not a table or materialized view", relation->relname)));
@@ -12068,6 +12219,7 @@ RangeVarCallbackForAlterRelation(const RangeVar *rv, Oid relid, Oid oldrelid,
*/
if (IsA(stmt, AlterObjectSchemaStmt) &&
relkind != RELKIND_RELATION &&
+ relkind != RELKIND_PARTITIONED_REL &&
relkind != RELKIND_VIEW &&
relkind != RELKIND_MATVIEW &&
relkind != RELKIND_SEQUENCE &&
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index 6f728ff..c1e1c4d 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -174,7 +174,8 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
* Triggers must be on tables or views, and there are additional
* relation-type-specific restrictions.
*/
- if (rel->rd_rel->relkind == RELKIND_RELATION)
+ if (rel->rd_rel->relkind == RELKIND_RELATION ||
+ rel->rd_rel->relkind == RELKIND_PARTITIONED_REL)
{
/* Tables can't have INSTEAD OF triggers */
if (stmt->timing != TRIGGER_TYPE_BEFORE &&
@@ -1112,6 +1113,7 @@ RemoveTriggerById(Oid trigOid)
rel = heap_open(relid, AccessExclusiveLock);
if (rel->rd_rel->relkind != RELKIND_RELATION &&
+ rel->rd_rel->relkind != RELKIND_PARTITIONED_REL &&
rel->rd_rel->relkind != RELKIND_VIEW &&
rel->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
ereport(ERROR,
@@ -1218,7 +1220,8 @@ RangeVarCallbackForRenameTrigger(const RangeVar *rv, Oid relid, Oid oldrelid,
/* only tables and views can have triggers */
if (form->relkind != RELKIND_RELATION && form->relkind != RELKIND_VIEW &&
- form->relkind != RELKIND_FOREIGN_TABLE)
+ form->relkind != RELKIND_FOREIGN_TABLE &&
+ form->relkind != RELKIND_PARTITIONED_REL)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not a table, view, or foreign table",
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 93361a0..a30b8b6 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -1298,6 +1298,7 @@ vacuum_rel(Oid relid, RangeVar *relation, int options, VacuumParams *params)
* relation.
*/
if (onerel->rd_rel->relkind != RELKIND_RELATION &&
+ onerel->rd_rel->relkind != RELKIND_PARTITIONED_REL &&
onerel->rd_rel->relkind != RELKIND_MATVIEW &&
onerel->rd_rel->relkind != RELKIND_TOASTVALUE)
{
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index ac02304..29073c0 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -1019,6 +1019,7 @@ CheckValidResultRel(Relation resultRel, CmdType operation)
switch (resultRel->rd_rel->relkind)
{
case RELKIND_RELATION:
+ case RELKIND_PARTITIONED_REL:
/* OK */
break;
case RELKIND_SEQUENCE:
@@ -1152,6 +1153,7 @@ CheckValidRowMarkRel(Relation rel, RowMarkType markType)
switch (rel->rd_rel->relkind)
{
case RELKIND_RELATION:
+ case RELKIND_PARTITIONED_REL:
/* OK */
break;
case RELKIND_SEQUENCE:
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index e62c8aa..7ad1140 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1871,6 +1871,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
relkind = resultRelInfo->ri_RelationDesc->rd_rel->relkind;
if (relkind == RELKIND_RELATION ||
+ relkind == RELKIND_PARTITIONED_REL ||
relkind == RELKIND_MATVIEW)
{
j->jf_junkAttNo = ExecFindJunkAttribute(j, "ctid");
diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c
index b56fd4f..77ffd84 100644
--- a/src/backend/parser/parse_agg.c
+++ b/src/backend/parser/parse_agg.c
@@ -488,9 +488,9 @@ check_agglevels_and_constraints(ParseState *pstate, Node *expr)
break;
case EXPR_KIND_PARTITION_KEY:
if (isAgg)
- err = _("aggregate functions are not allowed in partition key expressions");
+ err = _("aggregate functions are not allowed in partition key expression");
else
- err = _("grouping operations are not allowed in partition key expressions");
+ err = _("grouping operations are not allowed in partition key expression");
break;
@@ -852,7 +852,7 @@ transformWindowFuncCall(ParseState *pstate, WindowFunc *wfunc,
err = _("window functions are not allowed in trigger WHEN conditions");
break;
case EXPR_KIND_PARTITION_KEY:
- err = _("window functions are not allowed in partition key expressions");
+ err = _("window functions are not allowed in partition key expression");
break;
/*
diff --git a/src/backend/parser/parse_clause.c b/src/backend/parser/parse_clause.c
index 751de4b..307e383 100644
--- a/src/backend/parser/parse_clause.c
+++ b/src/backend/parser/parse_clause.c
@@ -21,6 +21,7 @@
#include "access/tsmapi.h"
#include "catalog/catalog.h"
#include "catalog/heap.h"
+#include "catalog/partition.h"
#include "catalog/pg_am.h"
#include "catalog/pg_constraint_fn.h"
#include "catalog/pg_type.h"
@@ -200,6 +201,13 @@ setTargetTable(ParseState *pstate, RangeVar *relation,
relation->alias, inh, false);
pstate->p_target_rangetblentry = rte;
+ /*
+ * Override specified inheritance option, if relation is a partitioned
+ * table and it's a target of INSERT (ie, alsoSource == false).
+ */
+ if (alsoSource)
+ rte->inh |= relid_is_partitioned(rte->relid);
+
/* assume new rte is at end */
rtindex = list_length(pstate->p_rtable);
Assert(rte == rt_fetch(rtindex, pstate->p_rtable));
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index b53b7f1..147c6da 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -1724,7 +1724,7 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
err = _("cannot use subquery in trigger WHEN condition");
break;
case EXPR_KIND_PARTITION_KEY:
- err = _("cannot use subquery in partition key expressions");
+ err = _("cannot use subquery in partition key expression");
break;
/*
diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c
index 81332b5..abcc1d6 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -20,6 +20,7 @@
#include "access/sysattr.h"
#include "catalog/heap.h"
#include "catalog/namespace.h"
+#include "catalog/partition.h"
#include "catalog/pg_type.h"
#include "funcapi.h"
#include "nodes/makefuncs.h"
@@ -1216,9 +1217,12 @@ addRangeTableEntry(ParseState *pstate,
*
* The initial default on access checks is always check-for-READ-access,
* which is the right thing for all except target tables.
+ *
+ * Note: we override the specified inheritance option, if relation is a
+ * partitioned table.
*/
rte->lateral = false;
- rte->inh = inh;
+ rte->inh = inh || relid_is_partitioned(RelationGetRelid(rel));
rte->inFromCl = inFromCl;
rte->requiredPerms = ACL_SELECT;
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e267f74..465597d 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -33,6 +33,7 @@
#include "catalog/heap.h"
#include "catalog/index.h"
#include "catalog/namespace.h"
+#include "catalog/partition.h"
#include "catalog/pg_am.h"
#include "catalog/pg_collation.h"
#include "catalog/pg_constraint.h"
@@ -821,6 +822,7 @@ transformTableLikeClause(CreateStmtContext *cxt, TableLikeClause *table_like_cla
relation = relation_openrv(table_like_clause->relation, AccessShareLock);
if (relation->rd_rel->relkind != RELKIND_RELATION &&
+ relation->rd_rel->relkind != RELKIND_PARTITIONED_REL &&
relation->rd_rel->relkind != RELKIND_VIEW &&
relation->rd_rel->relkind != RELKIND_MATVIEW &&
relation->rd_rel->relkind != RELKIND_COMPOSITE_TYPE &&
@@ -2577,7 +2579,7 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
cxt.blist = NIL;
cxt.alist = NIL;
cxt.pkey = NULL;
- cxt.ispartitioned = false;
+ cxt.ispartitioned = relid_is_partitioned(relid);
/*
* The only subtypes that currently require parse transformation handling
diff --git a/src/backend/rewrite/rewriteDefine.c b/src/backend/rewrite/rewriteDefine.c
index 96f3f37..db80c90 100644
--- a/src/backend/rewrite/rewriteDefine.c
+++ b/src/backend/rewrite/rewriteDefine.c
@@ -260,6 +260,7 @@ DefineQueryRewrite(char *rulename,
* blocks them for users. Don't mention them in the error message.
*/
if (event_relation->rd_rel->relkind != RELKIND_RELATION &&
+ event_relation->rd_rel->relkind != RELKIND_PARTITIONED_REL &&
event_relation->rd_rel->relkind != RELKIND_MATVIEW &&
event_relation->rd_rel->relkind != RELKIND_VIEW)
ereport(ERROR,
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index a22a11e..f429dd6 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -1222,6 +1222,7 @@ rewriteTargetListUD(Query *parsetree, RangeTblEntry *target_rte,
TargetEntry *tle;
if (target_relation->rd_rel->relkind == RELKIND_RELATION ||
+ target_relation->rd_rel->relkind == RELKIND_PARTITIONED_REL ||
target_relation->rd_rel->relkind == RELKIND_MATVIEW)
{
/*
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index ac50c2a..220cbfc 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -975,10 +975,13 @@ ProcessUtilitySlow(Node *parsetree,
{
Datum toast_options;
static char *validnsps[] = HEAP_RELOPT_NAMESPACES;
+ char relkind = ((CreateStmt *) stmt)->partby != NULL
+ ? RELKIND_PARTITIONED_REL
+ : RELKIND_RELATION;
/* Create the table itself */
address = DefineRelation((CreateStmt *) stmt,
- RELKIND_RELATION,
+ relkind,
InvalidOid, NULL);
EventTriggerCollectSimpleCommand(address,
secondaryObject,
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 8cb3075..a9e077f 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -33,6 +33,7 @@
#include "catalog/pg_language.h"
#include "catalog/pg_opclass.h"
#include "catalog/pg_operator.h"
+#include "catalog/pg_partitioned.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_trigger.h"
#include "catalog/pg_type.h"
@@ -315,6 +316,7 @@ static char *pg_get_indexdef_worker(Oid indexrelid, int colno,
const Oid *excludeOps,
bool attrsOnly, bool showTblSpc,
int prettyFlags);
+static char *pg_get_partkeydef_worker(Oid relId, int prettyFlags);
static char *pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
int prettyFlags);
static text *pg_get_expr_worker(text *expr, Oid relid, const char *relname,
@@ -1297,6 +1299,151 @@ pg_get_indexdef_worker(Oid indexrelid, int colno,
return buf.data;
}
+/*
+ * pg_get_partkeydef
+ *
+ * Returns the partition key specification, ie, the following:
+ *
+ * PARTITION BY { RANGE | LIST } (column [USING opclass_name] [, ...])
+ */
+Datum
+pg_get_partkeydef(PG_FUNCTION_ARGS)
+{
+ Oid relationId = PG_GETARG_OID(0);
+ int prettyFlags;
+
+ prettyFlags = PRETTYFLAG_INDENT;
+ PG_RETURN_TEXT_P(string_to_text(pg_get_partkeydef_worker(relationId,
+ prettyFlags)));
+}
+
+/*
+ * Internal workhorse to decompile a partition key definition.
+ */
+static char *
+pg_get_partkeydef_worker(Oid relId, int prettyFlags)
+{
+ Form_pg_partitioned form;
+ HeapTuple tuple;
+ oidvector *partclass;
+ List *partexprs;
+ ListCell *partexpr_item;
+ List *context;
+ Datum datum;
+ bool isnull;
+ StringInfoData buf;
+ int keyno;
+ char *str;
+ char *sep;
+
+ tuple = SearchSysCache1(PARTEDRELID, ObjectIdGetDatum(relId));
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "cache lookup failed for partition key of %u", relId);
+
+ form = (Form_pg_partitioned) GETSTRUCT(tuple);
+
+ Assert(form->partedrelid == relId);
+
+ /* Must get partclass, and partexprs the hard way */
+ datum = SysCacheGetAttr(PARTEDRELID, tuple,
+ Anum_pg_partitioned_partclass, &isnull);
+ Assert(!isnull);
+ partclass = (oidvector *) DatumGetPointer(datum);
+
+ /*
+ * Get the partition key expressions, if any. (NOTE: we do not use the
+ * relcache versions of the expressions, because we want to display
+ * non-const-folded expressions.)
+ */
+ if (!heap_attisnull(tuple, Anum_pg_partitioned_partexprs))
+ {
+ Datum exprsDatum;
+ bool isnull;
+ char *exprsString;
+
+ exprsDatum = SysCacheGetAttr(PARTEDRELID, tuple,
+ Anum_pg_partitioned_partexprs, &isnull);
+ Assert(!isnull);
+ exprsString = TextDatumGetCString(exprsDatum);
+ partexprs = (List *) stringToNode(exprsString);
+ pfree(exprsString);
+ }
+ else
+ partexprs = NIL;
+
+ partexpr_item = list_head(partexprs);
+ context = deparse_context_for(get_relation_name(relId), relId);
+
+ /*
+ * Start the partition key definition.
+ */
+ initStringInfo(&buf);
+
+ appendStringInfo(&buf, "PARTITION BY ");
+
+ switch (form->partstrat)
+ {
+ case 'l':
+ appendStringInfo(&buf, "LIST");
+ break;
+ case 'r':
+ appendStringInfo(&buf, "RANGE");
+ break;
+ }
+
+ /*
+ * Report the partition key columns
+ */
+ appendStringInfo(&buf, " (");
+ sep = "";
+ for (keyno = 0; keyno < form->partnatts; keyno++)
+ {
+ AttrNumber attnum = form->partattrs.values[keyno];
+ Oid keycoltype;
+
+ appendStringInfoString(&buf, sep);
+ sep = ", ";
+ if (attnum != 0)
+ {
+ /* Simple partition key column */
+ char *attname;
+
+ attname = get_relid_attribute_name(relId, attnum);
+ appendStringInfoString(&buf, quote_identifier(attname));
+ keycoltype = get_atttype(relId, attnum);
+ }
+ else
+ {
+ /* partition key expression */
+ Node *partkey;
+
+ if (partexpr_item == NULL)
+ elog(ERROR, "too few entries in partexprs list");
+ partkey = (Node *) lfirst(partexpr_item);
+ partexpr_item = lnext(partexpr_item);
+ /* Deparse */
+ str = deparse_expression_pretty(partkey, context, false, false,
+ prettyFlags, 0);
+
+ /* Need parens if it's not a bare function call */
+ if (partkey && IsA(partkey, FuncExpr) &&
+ ((FuncExpr *) partkey)->funcformat == COERCE_EXPLICIT_CALL)
+ appendStringInfoString(&buf, str);
+ else
+ appendStringInfo(&buf, "(%s)", str);
+ keycoltype = exprType(partkey);
+ }
+
+ /* Add the operator class name, if not default */
+ get_opclass_name(partclass->values[keyno], keycoltype, &buf);
+ }
+ appendStringInfoChar(&buf, ')');
+
+ /* Clean up */
+ ReleaseSysCache(tuple);
+
+ return buf.data;
+}
/*
* pg_get_constraintdef
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 79cc3df..e48a1f4 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -40,6 +40,7 @@
#include "catalog/index.h"
#include "catalog/indexing.h"
#include "catalog/namespace.h"
+#include "catalog/partition.h"
#include "catalog/pg_am.h"
#include "catalog/pg_amproc.h"
#include "catalog/pg_attrdef.h"
@@ -431,6 +432,7 @@ RelationParseRelOptions(Relation relation, HeapTuple tuple)
switch (relation->rd_rel->relkind)
{
case RELKIND_RELATION:
+ case RELKIND_PARTITIONED_REL:
case RELKIND_TOASTVALUE:
case RELKIND_INDEX:
case RELKIND_VIEW:
@@ -1049,6 +1051,12 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
else
relation->rd_rsdesc = NULL;
+ /* if it's a partitioned table, initialize key info */
+ if (relation->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+ RelationBuildPartitionKey(relation);
+ else
+ relation->rd_partkey = NULL;
+
/*
* if it's an index, initialize index-related information
*/
@@ -2046,6 +2054,7 @@ RelationDestroyRelation(Relation relation, bool remember_tupdesc)
MemoryContextDelete(relation->rd_rulescxt);
if (relation->rd_rsdesc)
MemoryContextDelete(relation->rd_rsdesc->rscxt);
+ FreePartitionKey(relation->rd_partkey);
if (relation->rd_fdwroutine)
pfree(relation->rd_fdwroutine);
pfree(relation);
@@ -2987,7 +2996,9 @@ RelationBuildLocalRelation(const char *relname,
/* system relations and non-table objects don't have one */
if (!IsSystemNamespace(relnamespace) &&
- (relkind == RELKIND_RELATION || relkind == RELKIND_MATVIEW))
+ (relkind == RELKIND_RELATION ||
+ relkind == RELKIND_PARTITIONED_REL ||
+ relkind == RELKIND_MATVIEW))
rel->rd_rel->relreplident = REPLICA_IDENTITY_DEFAULT;
else
rel->rd_rel->relreplident = REPLICA_IDENTITY_NOTHING;
@@ -4972,6 +4983,7 @@ load_relcache_init_file(bool shared)
rel->rd_rulescxt = NULL;
rel->trigdesc = NULL;
rel->rd_rsdesc = NULL;
+ rel->rd_partkey = NULL;
rel->rd_indexprs = NIL;
rel->rd_indpred = NIL;
rel->rd_exclops = NULL;
diff --git a/src/backend/utils/cache/syscache.c b/src/backend/utils/cache/syscache.c
index 65ffe84..a6563fe 100644
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -48,6 +48,7 @@
#include "catalog/pg_opclass.h"
#include "catalog/pg_operator.h"
#include "catalog/pg_opfamily.h"
+#include "catalog/pg_partitioned.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_range.h"
#include "catalog/pg_rewrite.h"
@@ -568,6 +569,17 @@ static const struct cachedesc cacheinfo[] = {
},
8
},
+ {PartitionedRelationId, /* PARTEDRELID */
+ PartitionedRelidIndexId,
+ 1,
+ {
+ Anum_pg_partitioned_partedrelid,
+ 0,
+ 0,
+ 0
+ },
+ 128
+ },
{ProcedureRelationId, /* PROCNAMEARGSNSP */
ProcedureNameArgsNspIndexId,
3,
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 1267afb..b3da2f7 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -1234,9 +1234,10 @@ expand_table_name_patterns(Archive *fout,
"SELECT c.oid"
"\nFROM pg_catalog.pg_class c"
"\n LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace"
- "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c')\n",
+ "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c')\n",
RELKIND_RELATION, RELKIND_SEQUENCE, RELKIND_VIEW,
- RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+ RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+ RELKIND_PARTITIONED_REL);
processSQLNamePattern(GetConnection(fout), query, cell->val, true,
false, "n.nspname", "c.relname", NULL,
"pg_catalog.pg_table_is_visible(c.oid)");
@@ -2093,6 +2094,9 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo, bool oids)
/* Skip FOREIGN TABLEs (no data to dump) */
if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
return;
+ /* Skip partitioned tables (data in partitions) */
+ if (tbinfo->relkind == RELKIND_PARTITIONED_REL)
+ return;
/* Don't dump data in unlogged tables, if so requested */
if (tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED &&
@@ -5171,6 +5175,7 @@ getTables(Archive *fout, int *numTables)
int i_reloftype;
int i_relpages;
int i_changed_acl;
+ int i_partkeydef;
/* Make sure we are in proper schema */
selectSourceSchema(fout, "pg_catalog");
@@ -5255,7 +5260,8 @@ getTables(Archive *fout, int *numTables)
"OR %s IS NOT NULL "
"OR %s IS NOT NULL"
"))"
- "AS changed_acl "
+ "AS changed_acl, "
+ "CASE WHEN c.relkind = 'P' THEN pg_catalog.pg_get_partkeydef(c.oid) ELSE NULL END AS partkeydef "
"FROM pg_class c "
"LEFT JOIN pg_depend d ON "
"(c.relkind = '%c' AND "
@@ -5267,7 +5273,7 @@ getTables(Archive *fout, int *numTables)
"(c.oid = pip.objoid AND pip.classoid = "
"(SELECT oid FROM pg_class "
"WHERE relname = 'pg_class') AND pip.objsubid = 0) "
- "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c') "
+ "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c', '%c') "
"ORDER BY c.oid",
acl_subquery->data,
racl_subquery->data,
@@ -5281,7 +5287,8 @@ getTables(Archive *fout, int *numTables)
RELKIND_SEQUENCE,
RELKIND_RELATION, RELKIND_SEQUENCE,
RELKIND_VIEW, RELKIND_COMPOSITE_TYPE,
- RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+ RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+ RELKIND_PARTITIONED_REL);
destroyPQExpBuffer(acl_subquery);
destroyPQExpBuffer(racl_subquery);
@@ -5843,6 +5850,7 @@ getTables(Archive *fout, int *numTables)
i_toastreloptions = PQfnumber(res, "toast_reloptions");
i_reloftype = PQfnumber(res, "reloftype");
i_changed_acl = PQfnumber(res, "changed_acl");
+ i_partkeydef = PQfnumber(res, "partkeydef");
if (dopt->lockWaitTimeout && fout->remoteVersion >= 70300)
{
@@ -5913,6 +5921,7 @@ getTables(Archive *fout, int *numTables)
else
tblinfo[i].checkoption = pg_strdup(PQgetvalue(res, i, i_checkoption));
tblinfo[i].toast_reloptions = pg_strdup(PQgetvalue(res, i, i_toastreloptions));
+ tblinfo[i].partkeydef = pg_strdup(PQgetvalue(res, i, i_partkeydef));
/* other fields were zeroed above */
@@ -5956,7 +5965,9 @@ getTables(Archive *fout, int *numTables)
*
* We only need to lock the table for certain components; see pg_dump.h
*/
- if (tblinfo[i].dobj.dump && tblinfo[i].relkind == RELKIND_RELATION &&
+ if (tblinfo[i].dobj.dump &&
+ (tblinfo[i].relkind == RELKIND_RELATION ||
+ tblinfo->relkind == RELKIND_PARTITIONED_REL) &&
(tblinfo[i].dobj.dump & DUMP_COMPONENTS_REQUIRING_LOCK))
{
resetPQExpBuffer(query);
@@ -6044,7 +6055,10 @@ getInherits(Archive *fout, int *numInherits)
/* find all the inheritance information */
- appendPQExpBufferStr(query, "SELECT inhrelid, inhparent FROM pg_inherits");
+ appendPQExpBufferStr(query,
+ "SELECT inhrelid, inhparent "
+ "FROM pg_inherits "
+ "WHERE inhparent NOT IN (SELECT oid FROM pg_class WHERE relkind = 'P')");
res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
@@ -15413,6 +15427,9 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
appendPQExpBufferChar(q, ')');
}
+ if (tbinfo->relkind == RELKIND_PARTITIONED_REL)
+ appendPQExpBuffer(q, "\n%s", tbinfo->partkeydef);
+
if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
appendPQExpBuffer(q, "\nSERVER %s", fmtId(srvname));
}
@@ -15473,6 +15490,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
*/
if (dopt->binary_upgrade &&
(tbinfo->relkind == RELKIND_RELATION ||
+ tbinfo->relkind == RELKIND_PARTITIONED_REL ||
tbinfo->relkind == RELKIND_FOREIGN_TABLE))
{
for (j = 0; j < tbinfo->numatts; j++)
@@ -15491,7 +15509,8 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
appendStringLiteralAH(q, fmtId(tbinfo->dobj.name), fout);
appendPQExpBufferStr(q, "::pg_catalog.regclass;\n");
- if (tbinfo->relkind == RELKIND_RELATION)
+ if (tbinfo->relkind == RELKIND_RELATION ||
+ tbinfo->relkind == RELKIND_PARTITIONED_REL)
appendPQExpBuffer(q, "ALTER TABLE ONLY %s ",
fmtId(tbinfo->dobj.name));
else
@@ -15708,6 +15727,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
* dump properties we only have ALTER TABLE syntax for
*/
if ((tbinfo->relkind == RELKIND_RELATION ||
+ tbinfo->relkind == RELKIND_PARTITIONED_REL ||
tbinfo->relkind == RELKIND_MATVIEW) &&
tbinfo->relreplident != REPLICA_IDENTITY_DEFAULT)
{
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 2bfa2d9..0292859 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -310,6 +310,7 @@ typedef struct _tableInfo
bool *inhNotNull; /* true if NOT NULL is inherited */
struct _attrDefInfo **attrdefs; /* DEFAULT expressions */
struct _constraintInfo *checkexprs; /* CHECK constraints */
+ char *partkeydef; /* partition key definition */
/*
* Stuff computed only for dumpable tables.
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 0a771bd..78a1fdb 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -764,6 +764,7 @@ permissionsList(const char *pattern)
" WHEN 'm' THEN '%s'"
" WHEN 'S' THEN '%s'"
" WHEN 'f' THEN '%s'"
+ " WHEN 'P' THEN '%s'"
" END as \"%s\",\n"
" ",
gettext_noop("Schema"),
@@ -773,6 +774,7 @@ permissionsList(const char *pattern)
gettext_noop("materialized view"),
gettext_noop("sequence"),
gettext_noop("foreign table"),
+ gettext_noop("partitioned table"),
gettext_noop("Type"));
printACLColumn(&buf, "c.relacl");
@@ -819,7 +821,7 @@ permissionsList(const char *pattern)
appendPQExpBufferStr(&buf, "\nFROM pg_catalog.pg_class c\n"
" LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace\n"
- "WHERE c.relkind IN ('r', 'v', 'm', 'S', 'f')\n");
+ "WHERE c.relkind IN ('r', 'v', 'm', 'S', 'f', 'P')\n");
/*
* Unless a schema pattern is specified, we suppress system and temp
@@ -1463,8 +1465,8 @@ describeOneTableDetails(const char *schemaname,
* types, and foreign tables (c.f. CommentObject() in comment.c).
*/
if (tableinfo.relkind == 'r' || tableinfo.relkind == 'v' ||
- tableinfo.relkind == 'm' ||
- tableinfo.relkind == 'f' || tableinfo.relkind == 'c')
+ tableinfo.relkind == 'm' || tableinfo.relkind == 'f' ||
+ tableinfo.relkind == 'c' || tableinfo.relkind == 'P')
appendPQExpBufferStr(&buf, ", pg_catalog.col_description(a.attrelid, a.attnum)");
}
@@ -1529,6 +1531,14 @@ describeOneTableDetails(const char *schemaname,
printfPQExpBuffer(&title, _("Foreign table \"%s.%s\""),
schemaname, relationname);
break;
+ case 'P':
+ if (tableinfo.relpersistence == 'u')
+ printfPQExpBuffer(&title, _("Unlogged partitioned table \"%s.%s\""),
+ schemaname, relationname);
+ else
+ printfPQExpBuffer(&title, _("Partitioned table \"%s.%s\""),
+ schemaname, relationname);
+ break;
default:
/* untranslated unknown relkind */
printfPQExpBuffer(&title, "?%c? \"%s.%s\"",
@@ -1542,8 +1552,8 @@ describeOneTableDetails(const char *schemaname,
cols = 2;
if (tableinfo.relkind == 'r' || tableinfo.relkind == 'v' ||
- tableinfo.relkind == 'm' ||
- tableinfo.relkind == 'f' || tableinfo.relkind == 'c')
+ tableinfo.relkind == 'm' || tableinfo.relkind == 'f' ||
+ tableinfo.relkind == 'c' || tableinfo.relkind == 'P')
{
show_modifiers = true;
headers[cols++] = gettext_noop("Modifiers");
@@ -1563,12 +1573,12 @@ describeOneTableDetails(const char *schemaname,
{
headers[cols++] = gettext_noop("Storage");
if (tableinfo.relkind == 'r' || tableinfo.relkind == 'm' ||
- tableinfo.relkind == 'f')
+ tableinfo.relkind == 'f' || tableinfo.relkind == 'P')
headers[cols++] = gettext_noop("Stats target");
/* Column comments, if the relkind supports this feature. */
if (tableinfo.relkind == 'r' || tableinfo.relkind == 'v' ||
- tableinfo.relkind == 'm' ||
- tableinfo.relkind == 'c' || tableinfo.relkind == 'f')
+ tableinfo.relkind == 'm' || tableinfo.relkind == 'c' ||
+ tableinfo.relkind == 'f' || tableinfo.relkind == 'P')
headers[cols++] = gettext_noop("Description");
}
@@ -1668,7 +1678,7 @@ describeOneTableDetails(const char *schemaname,
/* Statistics target, if the relkind supports this feature */
if (tableinfo.relkind == 'r' || tableinfo.relkind == 'm' ||
- tableinfo.relkind == 'f')
+ tableinfo.relkind == 'f' || tableinfo.relkind == 'P')
{
printTableAddCell(&cont, PQgetvalue(res, i, firstvcol + 1),
false, false);
@@ -1676,14 +1686,33 @@ describeOneTableDetails(const char *schemaname,
/* Column comments, if the relkind supports this feature. */
if (tableinfo.relkind == 'r' || tableinfo.relkind == 'v' ||
- tableinfo.relkind == 'm' ||
- tableinfo.relkind == 'c' || tableinfo.relkind == 'f')
+ tableinfo.relkind == 'm' || tableinfo.relkind == 'c' ||
+ tableinfo.relkind == 'f' || tableinfo.relkind == 'P')
printTableAddCell(&cont, PQgetvalue(res, i, firstvcol + 2),
false, false);
}
}
/* Make footers */
+ if (pset.sversion >= 90600 && tableinfo.relkind == 'P')
+ {
+ /* Get the partition key information */
+ PGresult *result;
+ char *partkeydef;
+
+ printfPQExpBuffer(&buf,
+ "SELECT pg_catalog.pg_get_partkeydef('%s'::pg_catalog.oid);",
+ oid);
+ result = PSQLexec(buf.data);
+ if (!result || PQntuples(result) != 1)
+ goto error_return;
+
+ partkeydef = PQgetvalue(result, 0, 0);
+ printfPQExpBuffer(&tmpbuf, _("Partition Key: %s"), partkeydef);
+ printTableAddFooter(&cont, tmpbuf.data);
+ PQclear(result);
+ }
+
if (tableinfo.relkind == 'i')
{
/* Footer information about an index */
@@ -1822,7 +1851,7 @@ describeOneTableDetails(const char *schemaname,
PQclear(result);
}
else if (tableinfo.relkind == 'r' || tableinfo.relkind == 'm' ||
- tableinfo.relkind == 'f')
+ tableinfo.relkind == 'f' || tableinfo.relkind == 'P')
{
/* Footer information about a table */
PGresult *result = NULL;
@@ -2381,7 +2410,7 @@ describeOneTableDetails(const char *schemaname,
* Finish printing the footer information about a table.
*/
if (tableinfo.relkind == 'r' || tableinfo.relkind == 'm' ||
- tableinfo.relkind == 'f')
+ tableinfo.relkind == 'f' || tableinfo.relkind == 'P')
{
PGresult *result;
int tuples;
@@ -2592,7 +2621,7 @@ add_tablespace_footer(printTableContent *const cont, char relkind,
Oid tablespace, const bool newline)
{
/* relkinds for which we support tablespaces */
- if (relkind == 'r' || relkind == 'm' || relkind == 'i')
+ if (relkind == 'r' || relkind == 'm' || relkind == 'i' || relkind == 'P')
{
/*
* We ignore the database default tablespace so that users not using
@@ -2926,6 +2955,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
" WHEN 'S' THEN '%s'"
" WHEN 's' THEN '%s'"
" WHEN 'f' THEN '%s'"
+ " WHEN 'P' THEN '%s'"
" END as \"%s\",\n"
" pg_catalog.pg_get_userbyid(c.relowner) as \"%s\"",
gettext_noop("Schema"),
@@ -2937,6 +2967,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
gettext_noop("sequence"),
gettext_noop("special"),
gettext_noop("foreign table"),
+ gettext_noop("partitioned table"),
gettext_noop("Type"),
gettext_noop("Owner"));
@@ -2975,7 +3006,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
appendPQExpBufferStr(&buf, "\nWHERE c.relkind IN (");
if (showTables)
- appendPQExpBufferStr(&buf, "'r',");
+ appendPQExpBufferStr(&buf, "'r', 'P',");
if (showViews)
appendPQExpBufferStr(&buf, "'v',");
if (showMatViews)
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index a62ffe6..741ac46 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -427,7 +427,7 @@ static const SchemaQuery Query_for_list_of_tables = {
/* catname */
"pg_catalog.pg_class c",
/* selcondition */
- "c.relkind IN ('r')",
+ "c.relkind IN ('r', 'P')",
/* viscondition */
"pg_catalog.pg_table_is_visible(c.oid)",
/* namespace */
@@ -458,7 +458,7 @@ static const SchemaQuery Query_for_list_of_updatables = {
/* catname */
"pg_catalog.pg_class c",
/* selcondition */
- "c.relkind IN ('r', 'f', 'v')",
+ "c.relkind IN ('r', 'f', 'v', 'P')",
/* viscondition */
"pg_catalog.pg_table_is_visible(c.oid)",
/* namespace */
@@ -488,7 +488,7 @@ static const SchemaQuery Query_for_list_of_tsvmf = {
/* catname */
"pg_catalog.pg_class c",
/* selcondition */
- "c.relkind IN ('r', 'S', 'v', 'm', 'f')",
+ "c.relkind IN ('r', 'S', 'v', 'm', 'f', 'P')",
/* viscondition */
"pg_catalog.pg_table_is_visible(c.oid)",
/* namespace */
diff --git a/src/include/catalog/indexing.h b/src/include/catalog/indexing.h
index ca5eb3d..cadb741 100644
--- a/src/include/catalog/indexing.h
+++ b/src/include/catalog/indexing.h
@@ -319,6 +319,9 @@ DECLARE_UNIQUE_INDEX(pg_replication_origin_roiident_index, 6001, on pg_replicati
DECLARE_UNIQUE_INDEX(pg_replication_origin_roname_index, 6002, on pg_replication_origin using btree(roname text_pattern_ops));
#define ReplicationOriginNameIndex 6002
+DECLARE_UNIQUE_INDEX(pg_partitioned_partedrelid_index, 3351, on pg_partitioned using btree(partedrelid oid_ops));
+#define PartitionedRelidIndexId 3351
+
/* last step of initialization script: build the indexes declared above */
BUILD_INDICES
diff --git a/src/include/catalog/partition.h b/src/include/catalog/partition.h
new file mode 100644
index 0000000..2c876c4
--- /dev/null
+++ b/src/include/catalog/partition.h
@@ -0,0 +1,55 @@
+/*-------------------------------------------------------------------------
+ *
+ * partition.h
+ * Header file for structures and utility functions related to
+ * partitioning
+ *
+ * Copyright (c) 2007-2016, PostgreSQL Global Development Group
+ *
+ * src/include/utils/partition.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PARTITION_H
+#define PARTITION_H
+
+#include "fmgr.h"
+#include "utils/relcache.h"
+
+/* Type and collation information for partition key columns */
+typedef struct
+{
+ Oid *typid;
+ int32 *typmod;
+ int16 *typlen;
+ bool *typbyval;
+ char *typalign;
+ Oid *typcoll;
+} KeyTypeCollInfo;
+
+/* Partition key information for one level */
+typedef struct PartitionKeyData
+{
+ char strategy; /* partition strategy */
+ int16 partnatts; /* number of partition attributes */
+ AttrNumber *partattrs; /* partition attnums */
+ Oid *partopfamily; /* OIDs of opfamily per col */
+ FmgrInfo *partsupfunc; /* lookup info for support funcs */
+ List *partexprs; /* partition key expressions, if any */
+ KeyTypeCollInfo *tcinfo; /* type and collation info (all columns) */
+} PartitionKeyData;
+
+typedef struct PartitionKeyData *PartitionKey;
+
+extern void StorePartitionKey(Relation rel,
+ char strategy,
+ int16 partnatts,
+ AttrNumber *partattrs,
+ List *partexprs,
+ Oid *partopclass);
+extern void RemovePartitionKeyByRelId(Oid relid);
+extern void RelationBuildPartitionKey(Relation relation);
+extern void FreePartitionKey(PartitionKey key);
+extern bool relid_is_partitioned(Oid relid);
+
+#endif /* PARTITION_H */
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index fdc67c2..149f619 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -153,6 +153,7 @@ DESCR("");
#define RELKIND_RELATION 'r' /* ordinary table */
+#define RELKIND_PARTITIONED_REL 'P' /* partitioned table */
#define RELKIND_INDEX 'i' /* secondary index */
#define RELKIND_SEQUENCE 'S' /* sequence object */
#define RELKIND_TOASTVALUE 't' /* for out-of-line values */
diff --git a/src/include/catalog/pg_partitioned.h b/src/include/catalog/pg_partitioned.h
new file mode 100644
index 0000000..edc52db
--- /dev/null
+++ b/src/include/catalog/pg_partitioned.h
@@ -0,0 +1,67 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_partitioned.h
+ * definition of the system "partitioned" relation (pg_partitioned)
+ * along with the relation's initial contents.
+ *
+ *
+ * Portions Copyright (c) 1996-2015, PostgreSQL Global Development Group
+ *
+ * $PostgreSQL: pgsql/src/include/catalog/pg_partitioned.h $
+ *
+ * NOTES
+ * the genbki.sh script reads this file and generates .bki
+ * information from the DATA() statements.
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_PARTITIONED_REL_H
+#define PG_PARTITIONED_REL_H
+
+#include "catalog/genbki.h"
+
+/* ----------------
+ * pg_partitioned definition. cpp turns this into
+ * typedef struct FormData_pg_partitioned
+ * ----------------
+ */
+#define PartitionedRelationId 3350
+
+CATALOG(pg_partitioned,3350) BKI_WITHOUT_OIDS
+{
+ Oid partedrelid; /* partitioned table oid */
+ char partstrat; /* partition key strategy */
+ int16 partnatts; /* number of partition key columns */
+
+ /* variable-length fields start here, but we allow direct access to partattrs */
+ int2vector partattrs; /* attribute numbers of partition key
+ * columns */
+
+#ifdef CATALOG_VARLEN
+ oidvector partclass; /* operator class to compare keys */
+ pg_node_tree partexprs; /* expression trees for partition key members
+ * that are not simple column references; one
+ * for each zero entry in partkey[] */
+#endif
+} FormData_pg_partitioned;
+
+/* ----------------
+ * Form_pg_partitioned corresponds to a pointer to a tuple with
+ * the format of pg_partitioned relation.
+ * ----------------
+ */
+typedef FormData_pg_partitioned *Form_pg_partitioned;
+
+/* ----------------
+ * compiler constants for pg_partitioned
+ * ----------------
+ */
+#define Natts_pg_partitioned 6
+#define Anum_pg_partitioned_partedrelid 1
+#define Anum_pg_partitioned_partstrat 2
+#define Anum_pg_partitioned_partnatts 3
+#define Anum_pg_partitioned_partattrs 4
+#define Anum_pg_partitioned_partclass 5
+#define Anum_pg_partitioned_partexprs 6
+
+#endif /* PG_PARTITIONED_REL_H */
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index d94943b..24d7c5f 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -1969,6 +1969,8 @@ DATA(insert OID = 1642 ( pg_get_userbyid PGNSP PGUID 12 1 0 0 0 f f f f t f
DESCR("role name by OID (with fallback)");
DATA(insert OID = 1643 ( pg_get_indexdef PGNSP PGUID 12 1 0 0 0 f f f f t f s s 1 0 25 "26" _null_ _null_ _null_ _null_ _null_ pg_get_indexdef _null_ _null_ _null_ ));
DESCR("index description");
+DATA(insert OID = 3352 ( pg_get_partkeydef PGNSP PGUID 12 1 0 0 0 f f f f t f s s 1 0 25 "26" _null_ _null_ _null_ _null_ _null_ pg_get_partkeydef _null_ _null_ _null_ ));
+DESCR("partition key description");
DATA(insert OID = 1662 ( pg_get_triggerdef PGNSP PGUID 12 1 0 0 0 f f f f t f s s 1 0 25 "26" _null_ _null_ _null_ _null_ _null_ pg_get_triggerdef _null_ _null_ _null_ ));
DESCR("trigger description");
DATA(insert OID = 1387 ( pg_get_constraintdef PGNSP PGUID 12 1 0 0 0 f f f f t f s s 1 0 25 "26" _null_ _null_ _null_ _null_ _null_ pg_get_constraintdef _null_ _null_ _null_ ));
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index 28024f8..ceeb41f 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -42,8 +42,8 @@ extern bool CheckIndexCompatible(Oid oldId,
List *attributeList,
List *exclusionOpNames);
extern Oid GetDefaultOpClass(Oid type_id, Oid am_id);
-extern Oid GetIndexOpClass(List *opclass, Oid attrType,
- char *accessMethodName, Oid accessMethodId);
+extern Oid GetIndexOpClass(List *opclass, Oid attrType,
+ char *accessMethodName, Oid accessMethodId);
/* commands/functioncmds.c */
extern ObjectAddress CreateFunction(CreateFunctionStmt *stmt, const char *queryString);
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
index 01976a1..027665c 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -720,6 +720,7 @@ extern Datum pg_get_viewdef_wrap(PG_FUNCTION_ARGS);
extern Datum pg_get_viewdef_name(PG_FUNCTION_ARGS);
extern Datum pg_get_viewdef_name_ext(PG_FUNCTION_ARGS);
extern Datum pg_get_indexdef(PG_FUNCTION_ARGS);
+extern Datum pg_get_partkeydef(PG_FUNCTION_ARGS);
extern Datum pg_get_indexdef_ext(PG_FUNCTION_ARGS);
extern Datum pg_get_triggerdef(PG_FUNCTION_ARGS);
extern Datum pg_get_triggerdef_ext(PG_FUNCTION_ARGS);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index a0ba417..2e43ebf 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -90,6 +90,8 @@ typedef struct RelationData
/* use "struct" here to avoid needing to include rowsecurity.h: */
struct RowSecurityDesc *rd_rsdesc; /* row security policies, or NULL */
+ struct PartitionKeyData *rd_partkey; /* partition key, or NULL */
+
/* data managed by RelationGetIndexList: */
List *rd_indexlist; /* list of OIDs of indexes on relation */
Oid rd_oidindex; /* OID of unique index on OID, if any */
@@ -502,6 +504,12 @@ typedef struct ViewOptions
RelationNeedsWAL(relation) && \
!IsCatalogRelation(relation))
+/*
+ * RelationGetPartitionKey
+ * Returns partition key for a relation.
+ */
+#define RelationGetPartitionKey(relation) ((relation)->rd_partkey)
+
/* routines in utils/cache/relcache.c */
extern void RelationIncrementReferenceCount(Relation rel);
extern void RelationDecrementReferenceCount(Relation rel);
diff --git a/src/include/utils/syscache.h b/src/include/utils/syscache.h
index 256615b..e727842 100644
--- a/src/include/utils/syscache.h
+++ b/src/include/utils/syscache.h
@@ -72,6 +72,7 @@ enum SysCacheIdentifier
OPEROID,
OPFAMILYAMNAMENSP,
OPFAMILYOID,
+ PARTEDRELID,
PROCNAMEARGSNSP,
PROCOID,
RANGETYPE,
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index aaae9f9..a4cc948 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -326,9 +326,35 @@ ERROR: cannot use a constant expression as partition key
CREATE TABLE fail_agg_in_key (
a int
) PARTITION BY RANGE ((avg(a)));
-ERROR: aggregate functions are not allowed in partition key expressions
+ERROR: aggregate functions are not allowed in partition key expression
CREATE TABLE fail_window_fun_in_key (
a int,
b int
) PARTITION BY RANGE ((avg(a) OVER (PARTITION BY b)));
-ERROR: window functions are not allowed in partition key expressions
+ERROR: window functions are not allowed in partition key expression
+-- check relkind
+CREATE TABLE check_relkind (
+ a int
+) PARTITION BY RANGE (a);
+SELECT relkind FROM pg_class WHERE relname = 'check_relkind';
+ relkind
+---------
+ P
+(1 row)
+
+-- cannot drop column that is part of the partition key
+CREATE TABLE no_drop_alter_partcol (
+ a int
+) PARTITION BY RANGE (a);
+ALTER TABLE no_drop_alter_partcol DROP COLUMN a;
+ERROR: cannot drop column named in partition key
+ALTER TABLE no_drop_alter_partcol ALTER COLUMN a TYPE char(5);
+ERROR: cannot alter column named in partition key
+CREATE TABLE no_drop_alter_partexpr (
+ a text
+) PARTITION BY RANGE ((substring(a from 1 for 1)));
+ALTER TABLE no_drop_alter_partexpr DROP COLUMN a;
+ERROR: cannot drop column referenced in partition key expression
+ALTER TABLE no_drop_alter_partcol ALTER COLUMN a TYPE char(5);
+ERROR: cannot alter column named in partition key
+DROP TABLE check_relkind, no_drop_alter_partcol, no_drop_alter_partexpr;
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index 1c087a3..b1e473d 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -118,6 +118,7 @@ pg_namespace|t
pg_opclass|t
pg_operator|t
pg_opfamily|t
+pg_partitioned|t
pg_pltemplate|t
pg_policy|t
pg_proc|t
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index df47656..022f38b 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -337,3 +337,24 @@ CREATE TABLE fail_window_fun_in_key (
a int,
b int
) PARTITION BY RANGE ((avg(a) OVER (PARTITION BY b)));
+
+-- check relkind
+CREATE TABLE check_relkind (
+ a int
+) PARTITION BY RANGE (a);
+SELECT relkind FROM pg_class WHERE relname = 'check_relkind';
+
+-- cannot drop column that is part of the partition key
+CREATE TABLE no_drop_alter_partcol (
+ a int
+) PARTITION BY RANGE (a);
+ALTER TABLE no_drop_alter_partcol DROP COLUMN a;
+ALTER TABLE no_drop_alter_partcol ALTER COLUMN a TYPE char(5);
+
+CREATE TABLE no_drop_alter_partexpr (
+ a text
+) PARTITION BY RANGE ((substring(a from 1 for 1)));
+ALTER TABLE no_drop_alter_partexpr DROP COLUMN a;
+ALTER TABLE no_drop_alter_partcol ALTER COLUMN a TYPE char(5);
+
+DROP TABLE check_relkind, no_drop_alter_partcol, no_drop_alter_partexpr;
--
1.7.1
[text/x-diff] 0004-Add-syntax-to-create-partitions-v5.patch (77.8K, ../../573ECCC3.7080104@lab.ntt.co.jp/5-0004-Add-syntax-to-create-partitions-v5.patch)
download | inline diff:
From b200e2ff50718090cee87d4b9bdeb7c39d5f4d69 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Thu, 3 Mar 2016 18:29:15 +0900
Subject: [PATCH 4/7] Add syntax to create partitions.
As follows:
CREATE TABLE name PARTITION OF parent
[ ( { column_name WITH OPTION column_constraint | table_constraint } [, ...] ) ]
partition_bound_spec [ PARTITION BY partition_key_spec ]
ALTER TABLE parent ATTACH PARTITION name
partition_bound_spec [ VALIDATE | NO VALIDATE ]
ALTER TABLE parent DETACH PARTITION name
partition_bound_spec refers to either list partition bound of form:
FOR VALUES IN (list-of-expressions)
or range partition bound of form:
FOR VALUES range_spec
where range_spec is:
START lower-bound [ INCLUSIVE | EXCLUSIVE ] END upper-bound [ INCLUSIVE | EXCLUSIVE ]
lower-bound and upper-bound is:
{ ( expression [,...] ) | UNBOUNDED }
In the above, each of lower-bound and upper-bound is either a single
expression for a single column key or list of expressions for a multi-
column key or empty. Note that in the absence of explicit specification,
lower-bound is inclusive and upper-bound is exclusive.
A partition is created as inheritance child of the parent with certain
differences from regular inheritance behavior. For example, when a new
partition is created, it cannot contain its own column definitions or
constraints (unlike CREATE TABLE child(...) INHERITS(parent)). When
"attaching" a table as partition of parent, it cannot contain columns
that are not contained in the parent (unlike ALTER TABLE child INHERIT
parent). No dependency is created between a partition and the parent.
One cannot create regular inheritance children of a partitioned table.
Whereas this commit enables inheritance, it does not yet store partition
bound info anywhere, IOW, the fact that a given inheritance child is a
"partition" is not persisted.
---
doc/src/sgml/ref/alter_table.sgml | 57 +++++
doc/src/sgml/ref/create_table.sgml | 77 ++++++-
src/backend/commands/createas.c | 1 +
src/backend/commands/sequence.c | 1 +
src/backend/commands/tablecmds.c | 490 +++++++++++++++++++++++++++---------
src/backend/commands/view.c | 1 +
src/backend/nodes/copyfuncs.c | 48 ++++
src/backend/nodes/equalfuncs.c | 42 +++
src/backend/nodes/outfuncs.c | 27 ++
src/backend/parser/gram.y | 167 ++++++++++++-
src/backend/parser/parse_agg.c | 7 +-
src/backend/parser/parse_expr.c | 23 ++
src/backend/parser/parse_utilcmd.c | 318 +++++++++++++++++++++++
src/include/catalog/partition.h | 1 +
src/include/nodes/nodes.h | 3 +
src/include/nodes/parsenodes.h | 43 +++-
src/include/parser/kwlist.h | 3 +
src/include/parser/parse_node.h | 3 +-
18 files changed, 1182 insertions(+), 130 deletions(-)
diff --git a/doc/src/sgml/ref/alter_table.sgml b/doc/src/sgml/ref/alter_table.sgml
index 5ca211e..97744c9 100644
--- a/doc/src/sgml/ref/alter_table.sgml
+++ b/doc/src/sgml/ref/alter_table.sgml
@@ -77,12 +77,15 @@ ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable>
NOT OF
OWNER TO { <replaceable class="PARAMETER">new_owner</replaceable> | CURRENT_USER | SESSION_USER }
REPLICA IDENTITY { DEFAULT | USING INDEX <replaceable class="PARAMETER">index_name</replaceable> | FULL | NOTHING }
+ ATTACH PARTITION <replaceable class="PARAMETER">name</replaceable> <replaceable class="PARAMETER">partition_bound_spec</replaceable> [ VALIDATE | NO VALIDATE ]
+ DETACH PARTITION <replaceable class="PARAMETER">name</replaceable>
<phrase>and <replaceable class="PARAMETER">table_constraint_using_index</replaceable> is:</phrase>
[ CONSTRAINT <replaceable class="PARAMETER">constraint_name</replaceable> ]
{ UNIQUE | PRIMARY KEY } USING INDEX <replaceable class="PARAMETER">index_name</replaceable>
[ DEFERRABLE | NOT DEFERRABLE ] [ INITIALLY DEFERRED | INITIALLY IMMEDIATE ]
+
</synopsis>
</refsynopsisdiv>
@@ -704,6 +707,51 @@ ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable>
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>ATTACH PARTITION</literal></term>
+ <listitem>
+ <para>
+ This form attaches an existing table as partition of the target table,
+ provided the target table is partitioned. The partition bound
+ specification must correspond with partition method and key of the
+ partitioned table. The table being attached must have all the columns
+ as the target table and no more. Also, it must have all the matching
+ constraints as the target table. That includes <literal>NOT NULL</>
+ and <literal>CHECK</> constraints. <literal>UNIQUE</literal>,
+ <literal>PRIMARY KEY</literal>, and <literal>FOREIGN KEY</literal>
+ constraints are not considered, but this might change in the future.
+ </para>
+
+ <para>
+ If <literal>NO VALIDATE</literal> clause is specified, existing rows in
+ source table are are not checked to see if they violate the partition
+ bound specification. This allows to quickly load a large amount of data
+ into the partitioned table. The default behavior is to perform the
+ check, as if <literal>VALIDATE</literal> clause were specified.
+ </para>
+
+ <para>
+ Note that once the table becomes a partition, it will be dropped or
+ truncated when the parent table is dropped or truncated. Dropping
+ it directly using <literal>DROP TABLE</literal> will fail; it must
+ first be detached from the parent. However, truncating a partition
+ directly works.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><literal>DETACH PARTITION</literal></term>
+ <listitem>
+ <para>
+ This form detaches specified partition of the target table, provided the
+ target table is partitioned and the specified partition exists. The
+ resulting table continues to exist as a standalone table with no ties
+ remaining with the parent partitioned table.
+ </para>
+ </listitem>
+ </varlistentry>
+
</variablelist>
</para>
@@ -936,6 +984,15 @@ ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable>
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><replaceable class="PARAMETER">partition_bound_spec</replaceable></term>
+ <listitem>
+ <para>
+ The partition bound specification for a new partition.
+ </para>
+ </listitem>
+ </varlistentry>
+
</variablelist>
</refsect1>
diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml
index 70a7579..79fb521 100644
--- a/doc/src/sgml/ref/create_table.sgml
+++ b/doc/src/sgml/ref/create_table.sgml
@@ -44,6 +44,17 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
[ ON COMMIT { PRESERVE ROWS | DELETE ROWS | DROP } ]
[ TABLESPACE <replaceable class="PARAMETER">tablespace_name</replaceable> ]
+CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name</replaceable>
+ PARTITION OF <replaceable class="PARAMETER">parent_table</replaceable> [ (
+ { <replaceable class="PARAMETER">column_name</replaceable> WITH OPTIONS [ <replaceable class="PARAMETER">column_constraint</replaceable> [ ... ] ]
+ | <replaceable>table_constraint</replaceable> }
+ [, ... ]
+) ] <replaceable class="PARAMETER">partition_bound_spec</replaceable>
+[ PARTITION BY {RANGE | LIST} ( { <replaceable class="parameter">column_name</replaceable> | ( <replaceable class="parameter">expression</replaceable> ) } [ <replaceable class="parameter">opclass</replaceable> ] [, ...] )
+[ WITH ( <replaceable class="PARAMETER">storage_parameter</replaceable> [= <replaceable class="PARAMETER">value</replaceable>] [, ... ] ) | WITH OIDS | WITHOUT OIDS ]
+[ ON COMMIT { PRESERVE ROWS | DELETE ROWS | DROP } ]
+[ TABLESPACE <replaceable class="PARAMETER">tablespace_name</replaceable> ]
+
<phrase>where <replaceable class="PARAMETER">column_constraint</replaceable> is:</phrase>
[ CONSTRAINT <replaceable class="PARAMETER">constraint_name</replaceable> ]
@@ -72,6 +83,10 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
{ INCLUDING | EXCLUDING } { DEFAULTS | CONSTRAINTS | INDEXES | STORAGE | COMMENTS | ALL }
+<phrase>and <replaceable class="PARAMETER">partition_bound_spec</replaceable> is:</phrase>
+
+FOR VALUES { <replaceable class="PARAMETER">list_spec</replaceable> | <replaceable class="PARAMETER">range_spec</replaceable> }
+
<phrase><replaceable class="PARAMETER">index_parameters</replaceable> in <literal>UNIQUE</literal>, <literal>PRIMARY KEY</literal>, and <literal>EXCLUDE</literal> constraints are:</phrase>
[ WITH ( <replaceable class="PARAMETER">storage_parameter</replaceable> [= <replaceable class="PARAMETER">value</replaceable>] [, ... ] ) ]
@@ -80,8 +95,20 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
<phrase><replaceable class="PARAMETER">exclude_element</replaceable> in an <literal>EXCLUDE</literal> constraint is:</phrase>
{ <replaceable class="parameter">column_name</replaceable> | ( <replaceable class="parameter">expression</replaceable> ) } [ <replaceable class="parameter">opclass</replaceable> ] [ ASC | DESC ] [ NULLS { FIRST | LAST } ]
-</synopsis>
+<phrase><replaceable class="PARAMETER">list_spec</replaceable> in <literal>FOR VALUES</literal> is:</phrase>
+
+IN ( <replaceable class="PARAMETER">expression</replaceable> [, ...] )
+
+<phrase><replaceable class="PARAMETER">range_spec</replaceable> in <literal>FOR VALUES</literal> is:</phrase>
+
+START <replaceable class="PARAMETER">lower-bound</replaceable> [ INCLUSIVE | EXCLUSIVE ] END <replaceable class="PARAMETER">upper-bound</replaceable> [ INCLUSIVE | EXCLUSIVE ]
+
+<phrase>where <replaceable class="PARAMETER">lower-bound</replaceable> and <replaceable class="PARAMETER">upper-bound</replaceable> are:</phrase>
+
+{ ( <replaceable class="PARAMETER">expression</replaceable> [, ...] ) | UNBOUNDED }
+
+</synopsis>
</refsynopsisdiv>
<refsect1 id="SQL-CREATETABLE-description">
@@ -232,6 +259,49 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
</varlistentry>
<varlistentry>
+ <term><literal>PARTITION OF <replaceable class="PARAMETER">parent_table</replaceable></literal></term>
+ <listitem>
+ <para>
+ Creates the table as <firstterm>partition</firstterm> of the specified
+ parent table (name optionally schema-qualified).
+ </para>
+
+ <para>
+ A partition bound specification must be present and must correspond with
+ partition method and key of the parent table. It is checked using the
+ specification that the new partition does not overlap with any existing
+ partitions of the parent.
+ </para>
+
+ <para>
+ A partition cannot have columns other than those inherited from the
+ parent. That includes the <structfield>oid</> column, which can be
+ specified using the <literal>WITH (OIDS)</literal> clause. On the other
+ hand, if parent has the <structfield>oid</> column, the partition
+ inherits the same, overriding the <literal>WITH (OIDS=FALSE)</literal>
+ clause, if any. Defaults and constraints can optionally be specified
+ for each of the inherited columns, which override those in the parent.
+ One can also specify table constraints, in addition to those inherited
+ from the parent. Note that all subsequent schema modifications to the
+ parent propagate to partition.
+ </para>
+
+ <para>
+ Any data row subsequently inserted into the parent table is mapped to
+ and stored in the partition, provided partition key of the row falls
+ within the partition bounds.
+ </para>
+
+ <para>
+ A partition is dropped or truncated when the parent table is dropped or
+ truncated. Dropping it directly using <literal>DROP TABLE</literal>
+ will fail; it must first be <firstterm>detached</> from the parent.
+ However, truncating a partition directly works.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><replaceable class="PARAMETER">column_name</replaceable></term>
<listitem>
<para>
@@ -340,6 +410,11 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
</para>
<para>
+ When specifying for a table being created as partition, one needs to
+ use column names from the parent table as part of the key.
+ </para>
+
+ <para>
Currently, there are following limitations on definition of partitioned
tables: one cannot specify any UNIQUE, PRIMARY KEY, EXCLUDE and/or
FOREIGN KEY constraints.
diff --git a/src/backend/commands/createas.c b/src/backend/commands/createas.c
index cb7a145..427470c 100644
--- a/src/backend/commands/createas.c
+++ b/src/backend/commands/createas.c
@@ -342,6 +342,7 @@ intorel_startup(DestReceiver *self, int operation, TupleDesc typeinfo)
col->is_local = true;
col->is_not_null = false;
col->is_from_type = false;
+ col->is_for_partition = false;
col->storage = 0;
col->raw_default = NULL;
col->cooked_default = NULL;
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index 5dcd4cf..21d9f96 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -159,6 +159,7 @@ DefineSequence(CreateSeqStmt *seq)
coldef->is_local = true;
coldef->is_not_null = true;
coldef->is_from_type = false;
+ coldef->is_for_partition = false;
coldef->storage = 0;
coldef->raw_default = NULL;
coldef->cooked_default = NULL;
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 87bf466..192cae9 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -279,7 +279,8 @@ typedef struct
static void truncate_check_rel(Relation rel);
static List *MergeAttributes(List *schema, List *supers, char relpersistence,
- List **supOids, List **supconstr, int *supOidCount);
+ List **supOids, List **supconstr, int *supOidCount,
+ bool is_partition);
static bool MergeCheckConstraint(List *constraints, char *name, Node *expr);
static void MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel);
static void MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel);
@@ -448,6 +449,11 @@ static void ComputePartitionAttrs(Oid relid, List *partParams,
static bool find_attr_reference_walker(Node *node,
find_attr_reference_context *context);
static bool is_partition_attr(Relation rel, AttrNumber attnum, bool *is_expr);
+static void CreateInheritance(Relation child_rel, Relation parent_rel);
+static void RemoveInheritance(Relation child_rel, Relation parent_rel);
+static ObjectAddress ATExecAttachPartition(List **wqueue, Relation rel,
+ PartitionCmd *cmd);
+static ObjectAddress ATExecDetachPartition(Relation rel, RangeVar *name);
/* ----------------------------------------------------------------
@@ -590,10 +596,16 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
/*
* Look up inheritance ancestors and generate relation schema, including
* inherited attributes.
+ *
+ * The last parameter implicitly specifies that the table is being created
+ * as partition and schema consists of columns definitions corresponding
+ * to non-dropped columns of the parent constructed such that each
+ * attribute of the table is created as inherited and non-local.
*/
schema = MergeAttributes(schema, stmt->inhRelations,
stmt->relation->relpersistence,
- &inheritOids, &old_constraints, &parentOidCount);
+ &inheritOids, &old_constraints, &parentOidCount,
+ stmt->partbound != NULL);
/*
* Create a tuple descriptor from the relation schema. Note that this
@@ -1452,7 +1464,8 @@ storage_name(char c)
*/
static List *
MergeAttributes(List *schema, List *supers, char relpersistence,
- List **supOids, List **supconstr, int *supOidCount)
+ List **supOids, List **supconstr, int *supOidCount,
+ bool is_partition)
{
ListCell *entry;
List *inhSchema = NIL;
@@ -1497,8 +1510,8 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
/*
* Typed table column option that does not belong to a column from
- * the type. This works because the columns from the type come
- * first in the list.
+ * the type or the partition parent. This works because the columns
+ * from the type or the partition parent come first in the list.
*/
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_COLUMN),
@@ -1525,6 +1538,18 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
coldef->is_from_type = false;
list_delete_cell(schema, rest, prev);
}
+ else if (coldef->is_for_partition)
+ {
+ /*
+ * merge the column options into the column from the parent
+ */
+ coldef->is_not_null = restdef->is_not_null;
+ coldef->raw_default = restdef->raw_default;
+ coldef->cooked_default = restdef->cooked_default;
+ coldef->constraints = restdef->constraints;
+ coldef->is_for_partition = false; /* job is done */
+ list_delete_cell(schema, rest, prev);
+ }
else
ereport(ERROR,
(errcode(ERRCODE_DUPLICATE_COLUMN),
@@ -1563,8 +1588,12 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
*/
relation = heap_openrv(parent, ShareUpdateExclusiveLock);
- /* Cannot inherit from partitioned tables */
- if (relation->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+ /*
+ * Cannot inherit from a partitioned table unless being created as
+ * partition.
+ */
+ if (relation->rd_rel->relkind == RELKIND_PARTITIONED_REL &&
+ !is_partition)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot inherit from partitioned table \"%s\"",
@@ -1710,6 +1739,7 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
def->is_local = false;
def->is_not_null = attribute->attnotnull;
def->is_from_type = false;
+ def->is_for_partition = false;
def->storage = attribute->attstorage;
def->raw_default = NULL;
def->cooked_default = NULL;
@@ -1838,6 +1868,9 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
* If we had no inherited attributes, the result schema is just the
* explicitly declared columns. Otherwise, we need to merge the declared
* columns into the inherited schema list.
+ *
+ * Note: In case of a partition, there are only inherited attributes that
+ * we copied from the parent as so, in transformPartitionOf().
*/
if (inhSchema != NIL)
{
@@ -1867,16 +1900,20 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
/*
* Yes, try to merge the two column definitions. They must
- * have the same type, typmod, and collation.
+ * have the same type, typmod, and collation. Don't output
+ * the notices, if partition, per note above.
*/
- if (exist_attno == schema_attno)
- ereport(NOTICE,
- (errmsg("merging column \"%s\" with inherited definition",
- attributeName)));
- else
- ereport(NOTICE,
- (errmsg("moving and merging column \"%s\" with inherited definition", attributeName),
- errdetail("User-specified column moved to the position of the inherited column.")));
+ if (!is_partition)
+ {
+ if (exist_attno == schema_attno)
+ ereport(NOTICE,
+ (errmsg("merging column \"%s\" with inherited definition",
+ attributeName)));
+ else
+ ereport(NOTICE,
+ (errmsg("moving and merging column \"%s\" with inherited definition", attributeName),
+ errdetail("User-specified column moved to the position of the inherited column.")));
+ }
def = (ColumnDef *) list_nth(inhSchema, exist_attno - 1);
typenameTypeIdAndMod(NULL, def->typeName, &defTypeId, &deftypmod);
typenameTypeIdAndMod(NULL, newdef->typeName, &newTypeId, &newtypmod);
@@ -1913,8 +1950,9 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
storage_name(def->storage),
storage_name(newdef->storage))));
- /* Mark the column as locally defined */
- def->is_local = true;
+ /* Mark the column as locally defined, unless partition */
+ if (!is_partition)
+ def->is_local = true;
/* Merge of NOT NULL constraints = OR 'em together */
def->is_not_null |= newdef->is_not_null;
/* If new def has a default, override previous default */
@@ -2093,16 +2131,20 @@ StoreCatalogInheritance1(Oid relationId, Oid parentOid,
heap_freetuple(tuple);
/*
- * Store a dependency too
+ * Store a dependency too, iff a regular inheritance relationship, which
+ * it's not, if parent is a partitioned table.
*/
- parentobject.classId = RelationRelationId;
- parentobject.objectId = parentOid;
- parentobject.objectSubId = 0;
- childobject.classId = RelationRelationId;
- childobject.objectId = relationId;
- childobject.objectSubId = 0;
+ if (!relid_is_partitioned(parentOid))
+ {
+ parentobject.classId = RelationRelationId;
+ parentobject.objectId = parentOid;
+ parentobject.objectSubId = 0;
+ childobject.classId = RelationRelationId;
+ childobject.objectId = relationId;
+ childobject.objectSubId = 0;
- recordDependencyOn(&childobject, &parentobject, DEPENDENCY_NORMAL);
+ recordDependencyOn(&childobject, &parentobject, DEPENDENCY_NORMAL);
+ }
/*
* Post creation hook of this inheritance. Since object_access_hook
@@ -3108,6 +3150,11 @@ AlterTableGetLockLevel(List *cmds)
cmd_lockmode = AlterTableGetRelOptionsLockLevel((List *) cmd->def);
break;
+ case AT_AttachPartition:
+ case AT_DetachPartition:
+ cmd_lockmode = AccessExclusiveLock;
+ break;
+
default: /* oops */
elog(ERROR, "unrecognized alter table type: %d",
(int) cmd->subtype);
@@ -3425,6 +3472,12 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
/* No command-specific prep needed */
pass = AT_PASS_MISC;
break;
+ case AT_AttachPartition:
+ case AT_DetachPartition:
+ ATSimplePermissions(rel, ATT_TABLE);
+ /* No command-specific prep needed */
+ pass = AT_PASS_MISC;
+ break;
default: /* oops */
elog(ERROR, "unrecognized alter table type: %d",
(int) cmd->subtype);
@@ -3744,6 +3797,12 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
case AT_GenericOptions:
ATExecGenericOptions(rel, (List *) cmd->def);
break;
+ case AT_AttachPartition:
+ ATExecAttachPartition(wqueue, rel, (PartitionCmd *) cmd->def);
+ break;
+ case AT_DetachPartition:
+ ATExecDetachPartition(rel, ((PartitionCmd *) cmd->def)->name);
+ break;
default: /* oops */
elog(ERROR, "unrecognized alter table type: %d",
(int) cmd->subtype);
@@ -10188,12 +10247,7 @@ ATPrepAddInherit(Relation child_rel)
static ObjectAddress
ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode)
{
- Relation parent_rel,
- catalogRelation;
- SysScanDesc scan;
- ScanKeyData key;
- HeapTuple inheritsTuple;
- int32 inhseqno;
+ Relation parent_rel;
List *children;
ObjectAddress address;
@@ -10238,38 +10292,6 @@ ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode)
errmsg("cannot inherit from partitioned table \"%s\"", parent->relname)));
/*
- * Check for duplicates in the list of parents, and determine the highest
- * inhseqno already present; we'll use the next one for the new parent.
- * (Note: get RowExclusiveLock because we will write pg_inherits below.)
- *
- * Note: we do not reject the case where the child already inherits from
- * the parent indirectly; CREATE TABLE doesn't reject comparable cases.
- */
- catalogRelation = heap_open(InheritsRelationId, RowExclusiveLock);
- ScanKeyInit(&key,
- Anum_pg_inherits_inhrelid,
- BTEqualStrategyNumber, F_OIDEQ,
- ObjectIdGetDatum(RelationGetRelid(child_rel)));
- scan = systable_beginscan(catalogRelation, InheritsRelidSeqnoIndexId,
- true, NULL, 1, &key);
-
- /* inhseqno sequences start at 1 */
- inhseqno = 0;
- while (HeapTupleIsValid(inheritsTuple = systable_getnext(scan)))
- {
- Form_pg_inherits inh = (Form_pg_inherits) GETSTRUCT(inheritsTuple);
-
- if (inh->inhparent == RelationGetRelid(parent_rel))
- ereport(ERROR,
- (errcode(ERRCODE_DUPLICATE_TABLE),
- errmsg("relation \"%s\" would be inherited from more than once",
- RelationGetRelationName(parent_rel))));
- if (inh->inhseqno > inhseqno)
- inhseqno = inh->inhseqno;
- }
- systable_endscan(scan);
-
- /*
* Prevent circularity by seeing if proposed parent inherits from child.
* (In particular, this disallows making a rel inherit from itself.)
*
@@ -10302,6 +10324,69 @@ ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode)
RelationGetRelationName(child_rel),
RelationGetRelationName(parent_rel))));
+ /* OK to create inheritance */
+ CreateInheritance(child_rel, parent_rel);
+
+ ObjectAddressSet(address, RelationRelationId,
+ RelationGetRelid(parent_rel));
+
+ /* keep our lock on the parent relation until commit */
+ heap_close(parent_rel, NoLock);
+
+ return address;
+}
+
+/*
+ * CreateInheritance
+ * Catalog manipulation portion of creating inheritance between a child
+ * table and a parent table.
+ *
+ * Common to ATExecAddInherit() and ATExecAttachPartition().
+ */
+static void
+CreateInheritance(Relation child_rel, Relation parent_rel)
+{
+ Relation catalogRelation;
+ SysScanDesc scan;
+ ScanKeyData key;
+ HeapTuple inheritsTuple;
+ int32 inhseqno;
+
+ /* Note: get RowExclusiveLock because we will write pg_inherits below. */
+ catalogRelation = heap_open(InheritsRelationId, RowExclusiveLock);
+
+ /*
+ * Check for duplicates in the list of parents, and determine the highest
+ * inhseqno already present; we'll use the next one for the new parent.
+ * Also, if proposed child is a partition, it cannot already be inheriting.
+ *
+ * Note: we do not reject the case where the child already inherits from
+ * the parent indirectly; CREATE TABLE doesn't reject comparable cases.
+ */
+ ScanKeyInit(&key,
+ Anum_pg_inherits_inhrelid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(RelationGetRelid(child_rel)));
+ scan = systable_beginscan(catalogRelation, InheritsRelidSeqnoIndexId,
+ true, NULL, 1, &key);
+
+ /* inhseqno sequences start at 1 */
+ inhseqno = 0;
+ while (HeapTupleIsValid(inheritsTuple = systable_getnext(scan)))
+ {
+ Form_pg_inherits inh = (Form_pg_inherits) GETSTRUCT(inheritsTuple);
+
+ if (inh->inhparent == RelationGetRelid(parent_rel))
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_TABLE),
+ errmsg("relation \"%s\" would be inherited from more than once",
+ RelationGetRelationName(parent_rel))));
+
+ if (inh->inhseqno > inhseqno)
+ inhseqno = inh->inhseqno;
+ }
+ systable_endscan(scan);
+
/* Match up the columns and bump attinhcount as needed */
MergeAttributesIntoExisting(child_rel, parent_rel);
@@ -10316,16 +10401,8 @@ ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode)
inhseqno + 1,
catalogRelation);
- ObjectAddressSet(address, RelationRelationId,
- RelationGetRelid(parent_rel));
-
/* Now we're done with pg_inherits */
heap_close(catalogRelation, RowExclusiveLock);
-
- /* keep our lock on the parent relation until commit */
- heap_close(parent_rel, NoLock);
-
- return address;
}
/*
@@ -10376,7 +10453,7 @@ constraints_equivalent(HeapTuple a, HeapTuple b, TupleDesc tupleDesc)
* Check columns in child table match up with columns in parent, and increment
* their attinhcount.
*
- * Called by ATExecAddInherit
+ * Called by CreateInheritance
*
* Currently all parent columns must be found in child. Missing columns are an
* error. One day we might consider creating new columns like CREATE TABLE
@@ -10394,11 +10471,13 @@ MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel)
int parent_natts;
TupleDesc tupleDesc;
HeapTuple tuple;
+ bool is_attach_partition;
attrrel = heap_open(AttributeRelationId, RowExclusiveLock);
tupleDesc = RelationGetDescr(parent_rel);
parent_natts = tupleDesc->natts;
+ is_attach_partition = relid_is_partitioned(RelationGetRelid(parent_rel));
for (parent_attno = 1; parent_attno <= parent_natts; parent_attno++)
{
@@ -10421,14 +10500,16 @@ MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel)
attribute->atttypmod != childatt->atttypmod)
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
- errmsg("child table \"%s\" has different type for column \"%s\"",
+ errmsg("%s table \"%s\" has different type for column \"%s\"",
+ is_attach_partition ? "source" : "child",
RelationGetRelationName(child_rel),
attributeName)));
if (attribute->attcollation != childatt->attcollation)
ereport(ERROR,
(errcode(ERRCODE_COLLATION_MISMATCH),
- errmsg("child table \"%s\" has different collation for column \"%s\"",
+ errmsg("%s table \"%s\" has different collation for column \"%s\"",
+ is_attach_partition ? "source" : "child",
RelationGetRelationName(child_rel),
attributeName)));
@@ -10439,8 +10520,9 @@ MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel)
if (attribute->attnotnull && !childatt->attnotnull)
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
- errmsg("column \"%s\" in child table must be marked NOT NULL",
- attributeName)));
+ errmsg("column \"%s\" in %s table must be marked NOT NULL",
+ attributeName,
+ is_attach_partition ? "source" : "child")));
/*
* OK, bump the child column's inheritance count. (If we fail
@@ -10455,7 +10537,8 @@ MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel)
{
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
- errmsg("child table is missing column \"%s\"",
+ errmsg("%s table is missing column \"%s\"",
+ is_attach_partition ? "source" : "child",
attributeName)));
}
}
@@ -10469,7 +10552,7 @@ MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel)
*
* Constraints that are marked ONLY in the parent are ignored.
*
- * Called by ATExecAddInherit
+ * Called by CreateInheritance
*
* Currently all constraints in parent must be present in the child. One day we
* may consider adding new constraints like CREATE TABLE does.
@@ -10488,9 +10571,11 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
SysScanDesc parent_scan;
ScanKeyData parent_key;
HeapTuple parent_tuple;
+ bool is_attach_partition;
catalog_relation = heap_open(ConstraintRelationId, RowExclusiveLock);
tuple_desc = RelationGetDescr(catalog_relation);
+ is_attach_partition = relid_is_partitioned(RelationGetRelid(parent_rel));
/* Outer loop scans through the parent's constraint definitions */
ScanKeyInit(&parent_key,
@@ -10538,7 +10623,8 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
if (!constraints_equivalent(parent_tuple, child_tuple, tuple_desc))
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
- errmsg("child table \"%s\" has different definition for check constraint \"%s\"",
+ errmsg("%s table \"%s\" has different definition for check constraint \"%s\"",
+ is_attach_partition ? "source" : "child",
RelationGetRelationName(child_rel),
NameStr(parent_con->conname))));
@@ -10546,8 +10632,9 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
if (child_con->connoinherit)
ereport(ERROR,
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
- errmsg("constraint \"%s\" conflicts with non-inherited constraint on child table \"%s\"",
+ errmsg("constraint \"%s\" conflicts with non-inherited constraint on %s table \"%s\"",
NameStr(child_con->conname),
+ is_attach_partition ? "source" : "child",
RelationGetRelationName(child_rel))));
/*
@@ -10570,7 +10657,8 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
if (!found)
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
- errmsg("child table is missing constraint \"%s\"",
+ errmsg("%s table is missing constraint \"%s\"",
+ is_attach_partition ? "source" : "child",
NameStr(parent_con->conname))));
}
@@ -10581,6 +10669,41 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
/*
* ALTER TABLE NO INHERIT
*
+ * Return value is the address of the relation that is no longer parent.
+ */
+static ObjectAddress
+ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
+{
+ ObjectAddress address;
+ Relation parent_rel;
+
+ /*
+ * AccessShareLock on the parent is probably enough, seeing that DROP
+ * TABLE doesn't lock parent tables at all. We need some lock since we'll
+ * be inspecting the parent's schema.
+ */
+ parent_rel = heap_openrv(parent, AccessShareLock);
+
+ /*
+ * We don't bother to check ownership of the parent table --- ownership of
+ * the child is presumed enough rights.
+ */
+
+ /* Off to RemoveInheritance() where most of the work happens */
+ RemoveInheritance(rel, parent_rel);
+
+ /* keep our lock on the parent relation until commit */
+ heap_close(parent_rel, NoLock);
+
+ ObjectAddressSet(address, RelationRelationId,
+ RelationGetRelid(parent_rel));
+
+ return address;
+}
+
+/*
+ * RemoveInheritance
+ *
* Drop a parent from the child's parents. This just adjusts the attinhcount
* and attislocal of the columns and removes the pg_inherit and pg_depend
* entries.
@@ -10594,13 +10717,11 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
* coninhcount and conislocal for inherited constraints are adjusted in
* exactly the same way.
*
- * Return value is the address of the relation that is no longer parent.
+ * Common to ATExecDropInherit() and ATExecDetachPartition().
*/
-static ObjectAddress
-ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
+static void
+RemoveInheritance(Relation child_rel, Relation parent_rel)
{
- Relation parent_rel;
- Oid parent_oid;
Relation catalogRelation;
SysScanDesc scan;
ScanKeyData key[3];
@@ -10609,19 +10730,9 @@ ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
constraintTuple;
List *connames;
bool found = false;
- ObjectAddress address;
+ bool is_detach_partition;
- /*
- * AccessShareLock on the parent is probably enough, seeing that DROP
- * TABLE doesn't lock parent tables at all. We need some lock since we'll
- * be inspecting the parent's schema.
- */
- parent_rel = heap_openrv(parent, AccessShareLock);
-
- /*
- * We don't bother to check ownership of the parent table --- ownership of
- * the child is presumed enough rights.
- */
+ is_detach_partition = relid_is_partitioned(RelationGetRelid(parent_rel));
/*
* Find and destroy the pg_inherits entry linking the two, or error out if
@@ -10631,7 +10742,7 @@ ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
ScanKeyInit(&key[0],
Anum_pg_inherits_inhrelid,
BTEqualStrategyNumber, F_OIDEQ,
- ObjectIdGetDatum(RelationGetRelid(rel)));
+ ObjectIdGetDatum(RelationGetRelid(child_rel)));
scan = systable_beginscan(catalogRelation, InheritsRelidSeqnoIndexId,
true, NULL, 1, key);
@@ -10652,11 +10763,20 @@ ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
heap_close(catalogRelation, RowExclusiveLock);
if (!found)
- ereport(ERROR,
- (errcode(ERRCODE_UNDEFINED_TABLE),
- errmsg("relation \"%s\" is not a parent of relation \"%s\"",
- RelationGetRelationName(parent_rel),
- RelationGetRelationName(rel))));
+ {
+ if (!is_detach_partition)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_TABLE),
+ errmsg("relation \"%s\" is not a parent of relation \"%s\"",
+ RelationGetRelationName(parent_rel),
+ RelationGetRelationName(child_rel))));
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_TABLE),
+ errmsg("relation \"%s\" is not a partition of relation \"%s\"",
+ RelationGetRelationName(child_rel),
+ RelationGetRelationName(parent_rel))));
+ }
/*
* Search through child columns looking for ones matching parent rel
@@ -10665,7 +10785,7 @@ ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
ScanKeyInit(&key[0],
Anum_pg_attribute_attrelid,
BTEqualStrategyNumber, F_OIDEQ,
- ObjectIdGetDatum(RelationGetRelid(rel)));
+ ObjectIdGetDatum(RelationGetRelid(child_rel)));
scan = systable_beginscan(catalogRelation, AttributeRelidNumIndexId,
true, NULL, 1, key);
while (HeapTupleIsValid(attributeTuple = systable_getnext(scan)))
@@ -10727,7 +10847,7 @@ ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
ScanKeyInit(&key[0],
Anum_pg_constraint_conrelid,
BTEqualStrategyNumber, F_OIDEQ,
- ObjectIdGetDatum(RelationGetRelid(rel)));
+ ObjectIdGetDatum(RelationGetRelid(child_rel)));
scan = systable_beginscan(catalogRelation, ConstraintRelidIndexId,
true, NULL, 1, key);
@@ -10758,7 +10878,7 @@ ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
if (copy_con->coninhcount <= 0) /* shouldn't happen */
elog(ERROR, "relation %u has non-inherited constraint \"%s\"",
- RelationGetRelid(rel), NameStr(copy_con->conname));
+ RelationGetRelid(child_rel), NameStr(copy_con->conname));
copy_con->coninhcount--;
if (copy_con->coninhcount == 0)
@@ -10770,30 +10890,22 @@ ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
}
}
- parent_oid = RelationGetRelid(parent_rel);
-
systable_endscan(scan);
heap_close(catalogRelation, RowExclusiveLock);
- drop_parent_dependency(RelationGetRelid(rel),
- RelationRelationId,
- RelationGetRelid(parent_rel));
-
+ /* Remember that dependency exists iff regular inheritance. */
+ if (!is_detach_partition)
+ drop_parent_dependency(RelationGetRelid(child_rel),
+ RelationRelationId,
+ RelationGetRelid(parent_rel));
/*
* Post alter hook of this inherits. Since object_access_hook doesn't take
* multiple object identifiers, we relay oid of parent relation using
* auxiliary_id argument.
*/
InvokeObjectPostAlterHookArg(InheritsRelationId,
- RelationGetRelid(rel), 0,
+ RelationGetRelid(child_rel), 0,
RelationGetRelid(parent_rel), false);
-
- /* keep our lock on the parent relation until commit */
- heap_close(parent_rel, NoLock);
-
- ObjectAddressSet(address, RelationRelationId, parent_oid);
-
- return address;
}
/*
@@ -12426,3 +12538,137 @@ ComputePartitionAttrs(Oid relid, List *partParams, AttrNumber *partattrs,
partopclass[attn++] = opclassOid;
}
}
+
+
+/*
+ * ALTER TABLE <name> ATTACH PARTITION <partition-name> FOR VALUES
+ *
+ * Return the address of the newly attached partition.
+ */
+static ObjectAddress
+ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
+{
+ Relation attachRel,
+ inheritsRelation;
+ SysScanDesc scan;
+ ScanKeyData key;
+ HeapTuple tuple;
+ AttrNumber attno;
+ int natts;
+ TupleDesc tupleDesc;
+ ObjectAddress address;
+
+ attachRel = heap_openrv(cmd->name, AccessShareLock);
+
+ /*
+ * Must be owner of both parent and source table -- parent was checked by
+ * ATSimplePermissions call in ATPrepCmd
+ */
+ ATSimplePermissions(attachRel, ATT_TABLE);
+
+ if (attachRel->rd_rel->reloftype)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot attach a typed table as partition")));
+
+ if (attachRel->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot attach a partitioned table as partition")));
+
+ /* attachRel should not already be a inheritance child of some relation */
+ inheritsRelation = heap_open(InheritsRelationId, AccessShareLock);
+ ScanKeyInit(&key,
+ Anum_pg_inherits_inhrelid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(RelationGetRelid(attachRel)));
+ scan = systable_beginscan(inheritsRelation, InheritsRelidSeqnoIndexId,
+ true, NULL, 1, &key);
+ if (HeapTupleIsValid(systable_getnext(scan)))
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot attach table that inherits as partition")));
+ systable_endscan(scan);
+ heap_close(inheritsRelation, AccessShareLock);
+
+ /* If attachRel is temp, it must belong to this session */
+ if (attachRel->rd_rel->relpersistence == RELPERSISTENCE_TEMP &&
+ !attachRel->rd_islocaltemp)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot attach a temporary relation of another session as partition ")));
+
+ /* If parent has OIDs, attachRel must have OIDs */
+ if (rel->rd_rel->relhasoids && !attachRel->rd_rel->relhasoids)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot attach table \"%s\" without OIDs as partition of"
+ " table \"%s\" with OIDs", RelationGetRelationName(attachRel),
+ RelationGetRelationName(rel))));
+
+ /* OTOH, if parent doesn't have them, do not allow in attachRel either */
+ if (attachRel->rd_rel->relhasoids && !rel->rd_rel->relhasoids)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot attach table \"%s\" with OIDs as partition of table"
+ " \"%s\" without OIDs", RelationGetRelationName(attachRel),
+ RelationGetRelationName(rel))));
+
+ /* Check if there are any columns in attachRel that aren't in the parent */
+ tupleDesc = RelationGetDescr(attachRel);
+ natts = tupleDesc->natts;
+ for (attno = 1; attno <= natts; attno++)
+ {
+ Form_pg_attribute attribute = tupleDesc->attrs[attno - 1];
+ char *attributeName = NameStr(attribute->attname);
+
+ /* Ignore dropped */
+ if (attribute->attisdropped)
+ continue;
+
+ /* Find same column in parent (matching on column name). */
+ tuple = SearchSysCacheCopyAttName(RelationGetRelid(rel), attributeName);
+ if (!HeapTupleIsValid(tuple))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("table \"%s\" contains column \"%s\" not found in parent \"%s\"",
+ RelationGetRelationName(attachRel), attributeName,
+ RelationGetRelationName(rel)),
+ errdetail("Table being attached should contain only the columns"
+ " present in parent.")));
+ }
+
+ /* OK to create inheritance. Rest of the checks performed there */
+ CreateInheritance(attachRel, rel);
+
+ ObjectAddressSet(address, RelationRelationId, RelationGetRelid(attachRel));
+
+ /* keep our lock until commit */
+ heap_close(attachRel, NoLock);
+
+ return address;
+}
+
+/*
+ * ALTER TABLE DETACH PARTITION
+ *
+ * Return the address of the relation that is no longer a partition of rel.
+ */
+static ObjectAddress
+ATExecDetachPartition(Relation rel, RangeVar *name)
+{
+ Relation partRel;
+ ObjectAddress address;
+
+ partRel = heap_openrv(name, AccessShareLock);
+
+ /* All inheritance related checks are performed within the function */
+ RemoveInheritance(partRel, rel);
+
+ ObjectAddressSet(address, RelationRelationId, RelationGetRelid(partRel));
+
+ /* keep our lock until commit */
+ heap_close(partRel, NoLock);
+
+ return address;
+}
diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c
index e9d9ba2..c9c72d9 100644
--- a/src/backend/commands/view.c
+++ b/src/backend/commands/view.c
@@ -95,6 +95,7 @@ DefineVirtualRelation(RangeVar *relation, List *tlist, bool replace,
def->is_local = true;
def->is_not_null = false;
def->is_from_type = false;
+ def->is_for_partition = false;
def->storage = 0;
def->raw_default = NULL;
def->cooked_default = NULL;
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 968a3f4..111fcf5 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2606,6 +2606,7 @@ _copyColumnDef(const ColumnDef *from)
COPY_SCALAR_FIELD(is_local);
COPY_SCALAR_FIELD(is_not_null);
COPY_SCALAR_FIELD(is_from_type);
+ COPY_SCALAR_FIELD(is_for_partition);
COPY_SCALAR_FIELD(storage);
COPY_NODE_FIELD(raw_default);
COPY_NODE_FIELD(cooked_default);
@@ -3001,6 +3002,7 @@ CopyCreateStmtFields(const CreateStmt *from, CreateStmt *newnode)
COPY_NODE_FIELD(relation);
COPY_NODE_FIELD(tableElts);
COPY_NODE_FIELD(inhRelations);
+ COPY_NODE_FIELD(partbound);
COPY_NODE_FIELD(partby);
COPY_NODE_FIELD(ofTypename);
COPY_NODE_FIELD(constraints);
@@ -4183,6 +4185,43 @@ _copyPartitionElem(const PartitionElem *from)
return newnode;
}
+static PartitionListSpec *
+_copyPartitionListSpec(const PartitionListSpec *from)
+{
+ PartitionListSpec *newnode = makeNode(PartitionListSpec);
+
+ COPY_NODE_FIELD(values);
+ COPY_LOCATION_FIELD(location);
+
+ return newnode;
+}
+
+static PartitionRangeSpec *
+_copyPartitionRangeSpec(const PartitionRangeSpec *from)
+{
+ PartitionRangeSpec *newnode = makeNode(PartitionRangeSpec);
+
+ COPY_SCALAR_FIELD(lowerinc);
+ COPY_NODE_FIELD(lower);
+ COPY_SCALAR_FIELD(upperinc);
+ COPY_NODE_FIELD(upper);
+ COPY_LOCATION_FIELD(location);
+
+ return newnode;
+}
+
+static PartitionCmd *
+_copyPartitionCmd(const PartitionCmd *from)
+{
+ PartitionCmd *newnode = makeNode(PartitionCmd);
+
+ COPY_NODE_FIELD(name);
+ COPY_NODE_FIELD(bound);
+ COPY_SCALAR_FIELD(skip_validate);
+
+ return newnode;
+}
+
/* ****************************************************************
* pg_list.h copy functions
* ****************************************************************
@@ -5082,6 +5121,15 @@ copyObject(const void *from)
case T_PartitionElem:
retval = _copyPartitionElem(from);
break;
+ case T_PartitionListSpec:
+ retval = _copyPartitionListSpec(from);
+ break;
+ case T_PartitionRangeSpec:
+ retval = _copyPartitionRangeSpec(from);
+ break;
+ case T_PartitionCmd:
+ retval = _copyPartitionCmd(from);
+ break;
default:
elog(ERROR, "unrecognized node type: %d", (int) nodeTag(from));
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 56325fe..1a25acf 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1156,6 +1156,7 @@ _equalCreateStmt(const CreateStmt *a, const CreateStmt *b)
COMPARE_NODE_FIELD(relation);
COMPARE_NODE_FIELD(tableElts);
COMPARE_NODE_FIELD(inhRelations);
+ COMPARE_NODE_FIELD(partbound);
COMPARE_NODE_FIELD(partby);
COMPARE_NODE_FIELD(ofTypename);
COMPARE_NODE_FIELD(constraints);
@@ -2363,6 +2364,7 @@ _equalColumnDef(const ColumnDef *a, const ColumnDef *b)
COMPARE_SCALAR_FIELD(is_local);
COMPARE_SCALAR_FIELD(is_not_null);
COMPARE_SCALAR_FIELD(is_from_type);
+ COMPARE_SCALAR_FIELD(is_for_partition);
COMPARE_SCALAR_FIELD(storage);
COMPARE_NODE_FIELD(raw_default);
COMPARE_NODE_FIELD(cooked_default);
@@ -2641,6 +2643,37 @@ _equalPartitionElem(const PartitionElem *a, const PartitionElem *b)
return true;
}
+static bool
+_equalPartitionListSpec(const PartitionListSpec *a, const PartitionListSpec *b)
+{
+ COMPARE_NODE_FIELD(values);
+ COMPARE_LOCATION_FIELD(location);
+
+ return true;
+}
+
+static bool
+_equalPartitionRangeSpec(const PartitionRangeSpec *a, const PartitionRangeSpec *b)
+{
+ COMPARE_SCALAR_FIELD(lowerinc);
+ COMPARE_NODE_FIELD(lower);
+ COMPARE_SCALAR_FIELD(upperinc);
+ COMPARE_NODE_FIELD(upper);
+ COMPARE_LOCATION_FIELD(location);
+
+ return true;
+}
+
+static bool
+_equalPartitionCmd(const PartitionCmd *a, const PartitionCmd *b)
+{
+ COMPARE_NODE_FIELD(name);
+ COMPARE_NODE_FIELD(bound);
+ COMPARE_SCALAR_FIELD(skip_validate);
+
+ return true;
+}
+
/*
* Stuff from pg_list.h
*/
@@ -3397,6 +3430,15 @@ equal(const void *a, const void *b)
case T_PartitionElem:
retval = _equalPartitionElem(a, b);
break;
+ case T_PartitionListSpec:
+ retval = _equalPartitionListSpec(a, b);
+ break;
+ case T_PartitionRangeSpec:
+ retval = _equalPartitionRangeSpec(a, b);
+ break;
+ case T_PartitionCmd:
+ retval = _equalPartitionCmd(a, b);
+ break;
default:
elog(ERROR, "unrecognized node type: %d",
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 5623471..7537473 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2369,6 +2369,7 @@ _outCreateStmtInfo(StringInfo str, const CreateStmt *node)
WRITE_NODE_FIELD(relation);
WRITE_NODE_FIELD(tableElts);
WRITE_NODE_FIELD(inhRelations);
+ WRITE_NODE_FIELD(partbound);
WRITE_NODE_FIELD(partby);
WRITE_NODE_FIELD(ofTypename);
WRITE_NODE_FIELD(constraints);
@@ -2549,6 +2550,7 @@ _outColumnDef(StringInfo str, const ColumnDef *node)
WRITE_BOOL_FIELD(is_local);
WRITE_BOOL_FIELD(is_not_null);
WRITE_BOOL_FIELD(is_from_type);
+ WRITE_BOOL_FIELD(is_for_partition);
WRITE_CHAR_FIELD(storage);
WRITE_NODE_FIELD(raw_default);
WRITE_NODE_FIELD(cooked_default);
@@ -3239,6 +3241,25 @@ _outPartitionElem(StringInfo str, const PartitionElem *node)
WRITE_LOCATION_FIELD(location);
}
+static void
+_outPartitionListSpec(StringInfo str, const PartitionListSpec *node)
+{
+ WRITE_NODE_TYPE("PARTITIONLISTVALUES");
+
+ WRITE_NODE_FIELD(values);
+}
+
+static void
+_outPartitionRangeSpec(StringInfo str, const PartitionRangeSpec *node)
+{
+ WRITE_NODE_TYPE("PARTITIONRANGE");
+
+ WRITE_BOOL_FIELD(lowerinc);
+ WRITE_NODE_FIELD(lower);
+ WRITE_BOOL_FIELD(upperinc);
+ WRITE_NODE_FIELD(upper);
+}
+
/*
* outNode -
* converts a Node into ascii string and append it to 'str'
@@ -3823,6 +3844,12 @@ outNode(StringInfo str, const void *obj)
case T_PartitionElem:
_outPartitionElem(str, obj);
break;
+ case T_PartitionListSpec:
+ _outPartitionListSpec(str, obj);
+ break;
+ case T_PartitionRangeSpec:
+ _outPartitionRangeSpec(str, obj);
+ break;
default:
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 0607439..79fbb32 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -229,6 +229,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
VariableSetStmt *vsetstmt;
PartitionElem *partelem;
PartitionBy *partby;
+ PartitionRangeSpec *partrange;
}
%type <node> stmt schema_stmt
@@ -544,6 +545,14 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <partby> PartitionBy OptPartitionBy
%type <partelem> part_elem
%type <list> part_params
+%type <list> OptPartitionElementList PartitionElementList
+%type <node> PartitionElement
+%type <node> ForValues
+%type <node> partvalue
+%type <list> partvalue_list
+%type <boolean> lb_inc ub_inc
+%type <list> RangeBound
+%type <boolean> opt_validate_spec
/*
* Non-keyword token types. These are hard-wired into the "flex" lexer.
@@ -569,7 +578,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
/* ordinary key words in alphabetical order */
%token <keyword> ABORT_P ABSOLUTE_P ACCESS ACTION ADD_P ADMIN AFTER
AGGREGATE ALL ALSO ALTER ALWAYS ANALYSE ANALYZE AND ANY ARRAY AS ASC
- ASSERTION ASSIGNMENT ASYMMETRIC AT ATTRIBUTE AUTHORIZATION
+ ASSERTION ASSIGNMENT ASYMMETRIC AT ATTACH ATTRIBUTE AUTHORIZATION
BACKWARD BEFORE BEGIN_P BETWEEN BIGINT BINARY BIT
BOOLEAN_P BOTH BY
@@ -585,7 +594,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
DATA_P DATABASE DAY_P DEALLOCATE DEC DECIMAL_P DECLARE DEFAULT DEFAULTS
DEFERRABLE DEFERRED DEFINER DELETE_P DELIMITER DELIMITERS DEPENDS DESC
- DICTIONARY DISABLE_P DISCARD DISTINCT DO DOCUMENT_P DOMAIN_P DOUBLE_P DROP
+ DETACH DICTIONARY DISABLE_P DISCARD DISTINCT DO DOCUMENT_P DOMAIN_P
+ DOUBLE_P DROP
EACH ELSE ENABLE_P ENCODING ENCRYPTED END_P ENUM_P ESCAPE EVENT EXCEPT
EXCLUDE EXCLUDING EXCLUSIVE EXECUTE EXISTS EXPLAIN
@@ -599,7 +609,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
HANDLER HAVING HEADER_P HOLD HOUR_P
IDENTITY_P IF_P ILIKE IMMEDIATE IMMUTABLE IMPLICIT_P IMPORT_P IN_P
- INCLUDING INCREMENT INDEX INDEXES INHERIT INHERITS INITIALLY INLINE_P
+ INCLUDING INCLUSIVE INCREMENT INDEX INDEXES INHERIT INHERITS INITIALLY INLINE_P
INNER_P INOUT INPUT_P INSENSITIVE INSERT INSTEAD INT_P INTEGER
INTERSECT INTERVAL INTO INVOKER IS ISNULL ISOLATION
@@ -2373,6 +2383,38 @@ alter_table_cmd:
n->def = (Node *)$1;
$$ = (Node *) n;
}
+ /* ALTER TABLE <name> ATTACH PARTITION <table_name> FOR VALUES */
+ | ATTACH PARTITION qualified_name ForValues opt_validate_spec
+ {
+ AlterTableCmd *n = makeNode(AlterTableCmd);
+ PartitionCmd *cmd = makeNode(PartitionCmd);
+
+ n->subtype = AT_AttachPartition;
+ cmd->name = $3;
+ cmd->bound = (Node *) $4;
+ cmd->skip_validate = $5;
+ n->def = (Node *) cmd;
+
+ $$ = (Node *) n;
+ }
+ /* ALTER TABLE <name> DETACH PARTITION <partition_name> */
+ | DETACH PARTITION qualified_name
+ {
+ AlterTableCmd *n = makeNode(AlterTableCmd);
+ PartitionCmd *cmd = makeNode(PartitionCmd);
+
+ n->subtype = AT_DetachPartition;
+ cmd->name = $3;
+ n->def = (Node *) cmd;
+
+ $$ = (Node *) n;
+ }
+ ;
+
+opt_validate_spec:
+ NO VALIDATE { $$ = true; }
+ | VALIDATE { $$ = false; }
+ | /* EMPTY */ { $$ = false; }
;
alter_column_default:
@@ -2468,6 +2510,60 @@ reloption_elem:
}
;
+ForValues:
+ /* a LIST partition */
+ FOR VALUES IN_P '(' partvalue_list ')'
+ {
+ PartitionListSpec *n = makeNode(PartitionListSpec);
+
+ n->values = $5;
+ n->location = @1;
+
+ $$ = (Node *) n;
+ }
+
+ /* a RANGE partition */
+ | FOR VALUES START RangeBound lb_inc END_P RangeBound ub_inc
+ {
+ PartitionRangeSpec *n = makeNode(PartitionRangeSpec);
+
+ n->lowerinc = $5;
+ n->lower = $4;
+ n->upperinc = $8;
+ n->upper = $7;
+ n->location = @1;
+
+ $$ = (Node *) n;
+ }
+ ;
+
+RangeBound:
+ UNBOUNDED { $$ = NIL; }
+ | '(' partvalue_list ')' { $$ = $2; }
+ ;
+
+lb_inc:
+ EXCLUSIVE { $$ = false;}
+ | INCLUSIVE { $$ = true; }
+ | /* EMPTY */ { $$ = true; }
+ ;
+
+ub_inc:
+ INCLUSIVE { $$ = true; }
+ | EXCLUSIVE { $$ = false; }
+ | /* EMPTY */ { $$ = false; }
+ ;
+
+partvalue:
+ Sconst { $$ = makeStringConst($1, @1); }
+ | NumericOnly { $$ = makeAConst($1, @1); }
+ | NULL_P { $$ = makeNullAConst(@1); }
+ ;
+
+partvalue_list:
+ partvalue { $$ = list_make1($1); }
+ | partvalue_list ',' partvalue { $$ = lappend($1, $3); }
+ ;
/*****************************************************************************
*
@@ -2885,6 +2981,44 @@ CreateStmt: CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
n->if_not_exists = true;
$$ = (Node *)n;
}
+ | CREATE OptTemp TABLE qualified_name PARTITION OF qualified_name
+ OptPartitionElementList ForValues OptPartitionBy OptWith
+ OnCommitOption OptTableSpace
+ {
+ CreateStmt *n = makeNode(CreateStmt);
+ $4->relpersistence = $2;
+ n->relation = $4;
+ n->tableElts = $8;
+ n->inhRelations = list_make1($7);
+ n->partbound = (Node *) $9;
+ n->partby = $10;
+ n->ofTypename = NULL;
+ n->constraints = NIL;
+ n->options = $11;
+ n->oncommit = $12;
+ n->tablespacename = $13;
+ n->if_not_exists = false;
+ $$ = (Node *)n;
+ }
+ | CREATE OptTemp TABLE IF_P NOT EXISTS qualified_name PARTITION OF
+ qualified_name OptPartitionElementList ForValues OptPartitionBy
+ OptWith OnCommitOption OptTableSpace
+ {
+ CreateStmt *n = makeNode(CreateStmt);
+ $7->relpersistence = $2;
+ n->relation = $7;
+ n->tableElts = $11;
+ n->inhRelations = list_make1($10);
+ n->partbound = (Node *) $12;
+ n->partby = $13;
+ n->ofTypename = NULL;
+ n->constraints = NIL;
+ n->options = $14;
+ n->oncommit = $15;
+ n->tablespacename = $16;
+ n->if_not_exists = true;
+ $$ = (Node *)n;
+ }
;
/*
@@ -2930,6 +3064,11 @@ OptTypedTableElementList:
| /*EMPTY*/ { $$ = NIL; }
;
+OptPartitionElementList:
+ '(' PartitionElementList ')' { $$ = $2; }
+ | /*EMPTY*/ { $$ = NIL; }
+ ;
+
TableElementList:
TableElement
{
@@ -2952,6 +3091,17 @@ TypedTableElementList:
}
;
+PartitionElementList:
+ PartitionElement
+ {
+ $$ = list_make1($1);
+ }
+ | PartitionElementList ',' PartitionElement
+ {
+ $$ = lappend($1, $3);
+ }
+ ;
+
TableElement:
columnDef { $$ = $1; }
| TableLikeClause { $$ = $1; }
@@ -2963,6 +3113,11 @@ TypedTableElement:
| TableConstraint { $$ = $1; }
;
+PartitionElement:
+ columnOptions { $$ = $1; }
+ | TableConstraint { $$ = $1; }
+ ;
+
columnDef: ColId Typename create_generic_options ColQualList
{
ColumnDef *n = makeNode(ColumnDef);
@@ -2972,6 +3127,7 @@ columnDef: ColId Typename create_generic_options ColQualList
n->is_local = true;
n->is_not_null = false;
n->is_from_type = false;
+ n->is_for_partition = false;
n->storage = 0;
n->raw_default = NULL;
n->cooked_default = NULL;
@@ -2993,6 +3149,7 @@ columnOptions: ColId WITH OPTIONS ColQualList
n->is_local = true;
n->is_not_null = false;
n->is_from_type = false;
+ n->is_for_partition = false;
n->storage = 0;
n->raw_default = NULL;
n->cooked_default = NULL;
@@ -11122,6 +11279,7 @@ TableFuncElement: ColId Typename opt_collate_clause
n->is_local = true;
n->is_not_null = false;
n->is_from_type = false;
+ n->is_for_partition = false;
n->storage = 0;
n->raw_default = NULL;
n->cooked_default = NULL;
@@ -13805,6 +13963,7 @@ unreserved_keyword:
| ASSERTION
| ASSIGNMENT
| AT
+ | ATTACH
| ATTRIBUTE
| BACKWARD
| BEFORE
@@ -13851,6 +14010,7 @@ unreserved_keyword:
| DELIMITER
| DELIMITERS
| DEPENDS
+ | DETACH
| DICTIONARY
| DISABLE_P
| DISCARD
@@ -13893,6 +14053,7 @@ unreserved_keyword:
| IMPLICIT_P
| IMPORT_P
| INCLUDING
+ | INCLUSIVE
| INCREMENT
| INDEX
| INDEXES
diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c
index 77ffd84..d71fdfe 100644
--- a/src/backend/parser/parse_agg.c
+++ b/src/backend/parser/parse_agg.c
@@ -493,7 +493,9 @@ check_agglevels_and_constraints(ParseState *pstate, Node *expr)
err = _("grouping operations are not allowed in partition key expression");
break;
-
+ case EXPR_KIND_PARTITION_FOR_VALUES:
+ errkind = true;
+ break;
/*
* There is intentionally no default: case here, so that the
@@ -854,6 +856,9 @@ transformWindowFuncCall(ParseState *pstate, WindowFunc *wfunc,
case EXPR_KIND_PARTITION_KEY:
err = _("window functions are not allowed in partition key expression");
break;
+ case EXPR_KIND_PARTITION_FOR_VALUES:
+ errkind = true;
+ break;
/*
* There is intentionally no default: case here, so that the
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 147c6da..f14d776 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -352,6 +352,26 @@ transformExprRecurse(ParseState *pstate, Node *expr)
result = (Node *) expr;
break;
}
+ case T_PartitionListSpec:
+ {
+ PartitionListSpec *list = (PartitionListSpec *) expr;
+
+ list->values = transformExpressionList(pstate, list->values,
+ pstate->p_expr_kind);
+ result = expr;
+ break;
+ }
+ case T_PartitionRangeSpec:
+ {
+ PartitionRangeSpec *range = (PartitionRangeSpec *) expr;
+
+ range->lower = transformExpressionList(pstate, range->lower,
+ pstate->p_expr_kind);
+ range->upper = transformExpressionList(pstate, range->upper,
+ pstate->p_expr_kind);
+ result = expr;
+ break;
+ }
default:
/* should not reach here */
@@ -1698,6 +1718,7 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
case EXPR_KIND_OFFSET:
case EXPR_KIND_RETURNING:
case EXPR_KIND_VALUES:
+ case EXPR_KIND_PARTITION_FOR_VALUES:
/* okay */
break;
case EXPR_KIND_CHECK_CONSTRAINT:
@@ -3254,6 +3275,8 @@ ParseExprKindName(ParseExprKind exprKind)
return "WHEN";
case EXPR_KIND_PARTITION_KEY:
return "partition key expression";
+ case EXPR_KIND_PARTITION_FOR_VALUES:
+ return "FOR VALUES";
/*
* There is intentionally no default: case here, so that the
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 465597d..e69cbfc 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -48,8 +48,10 @@
#include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "nodes/nodeFuncs.h"
+#include "optimizer/planner.h"
#include "parser/analyze.h"
#include "parser/parse_clause.h"
+#include "parser/parse_coerce.h"
#include "parser/parse_collate.h"
#include "parser/parse_expr.h"
#include "parser/parse_relation.h"
@@ -89,6 +91,7 @@ typedef struct
* the table */
IndexStmt *pkey; /* PRIMARY KEY index, if any */
bool ispartitioned; /* true if table is partitioned */
+ Node *partbound; /* transformed FOR VALUES */
} CreateStmtContext;
/* State shared by transformCreateSchemaStmt and its subroutines */
@@ -131,6 +134,10 @@ static void transformConstraintAttrs(CreateStmtContext *cxt,
List *constraintList);
static void transformColumnType(CreateStmtContext *cxt, ColumnDef *column);
static void setSchemaName(char *context_schema, char **stmt_schema_name);
+static void transformPartitionOf(CreateStmtContext *cxt, Node *bound);
+static void transformAttachPartition(CreateStmtContext *cxt, PartitionCmd *cmd);
+static Node *transformPartitionBound(CreateStmtContext *cxt, Relation parent,
+ Node *bound);
/*
@@ -232,6 +239,7 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
cxt.alist = NIL;
cxt.pkey = NULL;
cxt.ispartitioned = stmt->partby != NULL;
+ cxt.partbound = NULL;
/*
* Notice that we allow OIDs here only for plain tables, even though
@@ -250,6 +258,9 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
if (stmt->ofTypename)
transformOfType(&cxt, stmt->ofTypename);
+ if (stmt->partbound)
+ transformPartitionOf(&cxt, stmt->partbound);
+
if (stmt->partby)
{
int partnatts = list_length(stmt->partby->partParams);
@@ -355,6 +366,7 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
*/
stmt->tableElts = cxt.columns;
stmt->constraints = cxt.ckconstraints;
+ stmt->partbound = cxt.partbound;
result = lappend(cxt.blist, stmt);
result = list_concat(result, cxt.alist);
@@ -894,6 +906,7 @@ transformTableLikeClause(CreateStmtContext *cxt, TableLikeClause *table_like_cla
def->is_local = true;
def->is_not_null = attribute->attnotnull;
def->is_from_type = false;
+ def->is_for_partition = false;
def->storage = 0;
def->raw_default = NULL;
def->cooked_default = NULL;
@@ -1113,6 +1126,7 @@ transformOfType(CreateStmtContext *cxt, TypeName *ofTypename)
n->is_local = true;
n->is_not_null = false;
n->is_from_type = true;
+ n->is_for_partition = false;
n->storage = 0;
n->raw_default = NULL;
n->cooked_default = NULL;
@@ -2580,6 +2594,7 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
cxt.alist = NIL;
cxt.pkey = NULL;
cxt.ispartitioned = relid_is_partitioned(relid);
+ cxt.partbound = NULL;
/*
* The only subtypes that currently require parse transformation handling
@@ -2668,6 +2683,22 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
break;
}
+ case AT_AttachPartition:
+ {
+ PartitionCmd *partcmd = (PartitionCmd *) cmd->def;
+
+ transformAttachPartition(&cxt, partcmd);
+
+ /* assign transformed values */
+ partcmd->bound = cxt.partbound;
+ }
+
+ newcmds = lappend(newcmds, cmd);
+ break;
+ case AT_DetachPartition:
+ newcmds = lappend(newcmds, cmd);
+ break;
+
default:
newcmds = lappend(newcmds, cmd);
break;
@@ -3032,3 +3063,290 @@ setSchemaName(char *context_schema, char **stmt_schema_name)
"different from the one being created (%s)",
*stmt_schema_name, context_schema)));
}
+
+/*
+ * transformPartitionOf
+ * Analyze PARTITION OF ... FOR VALUES ...
+ */
+static void
+transformPartitionOf(CreateStmtContext *cxt, Node *bound)
+{
+ TupleDesc tupdesc;
+ int i;
+ Relation parentRel;
+ RangeVar *partof = linitial(cxt->inhRelations);
+
+ parentRel = heap_openrv(partof, AccessShareLock);
+
+ /* Check if the target table is partitioned at all */
+ if (parentRel->rd_rel->relkind != RELKIND_PARTITIONED_REL)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("\"%s\" is not partitioned",
+ RelationGetRelationName(parentRel))));
+ Assert(RelationGetPartitionKey(parentRel) != NULL);
+
+ /* If parent rel is temp, it must belong to this session */
+ if (parentRel->rd_rel->relpersistence == RELPERSISTENCE_TEMP &&
+ !parentRel->rd_islocaltemp)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot create as partition of temporary relation of another session")));
+
+ /*
+ * Do not allow OIDs in a partition, if not present in the parent. But
+ * force them in partition, if they are present in the parent.
+ */
+ if (cxt->hasoids && !parentRel->rd_rel->relhasoids)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot create table with OIDs as partition of table without OIDs")));
+
+ if (parentRel->rd_rel->relhasoids)
+ cxt->hasoids = true;
+
+ tupdesc = RelationGetDescr(parentRel);
+ for (i = 0; i < tupdesc->natts; i++)
+ {
+ Form_pg_attribute attribute = tupdesc->attrs[i];
+ ColumnDef *def;
+
+ if (attribute->attisdropped)
+ continue;
+
+ def = makeNode(ColumnDef);
+ def->colname = pstrdup(NameStr(attribute->attname));
+ def->typeName = makeTypeNameFromOid(attribute->atttypid,
+ attribute->atttypmod);
+ def->inhcount = 1;
+ def->is_local = false;
+ def->is_not_null = attribute->attnotnull;
+ def->is_from_type = false;
+ def->is_for_partition = true;
+ def->storage = attribute->attstorage;
+ def->raw_default = NULL;
+ def->cooked_default = NULL;
+ def->collClause = NULL;
+ def->collOid = attribute->attcollation;
+ def->constraints = NIL;
+ def->location = -1;
+
+ cxt->columns = lappend(cxt->columns, def);
+ }
+
+ /* tranform the values */
+ cxt->partbound = transformPartitionBound(cxt, parentRel, bound);
+
+ heap_close(parentRel, AccessShareLock);
+}
+
+/*
+ * transformAttachPartition
+ * Analyze ATTACH PARTITION ... FOR VALUES ...
+ */
+static void
+transformAttachPartition(CreateStmtContext *cxt, PartitionCmd *cmd)
+{
+ Relation parentRel = cxt->rel;
+
+ /* Check if the target table is partitioned at all */
+ if (parentRel->rd_rel->relkind != RELKIND_PARTITIONED_REL)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("\"%s\" is not partitioned", cxt->relation->relname)));
+ Assert(RelationGetPartitionKey(parentRel) != NULL);
+
+ /* tranform the values */
+ cxt->partbound = transformPartitionBound(cxt, parentRel, cmd->bound);
+}
+
+/*
+ * transformPartitionBound
+ *
+ * Transform partition bound per the partition key
+ */
+static Node *
+transformPartitionBound(CreateStmtContext *cxt, Relation parent, Node *bound)
+{
+ int i;
+ ListCell *cell;
+ Oid parentId = RelationGetRelid(parent);
+ PartitionKey key = RelationGetPartitionKey(parent);
+ PartitionListSpec *list, *result_list;
+ PartitionRangeSpec *range, *result_range;
+
+ switch (key->strategy)
+ {
+ case PARTITION_STRAT_LIST:
+ if (!IsA(bound, PartitionListSpec))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("invalid bound specification for a list partition"),
+ parser_errposition(cxt->pstate, exprLocation(bound))));
+
+ list = (PartitionListSpec *) transformExpr(cxt->pstate, bound,
+ EXPR_KIND_PARTITION_FOR_VALUES);
+
+ result_list = makeNode(PartitionListSpec);
+
+ foreach(cell, list->values)
+ {
+ Node *value = (Node *) lfirst(cell);
+ Node *orig_value = value;
+ Oid valuetype;
+
+ valuetype = exprType(value);
+ value = coerce_to_target_type(cxt->pstate,
+ value, valuetype,
+ key->tcinfo->typid[0],
+ key->tcinfo->typmod[0],
+ COERCION_ASSIGNMENT,
+ COERCE_IMPLICIT_CAST,
+ -1);
+ if (value == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("specified value cannot be cast to type \"%s\""
+ " of key column \"%s\"",
+ format_type_be(key->tcinfo->typid[0]),
+ get_relid_attribute_name(parentId, key->partattrs[0])),
+ parser_errposition(cxt->pstate, exprLocation(orig_value))));
+
+ /* Simplify the expression */
+ value = (Node *) expression_planner((Expr *) value);
+
+ result_list->values = lappend(result_list->values, value);
+ }
+ return (Node *) result_list;
+
+ case PARTITION_STRAT_RANGE:
+ if (!IsA(bound, PartitionRangeSpec))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("invalid bound specification for a range partition"),
+ parser_errposition(cxt->pstate, exprLocation(bound))));
+
+ range = (PartitionRangeSpec *) transformExpr(cxt->pstate, bound,
+ EXPR_KIND_PARTITION_FOR_VALUES);
+
+ if (!range->lower && !range->upper)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("both START and END cannot be UNBOUNDED"),
+ parser_errposition(cxt->pstate, range->location)));
+
+ result_range = makeNode(PartitionRangeSpec);
+ result_range->lowerinc = range->lowerinc;
+ result_range->upperinc = range->upperinc;
+
+ if (range->lower)
+ {
+ if (list_length(range->lower) > key->partnatts)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("START has more values specified than number"
+ " of columns in the partition key"),
+ parser_errposition(cxt->pstate,
+ exprLocation(list_nth(range->lower,
+ list_length(range->lower) - 1)))));
+ else if (list_length(range->lower) < key->partnatts)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("START has fewer values specified than number"
+ " of columns in the partition key"),
+ parser_errposition(cxt->pstate,
+ exprLocation(list_nth(range->lower,
+ list_length(range->lower) - 1)))));
+
+ i = 0;
+ foreach (cell, range->lower)
+ {
+ Node *value = (Node *) lfirst(cell);
+ Node *orig_value = value;
+ Oid valuetype;
+
+ valuetype = exprType(value);
+ value = coerce_to_target_type(cxt->pstate,
+ value, valuetype,
+ key->tcinfo->typid[i],
+ key->tcinfo->typmod[i],
+ COERCION_ASSIGNMENT,
+ COERCE_IMPLICIT_CAST,
+ -1);
+ if (value == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("specified value cannot be cast to type"
+ " \"%s\" of key column \"%s\"",
+ format_type_be(key->tcinfo->typid[i]),
+ get_relid_attribute_name(parentId, key->partattrs[i])),
+ parser_errposition(cxt->pstate, exprLocation(orig_value))));
+
+ /* Simplify the expression */
+ value = (Node *) expression_planner((Expr *) value);
+
+ result_range->lower = lappend(result_range->lower, value);
+ ++i;
+ }
+ }
+ else
+ result_range->lowerinc = false;
+
+ if (range->upper)
+ {
+ if (list_length(range->upper) > key->partnatts)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("END has more values specified than number"
+ " of columns in the partition key"),
+ parser_errposition(cxt->pstate,
+ exprLocation(list_nth(range->upper,
+ list_length(range->upper) - 1)))));
+ else if (list_length(range->upper) < key->partnatts)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("END has fewer values specified than number"
+ " of columns in the partition key"),
+ parser_errposition(cxt->pstate,
+ exprLocation(list_nth(range->upper,
+ list_length(range->upper) - 1)))));
+
+ i = 0;
+ foreach (cell, range->upper)
+ {
+ Node *value = (Node *) lfirst(cell);
+ Node *orig_value = value;
+ Oid valuetype;
+
+ valuetype = exprType(value);
+ value = coerce_to_target_type(cxt->pstate,
+ value, valuetype,
+ key->tcinfo->typid[i],
+ key->tcinfo->typmod[i],
+ COERCION_ASSIGNMENT,
+ COERCE_IMPLICIT_CAST,
+ -1);
+ if (value == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("specified value cannot be cast to type"
+ " \"%s\" of key column \"%s\"",
+ format_type_be(key->tcinfo->typid[i]),
+ get_relid_attribute_name(parentId, key->partattrs[i])),
+ parser_errposition(cxt->pstate, exprLocation(orig_value))));
+
+ /* Simplify the expression */
+ value = (Node *) expression_planner((Expr *) value);
+
+ result_range->upper = lappend(result_range->upper, value);
+ ++i;
+ }
+ }
+ else
+ result_range->upperinc = false;
+
+ return (Node *) result_range;
+ }
+
+ return NULL;
+}
diff --git a/src/include/catalog/partition.h b/src/include/catalog/partition.h
index 2c876c4..31dbe17 100644
--- a/src/include/catalog/partition.h
+++ b/src/include/catalog/partition.h
@@ -14,6 +14,7 @@
#define PARTITION_H
#include "fmgr.h"
+#include "parser/parse_node.h"
#include "utils/relcache.h"
/* Type and collation information for partition key columns */
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 9e582a5..56bbe43 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -405,6 +405,7 @@ typedef enum NodeTag
T_AlterPolicyStmt,
T_CreateTransformStmt,
T_CreateAmStmt,
+ T_PartitionCmd,
/*
* TAGS FOR PARSE TREE NODES (parsenodes.h)
@@ -454,6 +455,8 @@ typedef enum NodeTag
T_RoleSpec,
T_PartitionElem,
T_PartitionBy,
+ T_PartitionListSpec,
+ T_PartitionRangeSpec,
/*
* TAGS FOR REPLICATION GRAMMAR PARSE NODES (replnodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 4f697b8..d9dff96 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -591,6 +591,7 @@ typedef struct ColumnDef
bool is_local; /* column has local (non-inherited) def'n */
bool is_not_null; /* NOT NULL constraint specified? */
bool is_from_type; /* column definition came from table type */
+ bool is_for_partition; /* column definition is for a partition */
char storage; /* attstorage setting, or 0 for default */
Node *raw_default; /* default value (untransformed parse tree) */
Node *cooked_default; /* default value (transformed expr tree) */
@@ -731,6 +732,40 @@ typedef struct PartitionBy
int location; /* token location, or -1 if unknown */
} PartitionBy;
+/*
+ * PartitionRangeSpec - range type syntax for range partition bound
+ */
+typedef struct PartitionListSpec
+{
+ NodeTag type;
+ List *values;
+ int location;
+} PartitionListSpec;
+
+/*
+ * PartitionRangeSpec - range type syntax for range partition bound
+ */
+typedef struct PartitionRangeSpec
+{
+ NodeTag type;
+ bool lowerinc;
+ List *lower;
+ bool upperinc;
+ List *upper;
+ int location; /* token location, or -1 if unknown */
+} PartitionRangeSpec;
+
+/*
+ * PartitionCmd - ALTER TABLE partition commands
+ */
+typedef struct PartitionCmd
+{
+ NodeTag type;
+ RangeVar *name;
+ Node *bound;
+ bool skip_validate;
+} PartitionCmd;
+
/****************************************************************************
* Nodes for a Query tree
****************************************************************************/
@@ -1558,7 +1593,9 @@ typedef enum AlterTableType
AT_DisableRowSecurity, /* DISABLE ROW SECURITY */
AT_ForceRowSecurity, /* FORCE ROW SECURITY */
AT_NoForceRowSecurity, /* NO FORCE ROW SECURITY */
- AT_GenericOptions /* OPTIONS (...) */
+ AT_GenericOptions, /* OPTIONS (...) */
+ AT_AttachPartition, /* ATTACH PARTITION */
+ AT_DetachPartition /* DETACH PARTITION */
} AlterTableType;
typedef struct ReplicaIdentityStmt
@@ -1783,7 +1820,9 @@ typedef struct CreateStmt
RangeVar *relation; /* relation to create */
List *tableElts; /* column definitions (list of ColumnDef) */
List *inhRelations; /* relations to inherit from (list of
- * inhRelation) */
+ * inhRelation); (ab)used also as partition
+ * parent */
+ Node *partbound; /* FOR VALUES clause */
PartitionBy *partby; /* PARTITION BY clause */
TypeName *ofTypename; /* OF typename */
List *constraints; /* constraints (list of Constraint nodes) */
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 40da67a..70c264c 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -49,6 +49,7 @@ PG_KEYWORD("assertion", ASSERTION, UNRESERVED_KEYWORD)
PG_KEYWORD("assignment", ASSIGNMENT, UNRESERVED_KEYWORD)
PG_KEYWORD("asymmetric", ASYMMETRIC, RESERVED_KEYWORD)
PG_KEYWORD("at", AT, UNRESERVED_KEYWORD)
+PG_KEYWORD("attach", ATTACH, UNRESERVED_KEYWORD)
PG_KEYWORD("attribute", ATTRIBUTE, UNRESERVED_KEYWORD)
PG_KEYWORD("authorization", AUTHORIZATION, TYPE_FUNC_NAME_KEYWORD)
PG_KEYWORD("backward", BACKWARD, UNRESERVED_KEYWORD)
@@ -127,6 +128,7 @@ PG_KEYWORD("delimiter", DELIMITER, UNRESERVED_KEYWORD)
PG_KEYWORD("delimiters", DELIMITERS, UNRESERVED_KEYWORD)
PG_KEYWORD("depends", DEPENDS, UNRESERVED_KEYWORD)
PG_KEYWORD("desc", DESC, RESERVED_KEYWORD)
+PG_KEYWORD("detach", DETACH, UNRESERVED_KEYWORD)
PG_KEYWORD("dictionary", DICTIONARY, UNRESERVED_KEYWORD)
PG_KEYWORD("disable", DISABLE_P, UNRESERVED_KEYWORD)
PG_KEYWORD("discard", DISCARD, UNRESERVED_KEYWORD)
@@ -191,6 +193,7 @@ PG_KEYWORD("implicit", IMPLICIT_P, UNRESERVED_KEYWORD)
PG_KEYWORD("import", IMPORT_P, UNRESERVED_KEYWORD)
PG_KEYWORD("in", IN_P, RESERVED_KEYWORD)
PG_KEYWORD("including", INCLUDING, UNRESERVED_KEYWORD)
+PG_KEYWORD("inclusive", INCLUSIVE, UNRESERVED_KEYWORD)
PG_KEYWORD("increment", INCREMENT, UNRESERVED_KEYWORD)
PG_KEYWORD("index", INDEX, UNRESERVED_KEYWORD)
PG_KEYWORD("indexes", INDEXES, UNRESERVED_KEYWORD)
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index a13c6fb..3d45663 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -65,7 +65,8 @@ typedef enum ParseExprKind
EXPR_KIND_EXECUTE_PARAMETER, /* parameter value in EXECUTE */
EXPR_KIND_TRIGGER_WHEN, /* WHEN condition in CREATE TRIGGER */
EXPR_KIND_POLICY, /* USING or WITH CHECK expr in policy */
- EXPR_KIND_PARTITION_KEY /* partition key expression */
+ EXPR_KIND_PARTITION_KEY, /* partition key expression */
+ EXPR_KIND_PARTITION_FOR_VALUES /* partition bound */
} ParseExprKind;
--
1.7.1
[text/x-diff] 0005-Infrastructure-for-partition-metadata-storage-and-ma-v5.patch (101.7K, ../../573ECCC3.7080104@lab.ntt.co.jp/6-0005-Infrastructure-for-partition-metadata-storage-and-ma-v5.patch)
download | inline diff:
From 35add25224c3f05f41eb9953a11612aba59a9972 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Fri, 18 Mar 2016 15:54:17 +0900
Subject: [PATCH 5/7] Infrastructure for partition metadata storage and manipulation.
Catalog pg_partition with:
partrelid oid
partbound pg_node_tree
Now since partition metadata is stored in a dedicated catalog, it is possible
to use that knowledge to enforce special behaviors for inheritance children
(or relations in general) that are partitions. Some such behaviors include:
* Cannot drop using DROP TABLE without first "detaching" from its parent
* Cannot add, drop new columns
* Cannot rename, change type of existing columns
It also becomes now possible to check, from the FOR VALUES specification, that
the new partition (in CREATE TABLE PARTITION OF parent or ALTER TABLE parent
ATTACH PARTITION) does not overlap with any existing partition of parent.
A partition's check expression (that is, an expression tree that can be used
a check constraint for the rows in it) can be obtained with
RelationGetPartitionCheckExpr(). ExecConstraints() now checks using that
that expression, whether a row being inserted or new row value in update case
violate the partition boundary specification. Also, constraint exclusion
based partition-pruning is done using them.
When attaching a partition, rows in the table are checked whether any violates
the partition bound specification (AND'd with the parent's check expression,
if parent itself is a partition), unless NO VALIDATE clause is specified with
the command.
This commit also invents an internal representation of partition bounds in
the form of structs PartitionListBound and PartitionRangeBound, which consist
of datums and information like number of values in a list, booleans for
whether a range bound is infinite, inclusive (vs. exclusive), lower bound
(vs. upper bound), etc. "Partition descriptor" of a partitioned table
(struct PartitionDesc) consists of an array of oids of partitions, array
of pointers to PartitionListBound structs (NULL if not list partitioned,
and 2 arrays of pointers to PartitionRangeBound structs, (each NULL,
if not range partitioned).
One more data structure that this commit invents for future use is struct
PartitionDescNode. It represents a node in partition tree where each node
of the tree corresponds to a partitioned table in the tree. The tree
structure is such that a parent node links to the first child node (which
corresponds with the first partition of the parent that is partitioned
itself), which in turn, links to its right sibling (the next partition
that is partitioned) and so on. Each node consists of some book-keeping
information including a pointer to PartitionDesc of the corresponding table
and recursive pointers just mentioned viz. downlink and next.
---
doc/src/sgml/catalogs.sgml | 56 ++
src/backend/catalog/Makefile | 2 +-
src/backend/catalog/heap.c | 6 +
src/backend/catalog/partition.c | 1287 ++++++++++++++++++++++++++++
src/backend/commands/copy.c | 2 +-
src/backend/commands/lockcmds.c | 4 +-
src/backend/commands/tablecmds.c | 210 +++++-
src/backend/executor/execMain.c | 75 ++-
src/backend/executor/nodeModifyTable.c | 4 +-
src/backend/nodes/readfuncs.c | 33 +
src/backend/optimizer/util/plancat.c | 13 +
src/backend/parser/analyze.c | 1 +
src/backend/parser/parse_clause.c | 4 +-
src/backend/parser/parse_relation.c | 3 +-
src/backend/parser/parse_utilcmd.c | 6 +
src/backend/utils/adt/ruleutils.c | 78 ++
src/backend/utils/cache/relcache.c | 284 ++++++
src/backend/utils/cache/syscache.c | 12 +
src/bin/pg_dump/common.c | 86 ++
src/bin/pg_dump/pg_dump.c | 98 ++-
src/bin/pg_dump/pg_dump.h | 12 +
src/bin/psql/describe.c | 46 +-
src/include/catalog/indexing.h | 3 +
src/include/catalog/partition.h | 85 ++
src/include/catalog/pg_partition.h | 54 ++
src/include/nodes/execnodes.h | 4 +
src/include/utils/rel.h | 10 +
src/include/utils/syscache.h | 1 +
src/test/regress/expected/sanity_check.out | 1 +
29 files changed, 2444 insertions(+), 36 deletions(-)
create mode 100644 src/include/catalog/pg_partition.h
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 652f779..9e7c1d9 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -224,6 +224,11 @@
</row>
<row>
+ <entry><link linkend="catalog-pg-partition"><structname>pg_partition</structname></link></entry>
+ <entry>information about partitions</entry>
+ </row>
+
+ <row>
<entry><link linkend="catalog-pg-partitioned"><structname>pg_partitioned</structname></link></entry>
<entry>information about partitioned tables, including the partition key</entry>
</row>
@@ -4677,6 +4682,57 @@
</sect1>
+ <sect1 id="catalog-pg-partition">
+ <title><structname>pg_partition</structname></title>
+
+ <indexterm zone="catalog-pg-partition">
+ <primary>pg_partition</primary>
+ </indexterm>
+
+ <para>
+ The catalog <structname>pg_partition</structname> stores partition bounds
+ of tables that are partitions.
+ </para>
+
+ <table>
+ <title><structname>pg_partition</> Columns</title>
+
+ <tgroup cols="4">
+ <thead>
+ <row>
+ <entry>Name</entry>
+ <entry>Type</entry>
+ <entry>References</entry>
+ <entry>Description</entry>
+ </row>
+ </thead>
+
+ <tbody>
+
+ <row>
+ <entry><structfield>partrelid</structfield></entry>
+ <entry><type>oid</type></entry>
+ <entry><literal><link linkend="catalog-pg-class"><structname>pg_class</structname></link>.oid</literal></entry>
+ <entry>The OID of the <structname>pg_class</> entry for this partition</entry>
+ </row>
+
+ <row>
+ <entry><structfield>partbound</structfield></entry>
+ <entry><type>pg_node_tree</type></entry>
+ <entry></entry>
+ <entry>
+ Expression tree (in <function>nodeToString()</function> representation)
+ for partition boundary value. For list partitions, it is simply a list
+ of values. For range partitions, it consists of lower and upper bound
+ values along with their inclusivity.
+ </entry>
+ </row>
+
+ </tbody>
+ </tgroup>
+ </table>
+ </sect1>
+
<sect1 id="catalog-pg-partitioned">
<title><structname>pg_partitioned</structname></title>
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index 4dc6300..ee1be31 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -41,7 +41,7 @@ POSTGRES_BKI_SRCS = $(addprefix $(top_srcdir)/src/include/catalog/,\
pg_foreign_data_wrapper.h pg_foreign_server.h pg_user_mapping.h \
pg_foreign_table.h pg_policy.h pg_replication_origin.h \
pg_default_acl.h pg_init_privs.h pg_seclabel.h pg_shseclabel.h \
- pg_collation.h pg_range.h pg_transform.h pg_partitioned.h\
+ pg_collation.h pg_range.h pg_transform.h pg_partitioned.h pg_partition.h\
toasting.h indexing.h \
)
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 932a48b..2e99661 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -1808,7 +1808,13 @@ heap_drop_with_catalog(Oid relid)
* If a partitioned table, delete the pg_partitioned tuples.
*/
if (rel->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+ {
RemovePartitionKeyByRelId(relid);
+ RelationDropPartitions(relid);
+ }
+
+ if (relid_is_partition(relid))
+ RemovePartitionEntryByRelId(relid);
/*
* Schedule unlinking of the relation's physical files at commit.
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index c6c8dad..eb27937 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -18,19 +18,26 @@
#include "access/heapam.h"
#include "access/htup_details.h"
#include "access/nbtree.h"
+#include "access/sysattr.h"
#include "catalog/dependency.h"
#include "catalog/indexing.h"
#include "catalog/objectaddress.h"
#include "catalog/partition.h"
#include "catalog/pg_collation.h"
+#include "catalog/pg_inherits.h"
+#include "catalog/pg_inherits_fn.h"
#include "catalog/pg_opclass.h"
#include "catalog/pg_partitioned.h"
+#include "catalog/pg_partition.h"
#include "catalog/pg_type.h"
#include "executor/executor.h"
#include "miscadmin.h"
+#include "nodes/makefuncs.h"
#include "nodes/nodeFuncs.h"
+#include "nodes/parsenodes.h"
#include "optimizer/clauses.h"
#include "optimizer/planmain.h"
+#include "optimizer/var.h"
#include "storage/lmgr.h"
#include "utils/array.h"
#include "utils/builtins.h"
@@ -39,12 +46,55 @@
#include "utils/fmgroids.h"
#include "utils/inval.h"
#include "utils/lsyscache.h"
+#include "utils/rel.h"
#include "utils/syscache.h"
+typedef struct translate_var_attno_mutator_context
+{
+ AttrNumber old_attno;
+ AttrNumber new_attno;
+} translate_var_attno_mutator_context;
+
static PartitionKey CopyPartitionKey(PartitionKey fromkey);
static KeyTypeCollInfo *copy_key_type_coll_info(int nkeycols,
KeyTypeCollInfo *tcinfo);
static void free_key_type_coll_info(KeyTypeCollInfo *tcinfo);
+static bool list_overlaps_existing_partition(PartitionKey key,
+ PartitionListSpec *list_spec,
+ PartitionDesc pdesc,
+ Oid *with);
+static bool partition_range_empty(PartitionKey key,
+ PartitionRangeSpec *range_spec);
+static int32 compare_range_keys(PartitionKey key, Datum *val1, Datum *val2);
+static bool range_overlaps_existing_partition(PartitionKey key,
+ PartitionRangeSpec *range_spec,
+ PartitionDesc pdesc,
+ Oid *with);
+static PartitionListBound *make_list_from_spec(PartitionKey key, List *values);
+static PartitionRangeBound *make_range_from_spec(PartitionKey key, List *val,
+ bool inclusive, bool lower);
+static bool partition_range_overlaps(PartitionKey key,
+ PartitionRangeBound *part1_lower,
+ PartitionRangeBound *part1_upper,
+ PartitionRangeBound *part2_lower,
+ PartitionRangeBound *part2_upper);
+
+static List *get_partitions(Oid relid, int lockmode);
+
+static Expr *get_check_expr_for_list(PartitionKey key, List *values);
+static Expr *get_check_expr_for_range(PartitionKey key,
+ List *lowervalues,
+ bool lowerinc,
+ List *uppervalues,
+ bool upperinc);
+static Node *translate_var_attno(Node *expr, AttrNumber attno,
+ AttrNumber new_attno);
+static Node *translate_var_attno_mutator(Node *node,
+ translate_var_attno_mutator_context *cxt);
+static Node *generate_partition_check_expr(Relation rel);
+
+static PartitionDescNode GetPartitionDescNodeRecurse(Relation rel, int offset);
+static int get_leaf_partition_count(PartitionDescNode pdnode);
/*
* StorePartitionKey
@@ -448,3 +498,1240 @@ FreePartitionKey(PartitionKey key)
free_key_type_coll_info(key->tcinfo);
pfree(key);
}
+
+/*
+ * StorePartitionBound
+ *
+ * Store partition boundary info in pg_partition after checking for its
+ * validity.
+ */
+void
+StorePartitionBound(Oid relid, Oid parentId, Node *bound)
+{
+ Relation pg_partition;
+ Relation parent;
+ PartitionKey key;
+ PartitionDesc pdesc;
+ Oid with;
+ HeapTuple tuple;
+ char *boundString;
+ Datum values[Natts_pg_partition];
+ bool nulls[Natts_pg_partition];
+ ParseState *pstate = make_parsestate(NULL);
+
+ parent = heap_open(parentId, AccessShareLock);
+ key = RelationGetPartitionKey(parent);
+ pdesc = RelationGetPartitionDesc(parent);
+
+ /*
+ * Check if 'bound' happens to overlap with any of parent's existing
+ * partitions.
+ */
+ switch (key->strategy)
+ {
+ case PARTITION_STRAT_LIST:
+ {
+ PartitionListSpec *list;
+
+ Assert(IsA(bound, PartitionListSpec));
+ list = (PartitionListSpec *) bound;
+ if (list_overlaps_existing_partition(key, list, pdesc, &with))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("new partition's list of values overlaps with that"
+ " of partition \"%s\" of \"%s\"",
+ get_rel_name(with), get_rel_name(parentId)),
+ errhint("Please specify a list of values that does not"
+ " overlap with any existing list partition's list."),
+ parser_errposition(pstate, list->location)));
+ break;
+ }
+
+ case PARTITION_STRAT_RANGE:
+ {
+ PartitionRangeSpec *range;
+
+ Assert(IsA(bound, PartitionRangeSpec));
+ range = (PartitionRangeSpec *) bound;
+ if (partition_range_empty(key, range))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("new partition's range contains no element"),
+ errhint("Please specify a non-empty range."),
+ parser_errposition(pstate, range->location)));
+
+ if (range_overlaps_existing_partition(key, range, pdesc, &with))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("new partition's range overlaps with that of"
+ " partition \"%s\" of \"%s\"",
+ get_rel_name(with), get_rel_name(parentId)),
+ errhint("Please specify a range that does not overlap"
+ " with any existing partition's range."),
+ parser_errposition(pstate, range->location)));
+ break;
+ }
+ }
+
+ pg_partition = heap_open(PartitionRelationId, RowExclusiveLock);
+
+ boundString = nodeToString(bound);
+
+ /* Build the tuple - bound can never be null */
+ MemSet(nulls, false, sizeof(nulls));
+
+ values[Anum_pg_partition_partrelid - 1] = ObjectIdGetDatum(relid);
+ values[Anum_pg_partition_partbound - 1] = CStringGetTextDatum(boundString);
+
+ tuple = heap_form_tuple(RelationGetDescr(pg_partition), values, nulls);
+
+ simple_heap_insert(pg_partition, tuple);
+
+ /* Update the indexes on pg_partition */
+ CatalogUpdateIndexes(pg_partition, tuple);
+
+ /*
+ * Invalid the parent's relcache so that the partition is now included in
+ * its partition descriptor.
+ */
+ CacheInvalidateRelcache(parent);
+
+ heap_close(parent, AccessShareLock);
+ heap_close(pg_partition, RowExclusiveLock);
+}
+
+/*
+ * Does new list partition 'bound' overlap any of existing list partitions?
+ */
+static bool
+list_overlaps_existing_partition(PartitionKey key,
+ PartitionListSpec *list_spec,
+ PartitionDesc pdesc,
+ Oid *with)
+{
+ PartitionListBound *new_list;
+ int i;
+
+ if (pdesc->nparts == 0)
+ return false;
+
+ new_list = make_list_from_spec(key, list_spec->values);
+
+ for (i = 0; i < pdesc->nparts; i++)
+ {
+ int j;
+
+ if (pdesc->lists[i]->contains_null && new_list->contains_null)
+ return true;
+
+ for (j = 0; j < pdesc->lists[i]->nvalues; j++)
+ {
+ int k;
+
+ for (k = 0; k < new_list->nvalues; k++)
+ {
+ int32 result;
+
+ result = DatumGetInt32(FunctionCall2Coll(&key->partsupfunc[0],
+ key->tcinfo->typcoll[0],
+ pdesc->lists[i]->values[j],
+ new_list->values[k]));
+ if (!result)
+ {
+ *with = pdesc->oids[i];
+ return true;
+ }
+ }
+ }
+ }
+
+ return false;
+}
+
+/*
+ * Is new partition's range empty?
+ */
+static bool
+partition_range_empty(PartitionKey key, PartitionRangeSpec *range_spec)
+{
+ PartitionRangeBound *lower,
+ *upper;
+
+ lower = make_range_from_spec(key, range_spec->lower, range_spec->lowerinc,
+ true);
+ upper = make_range_from_spec(key, range_spec->upper, range_spec->upperinc,
+ false);
+
+ /*
+ * Only either lower or upper is allowed to be unbounded, which if
+ * one of them is, the range is not empty.
+ */
+ if (lower->infinite || upper->infinite)
+ return false;
+
+ /*
+ * If upper > lower, ouright empty or if lower = upper and either of
+ * them is exclusive.
+ */
+ if (compare_range_keys(key, upper->val, lower->val) < 0 ||
+ (compare_range_keys(key, lower->val, upper->val) == 0 &&
+ (!lower->inclusive || !upper->inclusive)))
+ return true;
+
+ return false;
+}
+
+/* Compare range composite keys using logic similar to RowCompareExpr */
+static int32
+compare_range_keys(PartitionKey key, Datum *val1, Datum *val2)
+{
+ int32 result;
+ int i;
+
+ for (i = 0; i < key->partnatts; i++)
+ {
+ result = DatumGetInt32(FunctionCall2Coll(&key->partsupfunc[i],
+ key->tcinfo->typcoll[i],
+ val1[i], val2[i]));
+ if (!result)
+ continue;
+ break;
+ }
+
+ return result;
+}
+
+/*
+ * Does new range partition's range overlap any of existing range partitions?
+ */
+static bool
+range_overlaps_existing_partition(PartitionKey key,
+ PartitionRangeSpec *range_spec,
+ PartitionDesc pdesc,
+ Oid *with)
+{
+ int i;
+ PartitionRangeBound *new_lower,
+ *new_upper;
+
+ if (pdesc->nparts == 0)
+ return false;
+
+ /*
+ * Create internal representation of rangelower and rangeupper from
+ * range_spec.
+ */
+ new_lower = make_range_from_spec(key, range_spec->lower, range_spec->lowerinc,
+ true);
+ new_upper = make_range_from_spec(key, range_spec->upper, range_spec->upperinc,
+ false);
+
+ /* Check with existing partitions for overlap */
+ for (i = 0; i < pdesc->nparts; i++)
+ {
+ if (partition_range_overlaps(key, new_lower, new_upper,
+ pdesc->rangelowers[i],
+ pdesc->rangeuppers[i]))
+ {
+ *with = pdesc->oids[i];
+ return true;
+ }
+ }
+
+ return false;
+}
+
+/*
+ * Make a PartitionListBound from list of values using the information in key
+ */
+static PartitionListBound *
+make_list_from_spec(PartitionKey key, List *values)
+{
+ PartitionListBound *list;
+ ListCell *cell;
+ int i;
+
+ list = (PartitionListBound *) palloc0(sizeof(PartitionListBound));
+ list->nvalues = list_length(values);
+ list->values = (Datum *) palloc(list->nvalues * sizeof(Datum));
+
+ i = 0;
+ foreach (cell, values)
+ {
+ Const *val = lfirst(cell);
+
+ if (val->constisnull)
+ list->contains_null = true;
+ else
+ list->values[i] = datumCopy(val->constvalue,
+ key->tcinfo->typbyval[0],
+ key->tcinfo->typlen[0]);
+ i++;
+ }
+
+ return list;
+}
+
+/*
+ * Make a PartitionRangeBound from arguments using the information in key
+ */
+static PartitionRangeBound *
+make_range_from_spec(PartitionKey key, List *val, bool inclusive, bool lower)
+{
+ PartitionRangeBound *range;
+ ListCell *cell;
+
+ range = (PartitionRangeBound *) palloc0(sizeof(PartitionRangeBound));
+ range->infinite = (val == NIL);
+ range->inclusive = inclusive;
+ range->lower = lower;
+
+ if (val)
+ {
+ int i;
+
+ range->val = (Datum *) palloc0(key->partnatts * sizeof(Datum));
+
+ i = 0;
+ foreach (cell, val)
+ {
+ Const *val = lfirst(cell);
+
+ if (val->constisnull)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("cannot specify NULL in range bound")));
+ else
+ range->val[i] = datumCopy(val->constvalue,
+ key->tcinfo->typbyval[i],
+ key->tcinfo->typlen[i]);
+ i++;
+ }
+ }
+
+ return range;
+}
+
+/* Check two range partitions for overlap */
+static bool
+partition_range_overlaps(PartitionKey key,
+ PartitionRangeBound *part1_lower,
+ PartitionRangeBound *part1_upper,
+ PartitionRangeBound *part2_lower,
+ PartitionRangeBound *part2_upper)
+{
+ if (partition_range_cmp_bounds(key, part1_lower, part2_lower) >= 0 &&
+ partition_range_cmp_bounds(key, part1_lower, part2_upper) <= 0)
+ return true;
+
+ if (partition_range_cmp_bounds(key, part2_lower, part1_lower) >= 0 &&
+ partition_range_cmp_bounds(key, part2_lower, part1_upper) <= 0)
+ return true;
+
+ return false;
+}
+
+/* Returns for two range partition bounds whether, b1 <=, =, >= b2 */
+int32
+partition_range_cmp_bounds(PartitionKey key,
+ PartitionRangeBound *b1,
+ PartitionRangeBound *b2)
+{
+ int32 result;
+
+ /*
+ * First, handle cases involving infinity, which don't require invoking
+ * the comparison proc.
+ */
+ if (b1->infinite && b2->infinite)
+ {
+ /*
+ * Both are infinity, so they are equal unless one is lower and the
+ * other not.
+ */
+ if (b1->lower == b2->lower)
+ return 0;
+ else
+ return b1->lower ? -1 : 1;
+ }
+ else if (b1->infinite)
+ return b1->lower ? -1 : 1;
+ else if (b2->infinite)
+ return b2->lower ? 1 : -1;
+
+ /*
+ * Both boundaries are finite, so compare the held values.
+ */
+ result = compare_range_keys(key, b1->val, b2->val);
+
+ /*
+ * If the comparison is anything other than equal, we're done. If they
+ * compare equal though, we still have to consider whether the boundaries
+ * are inclusive or exclusive.
+ */
+ if (result == 0)
+ {
+ if (!b1->inclusive && !b2->inclusive)
+ {
+ /* both are exclusive */
+ if (b1->lower == b2->lower)
+ return 0;
+ else
+ return b1->lower ? 1 : -1;
+ }
+ else if (!b1->inclusive)
+ return b1->lower ? 1 : -1;
+ else if (!b2->inclusive)
+ return b2->lower ? -1 : 1;
+ else
+ {
+ /*
+ * Both are inclusive and the values held are equal, so they are
+ * equal regardless of whether they are upper or lower boundaries,
+ * or a mix.
+ */
+ return 0;
+ }
+ }
+
+ return result;
+}
+
+/*
+ * RemovePartitionByRelId
+ * Remove a pg_partition entry for a partition.
+ */
+void
+RemovePartitionEntryByRelId(Oid relid)
+{
+ Relation rel;
+ HeapTuple tuple;
+
+ rel = heap_open(PartitionRelationId, RowExclusiveLock);
+ tuple = SearchSysCache1(PARTRELID, ObjectIdGetDatum(relid));
+
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "cache lookup failed for partition entry of relation %u",
+ relid);
+
+ simple_heap_delete(rel, &tuple->t_self);
+
+ /* Update the indexes on pg_partitioned_rel */
+ CatalogUpdateIndexes(rel, tuple);
+
+ ReleaseSysCache(tuple);
+ heap_close(rel, RowExclusiveLock);
+}
+
+/*
+ * does relid have pg_partition entry?
+ */
+bool
+relid_is_partition(Oid relid)
+{
+ return SearchSysCacheExists1(PARTRELID, ObjectIdGetDatum(relid));
+}
+
+/*
+ * Returns inheritance parent of relid by scanning pg_inherits
+ */
+Oid
+get_partition_parent(Oid relid)
+{
+ Form_pg_inherits form;
+ Relation catalogRelation;
+ SysScanDesc scan;
+ ScanKeyData key[2];
+ HeapTuple tuple;
+
+ catalogRelation = heap_open(InheritsRelationId, AccessShareLock);
+
+ ScanKeyInit(&key[0],
+ Anum_pg_inherits_inhrelid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(relid));
+ ScanKeyInit(&key[1],
+ Anum_pg_inherits_inhseqno,
+ BTEqualStrategyNumber, F_INT4EQ,
+ Int32GetDatum(1));
+
+ scan = systable_beginscan(catalogRelation, InheritsRelidSeqnoIndexId, true,
+ NULL, 2, key);
+
+ tuple = systable_getnext(scan);
+ Assert(HeapTupleIsValid(tuple));
+
+ form = (Form_pg_inherits) GETSTRUCT(tuple);
+
+ systable_endscan(scan);
+ heap_close(catalogRelation, AccessShareLock);
+
+ return form->inhparent;
+}
+
+/*
+ * RelationDropPartitions
+ * Find and drop all partitions of relation with OID 'relid'
+ */
+void
+RelationDropPartitions(Oid relid)
+{
+ List *partitionOids;
+ ListCell *cell;
+
+ partitionOids = get_partitions(relid, AccessExclusiveLock);
+
+ foreach (cell, partitionOids)
+ {
+ Oid partitionOid = lfirst_oid(cell);
+ ObjectAddress obj;
+
+ obj.classId = RelationRelationId;
+ obj.objectId = partitionOid;
+ obj.objectSubId = 0;
+
+ performDeletion(&obj, DROP_CASCADE, 0);
+ }
+}
+
+/*
+ * get_partition_info
+ * Return information of partitions of rel
+ *
+ * Returns number of partitions in *nparts.
+ */
+PartitionInfo **
+get_partition_info(Relation rel, int *nparts)
+{
+ int i;
+ Relation partrel;
+ List *partoids;
+ ListCell *cell;
+ PartitionInfo **parts;
+ PartitionKey key = RelationGetPartitionKey(rel);
+
+ partoids = get_partitions(RelationGetRelid(rel), NoLock);
+
+ /* no partitions found */
+ if ((*nparts = list_length(partoids)) == 0)
+ return NULL;
+
+ partrel = heap_open(PartitionRelationId, AccessShareLock);
+
+ parts = (PartitionInfo **) palloc0(*nparts * sizeof(PartitionInfo *));
+
+ i = 0;
+ foreach(cell, partoids)
+ {
+ Oid partrelid = lfirst_oid(cell);
+ HeapTuple tuple;
+ Datum datum;
+ bool isnull;
+ Node *bound;
+
+ tuple = SearchSysCache1(PARTRELID, partrelid);
+ Assert(HeapTupleIsValid(tuple));
+
+ parts[i] = (PartitionInfo *) palloc0(sizeof(PartitionInfo));
+ parts[i]->oid = partrelid;
+
+ datum = fastgetattr(tuple,
+ Anum_pg_partition_partbound,
+ partrel->rd_att,
+ &isnull);
+
+ bound = stringToNode(TextDatumGetCString(datum));
+
+ switch (key->strategy)
+ {
+ case PARTITION_STRAT_LIST:
+ {
+ PartitionListSpec *list_spec;
+
+ Assert(IsA(bound, PartitionListSpec));
+ list_spec = (PartitionListSpec *) bound;
+ parts[i]->list = make_list_from_spec(key, list_spec->values);
+ }
+ break;
+
+ case PARTITION_STRAT_RANGE:
+ {
+ PartitionRangeSpec *range_spec;
+
+ Assert(IsA(bound, PartitionRangeSpec));
+ range_spec = (PartitionRangeSpec *) bound;
+
+ parts[i]->rangelower = make_range_from_spec(key,
+ range_spec->lower,
+ range_spec->lowerinc,
+ true);
+ parts[i]->rangeupper = make_range_from_spec(key,
+ range_spec->upper,
+ range_spec->upperinc,
+ false);
+ }
+ break;
+ }
+
+ ReleaseSysCache(tuple);
+ i++;
+ }
+
+ heap_close(partrel, AccessShareLock);
+
+ return parts;
+}
+
+/*
+ * Wrapper around find_inheritance_children for use by partitioning
+ * related code
+ */
+static List *
+get_partitions(Oid relid, int lockmode)
+{
+ return find_inheritance_children(relid, lockmode);
+}
+
+/*
+ * Helper routine to deep-free a PartitionInfo array
+ */
+void
+free_partition_info(PartitionInfo **p, int num)
+{
+ int i;
+
+ for (i = 0; i < num; i++)
+ {
+ if (p[i]->list)
+ {
+ pfree(p[i]->list->values);
+ pfree(p[i]->list);
+ }
+
+ if (p[i]->rangelower)
+ {
+ if (p[i]->rangelower->val)
+ pfree(p[i]->rangelower->val);
+ pfree(p[i]->rangelower);
+ }
+
+ if (p[i]->rangeupper)
+ {
+ if (p[i]->rangeupper->val)
+ pfree(p[i]->rangeupper->val);
+ pfree(p[i]->rangeupper);
+ }
+
+ pfree(p[i]);
+ }
+
+ pfree(p);
+}
+
+/*
+ * get_check_expr_from_partbound - for direct use by tablecmds.c
+ */
+Expr *
+get_check_expr_from_partbound(Relation rel, Relation parent, Node *bound)
+{
+ PartitionKey key = RelationGetPartitionKey(parent);
+ Expr *my_expr;
+ int i;
+ ListCell *partexprs_item;
+
+ if (IsA(bound, PartitionListSpec))
+ {
+ PartitionListSpec *list_spec = (PartitionListSpec *) bound;
+
+ my_expr = get_check_expr_for_list(key, list_spec->values);
+ }
+ else if (IsA(bound, PartitionRangeSpec))
+ {
+ PartitionRangeSpec *range_spec = (PartitionRangeSpec *) bound;
+
+ my_expr = get_check_expr_for_range(key,
+ range_spec->lower,
+ range_spec->lowerinc,
+ range_spec->upper,
+ range_spec->upperinc);
+ }
+
+ /*
+ * Translate vars in the generated expression with the correct attnos.
+ * Note that the vars in my_expr bear attnos dictated by key which carries
+ * attnos of the parent.
+ */
+ partexprs_item = list_head(key->partexprs);
+ for (i = 0; i < key->partnatts; i++)
+ {
+ AttrNumber attno = key->partattrs[i],
+ new_attno;
+ char *attname;
+
+ if (attno != 0)
+ {
+ attname = get_attname(RelationGetRelid(parent), attno);
+ new_attno = get_attnum(RelationGetRelid(rel), attname);
+
+ my_expr = (Expr *) translate_var_attno((Node *) my_expr,
+ attno,
+ new_attno);
+ }
+ else
+ {
+ Node *expr = (Node *) lfirst(partexprs_item);
+ Bitmapset *expr_attrs = NULL;
+ int index;
+
+ pull_varattnos(expr, 1, &expr_attrs);
+ partexprs_item = lnext(partexprs_item);
+
+ index = -1;
+ while ((index = bms_next_member(expr_attrs, index)) > 0)
+ {
+ AttrNumber attno = index + FirstLowInvalidHeapAttributeNumber;
+ attname = get_attname(RelationGetRelid(parent), attno);
+ new_attno = get_attnum(RelationGetRelid(rel), attname);
+ my_expr = (Expr *) translate_var_attno((Node *) my_expr,
+ attno,
+ new_attno);
+ }
+ }
+ }
+
+ return my_expr;
+}
+
+/*
+ * get_check_expr_for_list
+ */
+static Expr *
+get_check_expr_for_list(PartitionKey key, List *values)
+{
+ ArrayExpr *values_arr = makeNode(ArrayExpr);
+ ScalarArrayOpExpr *arrayop_expr;
+ Node *key_col;
+ Oid operoid;
+ StrategyNumber strategy = BTEqualStrategyNumber;
+
+ /* Btree equality operator of specified opfamily */
+ operoid = get_opfamily_member(key->partopfamily[0],
+ key->tcinfo->typid[0],
+ key->tcinfo->typid[0],
+ strategy);
+
+ /* lexpr */
+ if (key->partattrs[0] != 0)
+ key_col = (Node *) makeVar(1, key->partattrs[0],
+ key->tcinfo->typid[0],
+ key->tcinfo->typmod[0],
+ key->tcinfo->typcoll[0],
+ 0);
+ else
+ key_col = (Node *) linitial(key->partexprs);
+
+ /* rexpr */
+ values_arr->array_typeid = get_array_type(key->tcinfo->typid[0]);
+ values_arr->array_collid = key->tcinfo->typcoll[0];
+ values_arr->element_typeid = key->tcinfo->typid[0];
+ values_arr->elements = values;
+ values_arr->multidims = false;
+ values_arr->location = -1;
+
+ /* build the actual predicate expression node */
+ arrayop_expr = makeNode(ScalarArrayOpExpr);
+ arrayop_expr->opno = operoid;
+ arrayop_expr->opfuncid = get_opcode(operoid);
+ arrayop_expr->useOr = true;
+ arrayop_expr->inputcollid = key->tcinfo->typcoll[0];
+ arrayop_expr->args = list_make2(key_col, values_arr);
+ arrayop_expr->location = -1;
+
+ return (Expr *) arrayop_expr;
+}
+
+/*
+ * get_check_expr_for_range
+ */
+static Expr *
+get_check_expr_for_range(PartitionKey key,
+ List *lowervalues,
+ bool lowerinc,
+ List *uppervalues,
+ bool upperinc)
+{
+ List *and_args = NIL;
+ ListCell *cell1,
+ *cell2,
+ *partexprs_item;
+ int i;
+ uint16 strategy;
+
+ /*
+ * Handle the case with only one bound.
+ *
+ * In this case, consider only the first column of the key and generate
+ * appropriate opclause since only the comparison with the first column
+ * would have determined whether a rows went into such a partition.
+ * It's always true that -INF < someval and someval < +INF.
+ */
+ if (lowervalues == NIL || uppervalues == NIL)
+ {
+ List *values;
+ Const *key_val;
+ Node *key_col;
+ Oid operoid;
+ bool islower,
+ inclusive;
+
+ if (lowervalues != NIL)
+ {
+ values = lowervalues;
+ islower = true;
+ inclusive = lowerinc;
+ }
+ else
+ {
+ values = uppervalues;
+ islower = false;
+ inclusive = upperinc;
+ }
+
+ /* lexpr */
+ if (key->partattrs[0] != 0)
+ key_col = (Node *) makeVar(1, key->partattrs[0],
+ key->tcinfo->typid[0],
+ key->tcinfo->typmod[0],
+ key->tcinfo->typcoll[0],
+ 0);
+ else
+ key_col = (Node *) linitial(key->partexprs);
+
+ /* rexpr */
+ key_val = linitial(values);
+
+ if (islower)
+ strategy = inclusive ? BTGreaterEqualStrategyNumber : BTGreaterStrategyNumber;
+ else
+ strategy = inclusive ? BTLessEqualStrategyNumber : BTLessStrategyNumber;
+
+ operoid = get_opfamily_member(key->partopfamily[0],
+ key->tcinfo->typid[0],
+ key->tcinfo->typid[0],
+ strategy);
+
+ and_args = lappend(and_args,
+ make_opclause(operoid, BOOLOID,
+ false,
+ (Expr *) key_col,
+ (Expr *) key_val,
+ InvalidOid,
+ key->tcinfo->typcoll[0]));
+
+ return makeBoolExpr(AND_EXPR, and_args, -1);
+ }
+
+ /* Both bounds constrain values going into the partition. */
+ i = 0;
+ partexprs_item = list_head(key->partexprs);
+ forboth (cell1, lowervalues, cell2, uppervalues)
+ {
+ Node *key_col;
+ Const *lower_val = lfirst(cell1);
+ Const *upper_val = lfirst(cell2);
+ Expr *lower_clause,
+ *upper_clause;
+ Oid eqop,
+ lowerop,
+ upperop;
+ EState *estate;
+ MemoryContext oldcxt;
+ Expr *test_expr;
+ ExprState *test_exprstate;
+ Datum test_result;
+ bool isNull;
+
+ /* lexpr */
+ if (key->partattrs[i] != 0)
+ {
+ key_col = (Node *) makeVar(1, key->partattrs[i],
+ key->tcinfo->typid[i],
+ key->tcinfo->typmod[i],
+ key->tcinfo->typcoll[i],
+ 0);
+ }
+ else
+ {
+ key_col = (Node *) lfirst(partexprs_item);
+ partexprs_item = lnext(partexprs_item);
+ }
+
+ /*
+ * If lower_val = upper_val (= val, say), create a column = val
+ * constraint.
+ */
+ eqop = get_opfamily_member(key->partopfamily[i],
+ key->tcinfo->typid[i],
+ key->tcinfo->typid[i],
+ BTEqualStrategyNumber);
+
+ estate = CreateExecutorState();
+ oldcxt = MemoryContextSwitchTo(estate->es_query_cxt);
+ test_expr = make_opclause(eqop,
+ BOOLOID,
+ false,
+ (Expr *) lower_val,
+ (Expr *) upper_val,
+ InvalidOid,
+ key->tcinfo->typcoll[i]);
+ fix_opfuncids((Node *) test_expr);
+ test_exprstate = ExecInitExpr(test_expr, NULL);
+ test_result = ExecEvalExprSwitchContext(test_exprstate,
+ GetPerTupleExprContext(estate),
+ &isNull, NULL);
+ MemoryContextSwitchTo(oldcxt);
+ FreeExecutorState(estate);
+
+ if (DatumGetBool(test_result))
+ {
+ and_args = lappend(and_args,
+ make_opclause(eqop,
+ BOOLOID,
+ false,
+ (Expr *) key_col,
+ /* could've been upper_val */
+ (Expr *) lower_val,
+ InvalidOid,
+ key->tcinfo->typcoll[i]));
+
+ /* Go to the next column. */
+ }
+ else
+ {
+ /*
+ * For all columns but last, use inclusive strategy (<= or >=).
+ *
+ * For the last column, the bound's inclusivity determines.
+ */
+ strategy = i < key->partnatts - 1 || lowerinc
+ ? BTGreaterEqualStrategyNumber
+ : BTGreaterStrategyNumber;
+ lowerop = get_opfamily_member(key->partopfamily[i],
+ key->tcinfo->typid[i],
+ key->tcinfo->typid[i],
+ strategy);
+ strategy = i < key->partnatts - 1 || upperinc
+ ? BTLessEqualStrategyNumber
+ : BTLessStrategyNumber;
+ upperop = get_opfamily_member(key->partopfamily[i],
+ key->tcinfo->typid[i],
+ key->tcinfo->typid[i],
+ strategy);
+
+ lower_clause = make_opclause(lowerop,
+ BOOLOID,
+ false,
+ (Expr *) key_col,
+ (Expr *) lower_val,
+ InvalidOid,
+ key->tcinfo->typcoll[i]);
+ upper_clause = make_opclause(upperop,
+ BOOLOID,
+ false,
+ (Expr *) key_col,
+ (Expr *) upper_val,
+ InvalidOid,
+ key->tcinfo->typcoll[i]);
+
+ and_args = lappend(and_args,
+ makeBoolExpr(AND_EXPR,
+ list_make2(lower_clause, upper_clause), -1));
+
+ /* No need to constraint further columns. */
+ break;
+ }
+
+ i++;
+ }
+
+ return makeBoolExpr(AND_EXPR, and_args, -1);
+}
+
+/*
+ * translate_var_attno
+ * Changes Vars with a given attno in the provided expression tree to
+ * Vars with new_attno
+ */
+static Node *
+translate_var_attno(Node *expr, AttrNumber attno, AttrNumber new_attno)
+{
+ translate_var_attno_mutator_context cxt;
+
+ cxt.old_attno = attno;
+ cxt.new_attno = new_attno;
+
+ return expression_tree_mutator(expr, translate_var_attno_mutator, &cxt);
+}
+
+/*
+ * translate_var_attno_mutator
+ */
+static Node *
+translate_var_attno_mutator(Node *node,
+ translate_var_attno_mutator_context *cxt)
+{
+ if (node == NULL)
+ return NULL;
+
+ if (IsA(node, Var) && ((Var *) node)->varattno == cxt->old_attno)
+ {
+ Var *newvar = copyObject(node);
+
+ newvar->varattno = cxt->new_attno;
+
+ return (Node *) newvar;
+ }
+
+ return expression_tree_mutator(node, translate_var_attno_mutator,
+ (void *) cxt);
+}
+
+/*
+ * RelationGetPartitionConstraint
+ * Returns the equivelent CHECK constraint expression for a partition
+ */
+Node *
+RelationGetPartitionCheckExpr(Relation rel)
+{
+ /* Quick exit */
+ if (!relid_is_partition(RelationGetRelid(rel)))
+ return NULL;
+
+ /* Quick copy */
+ if (rel->rd_partcheck)
+ return copyObject(rel->rd_partcheck);
+
+ /* None of those, generate */
+ return generate_partition_check_expr(rel);
+}
+
+/*
+ * generate_partition_constraints
+ * Generate partition check constraint expression rel's pg_partition
+ * entry
+ *
+ * Result expression tree is stored CacheMemoryContext to ensure it survives
+ * as long as the relcache entry. But we should be running in a less long-lived
+ * working context. To avoid leaking cache memory if this routine fails partway
+ * routine fails partway through, we build in working memory and then copy
+ * the completed structure into cache memory.
+ */
+static Node *
+generate_partition_check_expr(Relation rel)
+{
+ HeapTuple tuple;
+ MemoryContext oldcxt;
+ Datum boundDatum;
+ bool isnull;
+ Node *bound;
+ Node *my_expr = NULL,
+ *result = NULL;
+ Oid relid = RelationGetRelid(rel);
+ Relation parent = heap_open(get_partition_parent(relid), NoLock);
+
+ /* quick copy */
+ if (rel->rd_partcheck)
+ {
+ heap_close(parent, NoLock);
+ return copyObject(rel->rd_partcheck);
+ }
+
+ tuple = SearchSysCache1(PARTRELID, relid);
+ Assert(HeapTupleIsValid(tuple));
+ boundDatum = SysCacheGetAttr(PARTRELID, tuple,
+ Anum_pg_partition_partbound,
+ &isnull);
+ Assert(!isnull);
+ bound = stringToNode(TextDatumGetCString(boundDatum));
+
+ my_expr = (Node *) get_check_expr_from_partbound(rel, parent, bound);
+
+ /*
+ * If parent is also a partition, create a AND_EXPR BoolExpr with just
+ * created expression and parent's check expression, as its args.
+ */
+ if (relid_is_partition(RelationGetRelid(parent)))
+ {
+ Node *parent_expr;
+
+ parent_expr = generate_partition_check_expr(parent);
+ result = (Node *) makeBoolExpr(AND_EXPR,
+ list_make2(parent_expr, my_expr), -1);
+ }
+ else
+ result = my_expr;
+
+ oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
+ rel->rd_partcheck = copyObject(result);
+ MemoryContextSwitchTo(oldcxt);
+
+ ReleaseSysCache(tuple);
+ heap_close(parent, NoLock);
+
+ return result;
+}
+
+/*
+ * RelationGetPartitionDescNode
+ * Recursively form partition node tree rooted at rel's node
+ */
+PartitionDescNode
+RelationGetPartitionDescNode(Relation rel)
+{
+ PartitionDescNode root;
+
+ /*
+ * Recurse to build the tree of partition descriptor nodes with
+ * rel's descriptor as the root of the tree.
+ */
+ root = GetPartitionDescNodeRecurse(rel, 0);
+ root->index = 0;
+ root->num_leaf_partitions = get_leaf_partition_count(root);
+
+ return root;
+}
+
+/*
+ * GetPartitionDescNodeRecurse
+ * Workhorse of RelationGetPartitionDescNode
+ *
+ * offset is the index (0-based) of the first leaf node in the tree, as
+ * as determined and passed by the parent or the left sibling.
+ */
+static PartitionDescNode
+GetPartitionDescNodeRecurse(Relation rel, int offset)
+{
+ PartitionDescNode root,
+ prev;
+ PartitionDesc pdesc = RelationGetPartitionDesc(rel);
+ int i;
+
+ /* First build our own node */
+ root = (PartitionDescNode) palloc0(sizeof(PartitionDescNodeData));
+ root->pdesc = pdesc;
+ root->relid = RelationGetRelid(rel);
+ root->offset = offset;
+ root->downlink = NULL;
+ root->next = NULL;
+
+ /*
+ * Recursively add nodes for partitions that are themselves partitioned,
+ * linking ourselves to the first such node via 'downlink'. Each node
+ * thus created (being on the same level) is linked to the right sibling
+ * via 'next'. For each node, determine the offset to pass and once
+ * created, update its index and num_leaf_partitions.
+ */
+ prev = NULL;
+ for (i = 0; i < pdesc->nparts; i++)
+ {
+ Oid relid = pdesc->oids[i];
+ int offset;
+ Relation rel;
+ PartitionDescNode pdnode;
+
+ if (!relid_is_partitioned(relid))
+ continue;
+
+ rel = heap_open(relid, AccessShareLock);
+
+ /* Offset for the new node */
+ if (prev)
+ /*
+ * Count leaves on the left - prev node has the account of those
+ * on its own left and then there may have been some siblings
+ * in-between that were leaves themselves.
+ */
+ offset = prev->offset + prev->num_leaf_partitions +
+ (i - prev->index - 1);
+ else
+ /*
+ * We're the first child - account of leaf nodes comes from the
+ * parent. Left siblings were all leaves themselves.
+ */
+ offset = root->offset + i;
+
+ pdnode = GetPartitionDescNodeRecurse(rel, offset);
+ pdnode->index = i;
+ pdnode->num_leaf_partitions = get_leaf_partition_count(pdnode);
+
+ heap_close(rel, AccessShareLock);
+
+ /* Found our first child; link to it. */
+ if (root->downlink == NULL)
+ root->downlink = pdnode;
+
+ /* Link new node to the left sibling, if any */
+ if (prev)
+ prev->next = pdnode;
+
+ prev = pdnode;
+ }
+
+ return root;
+}
+
+/*
+ * get_leaf_partition_count
+ * Recursively count the number of leaf partitions in the partition
+ * tree rooted at pdnode
+ */
+static int
+get_leaf_partition_count(PartitionDescNode pdnode)
+{
+ int i;
+ int result = 0;
+ PartitionDescNode node = pdnode->downlink;
+
+ for (i = 0; i < pdnode->pdesc->nparts; i++)
+ {
+ /* Indexes 0..(node->index - 1) are of leaf partitions */
+ if (node && i == node->index)
+ {
+ result += get_leaf_partition_count(node);
+ node = node->next;
+ }
+ else
+ result += 1;
+ }
+
+ return result;
+}
+
+/*
+ * get_leaf_partition_count
+ * Recursively compute the list of OIDs of leaf partitions in the
+ * partition tree rooted at pdnode
+ */
+List *
+get_leaf_partition_oids(PartitionDescNode pdnode)
+{
+ int i;
+ List *result = NIL;
+ Oid *myparts = pdnode->pdesc->oids;
+ int n_myparts = pdnode->pdesc->nparts;
+ PartitionDescNode node = pdnode->downlink;
+
+ for (i = 0; i < n_myparts; i++)
+ {
+ /* Indexes 0..(node->index - 1) are leaf partitions */
+ if (node && i == node->index)
+ {
+ result = list_concat(result, get_leaf_partition_oids(node));
+ node = node->next;
+ }
+ else
+ result = lappend_oid(result, myparts[i]);
+ }
+
+ return result;
+}
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 403738d..14e81de 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -2464,7 +2464,7 @@ CopyFrom(CopyState cstate)
if (!skip_tuple)
{
/* Check the constraints of the tuple */
- if (cstate->rel->rd_att->constr)
+ if (cstate->rel->rd_att->constr || resultRelInfo->ri_PartitionCheck)
ExecConstraints(resultRelInfo, slot, estate);
if (useHeapMultiInsert)
diff --git a/src/backend/commands/lockcmds.c b/src/backend/commands/lockcmds.c
index 62671a8..5120c93 100644
--- a/src/backend/commands/lockcmds.c
+++ b/src/backend/commands/lockcmds.c
@@ -16,6 +16,7 @@
#include "access/heapam.h"
#include "catalog/namespace.h"
+#include "catalog/partition.h"
#include "catalog/pg_inherits_fn.h"
#include "commands/lockcmds.h"
#include "miscadmin.h"
@@ -63,7 +64,8 @@ LockTableCommand(LockStmt *lockstmt)
RangeVarCallbackForLockTable,
(void *) &lockstmt->mode);
- if (recurse)
+ /* Force inheritance recursion, if partitioned table. */
+ if (recurse || relid_is_partitioned(reloid))
LockTableRecurse(reloid, lockstmt->mode, lockstmt->nowait);
}
}
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 192cae9..b5a1136 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -121,6 +121,19 @@ static List *on_commits = NIL;
/*
+ * For AT ATTACH PARTITION <table> FOR VALUES <partition_bound_spec>,
+ *
+ * Struct describing partition check expression corresponding to
+ * <partition_bound_spec> to validate for rows in <table> during
+ * Phase 3 scan.
+ */
+typedef struct PartitionCheck
+{
+ Expr *expr;
+ List *state;
+} PartitionCheck;
+
+/*
* State information for ALTER TABLE
*
* The pending-work queue for an ALTER TABLE is a List of AlteredTableInfo
@@ -163,6 +176,8 @@ typedef struct AlteredTableInfo
Oid newTableSpace; /* new tablespace; 0 means no change */
bool chgPersistence; /* T if SET LOGGED/UNLOGGED is used */
char newrelpersistence; /* if above is true */
+ PartitionCheck *partitionCheck; /* partition check expression for
+ * ATTACH PARTITION validation */
/* Objects to rebuild after completing ALTER TYPE operations */
List *changedConstraintOids; /* OIDs of constraints to rebuild */
List *changedConstraintDefs; /* string definitions of same */
@@ -712,6 +727,19 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
StoreCatalogInheritance(relationId, inheritOids);
/*
+ * Process and store partition bound. Do this before
+ * CommandCounterIncrement() below, so that we are not deemed an existing
+ * partition by StorePartitionBound() (which is basically found out by
+ * querying pg_inherits).
+ */
+ if (stmt->partbound)
+ {
+ Oid parentId = linitial_oid(inheritOids);
+
+ StorePartitionBound(relationId, parentId, stmt->partbound);
+ }
+
+ /*
* We must bump the command counter to make the newly-created relation
* tuple visible for opening.
*/
@@ -995,6 +1023,13 @@ RangeVarCallbackForDropRelation(const RangeVar *rel, Oid relOid, Oid oldRelOid,
if (!OidIsValid(relOid))
return;
+ if (relid_is_partition(relOid))
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("\"%s\" is a partition of \"%s\"", rel->relname,
+ get_rel_name(get_partition_parent(relOid))),
+ errhint("Use ALTER TABLE DETACH PARTITION to be able to drop it.")));
+
tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relOid));
if (!HeapTupleIsValid(tuple))
return; /* concurrently dropped, so nothing to do */
@@ -1086,7 +1121,8 @@ ExecuteTruncate(TruncateStmt *stmt)
rels = lappend(rels, rel);
relids = lappend_oid(relids, myrelid);
- if (recurse)
+ /* Force inheritance recursion, if partitioned table. */
+ if (recurse || relid_is_partitioned(myrelid))
{
ListCell *child;
List *children;
@@ -1810,8 +1846,12 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
Node *expr;
bool found_whole_row;
- /* ignore if the constraint is non-inheritable */
- if (check[i].ccnoinherit)
+ /*
+ * ignore if the constraint is non-inheritable and this is not
+ * the partition case (constraints of partitioned tables are
+ * always present in their partitions)
+ */
+ if (check[i].ccnoinherit && !is_partition)
continue;
/* Adjust Vars to match new table's column numbering */
@@ -2246,6 +2286,11 @@ renameatt_check(Oid myrelid, Form_pg_class classform, bool recursing)
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot rename column of typed table")));
+ if (relid_is_partition(myrelid) && !recursing)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot rename column of a partition")));
+
/*
* Renaming the columns of sequences or toast tables doesn't actually
* break anything from the system's point of view, since internal
@@ -2472,11 +2517,13 @@ renameatt(RenameStmt *stmt)
return InvalidObjectAddress;
}
+ /* Force inheritance recursion, if partitioned table. */
attnum =
renameatt_internal(relid,
stmt->subname, /* old att name */
stmt->newname, /* new att name */
- interpretInhOption(stmt->relation->inhOpt), /* recursive? */
+ interpretInhOption(stmt->relation->inhOpt) ||
+ relid_is_partitioned(relid), /* recursive? */
false, /* recursing? */
0, /* expected inhcount */
stmt->behavior);
@@ -2624,11 +2671,13 @@ RenameConstraint(RenameStmt *stmt)
}
}
+ /* Force inheritance recursion, if partitioned table. */
return
rename_constraint_internal(relid, typid,
stmt->subname,
stmt->newname,
- stmt->relation ? interpretInhOption(stmt->relation->inhOpt) : false, /* recursive? */
+ stmt->relation ? interpretInhOption(stmt->relation->inhOpt) ||
+ relid_is_partitioned(relid) : false, /* recursive? */
false, /* recursing? */
0 /* expected inhcount */ );
@@ -2870,8 +2919,11 @@ AlterTable(Oid relid, LOCKMODE lockmode, AlterTableStmt *stmt)
CheckTableNotInUse(rel, "ALTER TABLE");
+ /* Force inheritance recursion, if partitioned table */
ATController(stmt,
- rel, stmt->cmds, interpretInhOption(stmt->relation->inhOpt),
+ rel, stmt->cmds,
+ interpretInhOption(stmt->relation->inhOpt) ||
+ relid_is_partitioned(RelationGetRelid(rel)),
lockmode);
}
@@ -3988,7 +4040,7 @@ ATRewriteTables(AlterTableStmt *parsetree, List **wqueue, LOCKMODE lockmode)
* Test the current data within the table against new constraints
* generated by ALTER TABLE commands, but don't rebuild data.
*/
- if (tab->constraints != NIL || tab->new_notnull)
+ if (tab->constraints != NIL || tab->new_notnull || tab->partitionCheck)
ATRewriteTable(tab, InvalidOid, lockmode);
/*
@@ -4132,6 +4184,21 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
}
}
+ /*
+ * Build expression execution states for partition check expression
+ * as well.
+ */
+ if (tab->partitionCheck)
+ {
+ List *expr;
+
+ needscan = true;
+ Assert(tab->partitionCheck->expr);
+ expr = list_make1(tab->partitionCheck->expr);
+ tab->partitionCheck->state = (List *)
+ ExecPrepareExpr((Expr *) expr, estate);
+ }
+
foreach(l, tab->newvals)
{
NewColumnValue *ex = lfirst(l);
@@ -4321,6 +4388,12 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
}
}
+ if (tab->partitionCheck &&
+ !ExecQual(tab->partitionCheck->state, econtext, true))
+ ereport(ERROR,
+ (errcode(ERRCODE_CHECK_VIOLATION),
+ errmsg("table contains a row violating the partition bound specification")));
+
/* Write the tuple out to the new relation */
if (newrel)
heap_insert(newrel, tuple, mycid, hi_options, bistate);
@@ -4518,7 +4591,8 @@ ATSimpleRecursion(List **wqueue, Relation rel,
*/
if (recurse &&
(rel->rd_rel->relkind == RELKIND_RELATION ||
- rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE))
+ rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE ||
+ rel->rd_rel->relkind == RELKIND_PARTITIONED_REL))
{
Oid relid = RelationGetRelid(rel);
ListCell *child;
@@ -4840,6 +4914,11 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
if (recursing)
ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
+ if (relid_is_partition(RelationGetRelid(rel)) && !recursing)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot add column to a partition")));
+
attrdesc = heap_open(AttributeRelationId, RowExclusiveLock);
/*
@@ -5295,6 +5374,7 @@ ATExecDropNotNull(Relation rel, const char *colName, LOCKMODE lockmode)
List *indexoidlist;
ListCell *indexoidscan;
ObjectAddress address;
+ Oid relid;
/*
* lookup the attribute
@@ -5362,6 +5442,28 @@ ATExecDropNotNull(Relation rel, const char *colName, LOCKMODE lockmode)
list_free(indexoidlist);
/*
+ * If rel is partition, throw error if the attribute is not null in
+ * parent
+ */
+ relid = RelationGetRelid(rel);
+ if (relid_is_partition(relid))
+ {
+ Oid parentId = get_partition_parent(relid);
+ Relation parent = heap_open(parentId, AccessShareLock);
+ TupleDesc tupDesc = RelationGetDescr(parent);
+ AttrNumber parent_attnum;
+
+ parent_attnum = get_attnum(parentId, colName);
+ if (tupDesc->attrs[parent_attnum - 1]->attnotnull)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("column \"%s\" is not null in parent",
+ colName),
+ errhint("Please drop not null in the parent instead")));
+ heap_close(parent, AccessShareLock);
+ }
+
+ /*
* Okay, actually perform the catalog change ... if needed
*/
if (((Form_pg_attribute) GETSTRUCT(tuple))->attnotnull)
@@ -5891,6 +5993,11 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
if (recursing)
ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
+ if (relid_is_partition(RelationGetRelid(rel)) && !recursing)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot drop column from a partition")));
+
/*
* get the number of the attribute
*/
@@ -5936,11 +6043,13 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
if (!is_expr)
ereport(ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
- errmsg("cannot drop column named in partition key")));
+ errmsg("cannot drop column named in%s partition key",
+ recursing ? " downstream" : "")));
else
ereport(ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
- errmsg("cannot drop column referenced in partition key expression")));
+ errmsg("cannot drop column referenced in%s partition key expression",
+ recursing ? " downstream" : "")));
}
ReleaseSysCache(tuple);
@@ -6374,8 +6483,9 @@ ATAddCheckConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
/*
* If adding a NO INHERIT constraint, no need to find our children.
+ * However, that applies only to regular inheritance parents.
*/
- if (constr->is_no_inherit)
+ if (constr->is_no_inherit && !relid_is_partitioned(RelationGetRelid(rel)))
return address;
/*
@@ -7936,9 +8046,12 @@ ATExecDropConstraint(Relation rel, const char *constrName,
/*
* Propagate to children as appropriate. Unlike most other ALTER
* routines, we have to do this one level of recursion at a time; we can't
- * use find_all_inheritors to do it in one pass.
+ * use find_all_inheritors to do it in one pass. Note that if the parent
+ * is a partitioned table, we propagate to children (partitions) despite
+ * is_no_inherit_constraint.
*/
- if (!is_no_inherit_constraint)
+ if (!is_no_inherit_constraint ||
+ relid_is_partitioned(RelationGetRelid(rel)))
children = find_inheritance_children(RelationGetRelid(rel), lockmode);
else
children = NIL;
@@ -8068,6 +8181,11 @@ ATPrepAlterColumnType(List **wqueue,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot alter column type of typed table")));
+ if (relid_is_partition(RelationGetRelid(rel)) && !recursing)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot alter column type of a partition")));
+
/* lookup the attribute so we can check inheritance status */
tuple = SearchSysCacheAttName(RelationGetRelid(rel), colName);
if (!HeapTupleIsValid(tuple))
@@ -8098,11 +8216,13 @@ ATPrepAlterColumnType(List **wqueue,
if (!is_expr)
ereport(ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
- errmsg("cannot alter column named in partition key")));
+ errmsg("cannot alter column named in%s partition key",
+ recursing ? " downstream" : "")));
else
ereport(ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
- errmsg("cannot alter column referenced in partition key expression")));
+ errmsg("cannot alter column referenced in%s partition key expression",
+ recursing ? " downstream" : "")));
}
/* Look up the target type */
@@ -10235,6 +10355,11 @@ ATPrepAddInherit(Relation child_rel)
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot change inheritance of typed table")));
+ if (relid_is_partition(RelationGetRelid(child_rel)))
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot change inheritance of a partition")));
+
if (child_rel->rd_rel->relkind == RELKIND_PARTITIONED_REL)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
@@ -10291,6 +10416,11 @@ ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode)
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot inherit from partitioned table \"%s\"", parent->relname)));
+ if (relid_is_partition(RelationGetRelid(parent_rel)))
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot inherit from a partition")));
+
/*
* Prevent circularity by seeing if proposed parent inherits from child.
* (In particular, this disallows making a rel inherit from itself.)
@@ -10677,6 +10807,11 @@ ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
ObjectAddress address;
Relation parent_rel;
+ if (relid_is_partition(RelationGetRelid(rel)))
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot change inheritance of a partition")));
+
/*
* AccessShareLock on the parent is probably enough, seeing that DROP
* TABLE doesn't lock parent tables at all. We need some lock since we'll
@@ -12566,6 +12701,12 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
*/
ATSimplePermissions(attachRel, ATT_TABLE);
+ if (relid_is_partition(RelationGetRelid(attachRel)))
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("\"%s\" is already a partition",
+ RelationGetRelationName(attachRel))));
+
if (attachRel->rd_rel->reloftype)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
@@ -12599,6 +12740,12 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
errmsg("cannot attach a temporary relation of another session as partition ")));
/* If parent has OIDs, attachRel must have OIDs */
+ if (attachRel->rd_rel->reloftype)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot attach a typed table as partition")));
+
+ /* If parent has OIDs then child must have OIDs */
if (rel->rd_rel->relhasoids && !attachRel->rd_rel->relhasoids)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
@@ -12641,6 +12788,30 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
/* OK to create inheritance. Rest of the checks performed there */
CreateInheritance(attachRel, rel);
+ /*
+ * Set up to have the rows in table to be checked for violation of the
+ * partition bound spec in phase 3 scan.
+ */
+ if (!cmd->skip_validate)
+ {
+ AlteredTableInfo *tab;
+ PartitionCheck *check;
+ Expr *my_expr,
+ *parent_expr = (Expr *) RelationGetPartitionCheckExpr(rel);
+
+ tab = ATGetQueueEntry(wqueue, attachRel);
+ check = (PartitionCheck *) palloc0(sizeof(PartitionCheck));
+ my_expr = get_check_expr_from_partbound(attachRel, rel, cmd->bound);
+ check->expr = parent_expr ? makeBoolExpr(AND_EXPR,
+ list_make2(parent_expr, my_expr), -1)
+ : my_expr;
+ tab->partitionCheck = check;
+ }
+
+ /* Store bound into the catalog */
+ StorePartitionBound(RelationGetRelid(attachRel), RelationGetRelid(rel),
+ cmd->bound);
+
ObjectAddressSet(address, RelationRelationId, RelationGetRelid(attachRel));
/* keep our lock until commit */
@@ -12665,6 +12836,15 @@ ATExecDetachPartition(Relation rel, RangeVar *name)
/* All inheritance related checks are performed within the function */
RemoveInheritance(partRel, rel);
+ /* Remove the pg_partition entry */
+ RemovePartitionEntryByRelId(RelationGetRelid(partRel));
+
+ /*
+ * Invalidate the relcache so that the partition is no longer included
+ * in our partition descriptor.
+ */
+ CacheInvalidateRelcache(rel);
+
ObjectAddressSet(address, RelationRelationId, RelationGetRelid(partRel));
/* keep our lock until commit */
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 29073c0..f7a23d9 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -42,6 +42,7 @@
#include "access/transam.h"
#include "access/xact.h"
#include "catalog/namespace.h"
+#include "catalog/partition.h"
#include "commands/matview.h"
#include "commands/trigger.h"
#include "executor/execdebug.h"
@@ -1251,6 +1252,8 @@ InitResultRelInfo(ResultRelInfo *resultRelInfo,
resultRelInfo->ri_ConstraintExprs = NULL;
resultRelInfo->ri_junkFilter = NULL;
resultRelInfo->ri_projectReturning = NULL;
+ resultRelInfo->ri_PartitionCheck = (Expr *)
+ RelationGetPartitionCheckExpr(resultRelationDesc);
}
/*
@@ -1684,6 +1687,49 @@ ExecRelCheck(ResultRelInfo *resultRelInfo,
return NULL;
}
+/*
+ * ExecPartitionCheck --- check that tuple meets the partition boundary
+ * specification.
+ *
+ * Note: This is called, *iff* resultRelInfo is the main target table.
+ */
+static bool
+ExecPartitionCheck(ResultRelInfo *resultRelInfo, TupleTableSlot *slot,
+ EState *estate)
+{
+ ExprContext *econtext;
+
+ /*
+ * If first time through, build expression state tree for the partition
+ * check expression. Keep it in the per-query memory context so they'll
+ * survive throughout the query.
+ */
+ if (resultRelInfo->ri_PartitionCheckExpr == NULL)
+ {
+ List *qual = make_ands_implicit(resultRelInfo->ri_PartitionCheck);
+ resultRelInfo->ri_PartitionCheckExpr = (List *)
+ ExecPrepareExpr((Expr *) qual, estate);
+ }
+
+ /*
+ * We will use the EState's per-tuple context for evaluating constraint
+ * expressions (creating it if it's not already there).
+ */
+ econtext = GetPerTupleExprContext(estate);
+
+ /* Arrange for econtext's scan tuple to be the tuple under test */
+ econtext->ecxt_scantuple = slot;
+
+ /*
+ * NOTE: SQL specifies that a NULL result from a constraint expression
+ * is not to be treated as a failure. Therefore, tell ExecQual to
+ * return TRUE for NULL.
+ *
+ * XXX - although, it's unlikely that NULL would result.
+ */
+ return ExecQual(resultRelInfo->ri_PartitionCheckExpr, econtext, true);
+}
+
void
ExecConstraints(ResultRelInfo *resultRelInfo,
TupleTableSlot *slot, EState *estate)
@@ -1695,9 +1741,9 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
Bitmapset *insertedCols;
Bitmapset *updatedCols;
- Assert(constr);
+ Assert(constr || resultRelInfo->ri_PartitionCheck);
- if (constr->has_not_null)
+ if (constr && constr->has_not_null)
{
int natts = tupdesc->natts;
int attrChk;
@@ -1728,7 +1774,7 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
}
}
- if (constr->num_check > 0)
+ if (constr && constr->num_check > 0)
{
const char *failed;
@@ -1752,6 +1798,29 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
errtableconstraint(rel, failed)));
}
}
+
+ if (resultRelInfo->ri_PartitionCheck)
+ {
+ if (!ExecPartitionCheck(resultRelInfo, slot, estate))
+ {
+ char *val_desc;
+
+ insertedCols = GetInsertedColumns(resultRelInfo, estate);
+ updatedCols = GetUpdatedColumns(resultRelInfo, estate);
+ modifiedCols = bms_union(insertedCols, updatedCols);
+ val_desc = ExecBuildSlotValueDescription(RelationGetRelid(rel),
+ slot,
+ tupdesc,
+ modifiedCols,
+ 64);
+ ereport(ERROR,
+ (errcode(ERRCODE_CHECK_VIOLATION),
+ errmsg("new row violates the partition boundary"
+ " specification of \"%s\"",
+ RelationGetRelationName(rel)),
+ val_desc ? errdetail("Failing row contains %s.", val_desc) : 0));
+ }
+ }
}
/*
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 7ad1140..6ecb6be 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -354,7 +354,7 @@ ExecInsert(ModifyTableState *mtstate,
/*
* Check the constraints of the tuple
*/
- if (resultRelationDesc->rd_att->constr)
+ if (resultRelationDesc->rd_att->constr || resultRelInfo->ri_PartitionCheck)
ExecConstraints(resultRelInfo, slot, estate);
if (onconflict != ONCONFLICT_NONE && resultRelInfo->ri_NumIndices > 0)
@@ -907,7 +907,7 @@ lreplace:;
/*
* Check the constraints of the tuple
*/
- if (resultRelationDesc->rd_att->constr)
+ if (resultRelationDesc->rd_att->constr || resultRelInfo->ri_PartitionCheck)
ExecConstraints(resultRelInfo, slot, estate);
/*
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 6f28047..1ea1439 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -2249,6 +2249,35 @@ _readExtensibleNode(void)
}
/*
+ * _readPartitionListSpec
+ */
+static PartitionListSpec *
+_readPartitionListSpec(void)
+{
+ READ_LOCALS(PartitionListSpec);
+
+ READ_NODE_FIELD(values);
+
+ READ_DONE();
+}
+
+/*
+ * _readPartitionRangeSpec
+ */
+static PartitionRangeSpec *
+_readPartitionRangeSpec(void)
+{
+ READ_LOCALS(PartitionRangeSpec);
+
+ READ_BOOL_FIELD(lowerinc);
+ READ_NODE_FIELD(lower);
+ READ_BOOL_FIELD(upperinc);
+ READ_NODE_FIELD(upper);
+
+ READ_DONE();
+}
+
+/*
* parseNodeString
*
* Given a character string representing a node tree, parseNodeString creates
@@ -2478,6 +2507,10 @@ parseNodeString(void)
return_value = _readAlternativeSubPlan();
else if (MATCH("EXTENSIBLENODE", 14))
return_value = _readExtensibleNode();
+ else if (MATCH("PARTITIONLISTVALUES", 19))
+ return_value = _readPartitionListSpec();
+ else if (MATCH("PARTITIONRANGE", 14))
+ return_value = _readPartitionRangeSpec();
else
{
elog(ERROR, "badly formatted node string \"%.32s\"...", token);
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index b0b606e..1179643 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -27,6 +27,7 @@
#include "catalog/catalog.h"
#include "catalog/dependency.h"
#include "catalog/heap.h"
+#include "catalog/partition.h"
#include "catalog/pg_am.h"
#include "catalog/pg_constraint.h"
#include "foreign/fdwapi.h"
@@ -1112,6 +1113,7 @@ get_relation_constraints(PlannerInfo *root,
Index varno = rel->relid;
Relation relation;
TupleConstr *constr;
+ Node *pexpr;
/*
* We assume the relation has already been safely locked.
@@ -1191,6 +1193,17 @@ get_relation_constraints(PlannerInfo *root,
}
}
+ /* Append partition check constraint, if any */
+ pexpr = RelationGetPartitionCheckExpr(relation);
+ if (pexpr)
+ {
+ /* Fix Vars to have the desired varno */
+ if (varno != 1)
+ ChangeVarNodes(pexpr, 1, varno, 0);
+
+ result = list_concat(result, make_ands_implicit((Expr *) pexpr));
+ }
+
heap_close(relation, NoLock);
return result;
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 7d2fedf..f55129a 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -25,6 +25,7 @@
#include "postgres.h"
#include "access/sysattr.h"
+#include "catalog/partition.h"
#include "catalog/pg_type.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
diff --git a/src/backend/parser/parse_clause.c b/src/backend/parser/parse_clause.c
index 307e383..5124dbf 100644
--- a/src/backend/parser/parse_clause.c
+++ b/src/backend/parser/parse_clause.c
@@ -202,8 +202,8 @@ setTargetTable(ParseState *pstate, RangeVar *relation,
pstate->p_target_rangetblentry = rte;
/*
- * Override specified inheritance option, if relation is a partitioned
- * table and it's a target of INSERT (ie, alsoSource == false).
+ * For UPDATE/DELETE, force inheritance recursion, if partitioned
+ * table.
*/
if (alsoSource)
rte->inh |= relid_is_partitioned(rte->relid);
diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c
index abcc1d6..b3870f6 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -1218,8 +1218,7 @@ addRangeTableEntry(ParseState *pstate,
* The initial default on access checks is always check-for-READ-access,
* which is the right thing for all except target tables.
*
- * Note: we override the specified inheritance option, if relation is a
- * partitioned table.
+ * Note: We force inheritance recursion, if partitioned table.
*/
rte->lateral = false;
rte->inh = inh || relid_is_partitioned(RelationGetRelid(rel));
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e69cbfc..a477883 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -32,6 +32,7 @@
#include "catalog/dependency.h"
#include "catalog/heap.h"
#include "catalog/index.h"
+#include "catalog/partition.h"
#include "catalog/namespace.h"
#include "catalog/partition.h"
#include "catalog/pg_am.h"
@@ -3105,6 +3106,11 @@ transformPartitionOf(CreateStmtContext *cxt, Node *bound)
if (parentRel->rd_rel->relhasoids)
cxt->hasoids = true;
+ /*
+ * Create ColumnDefs correponding to parent's attributes as "inherited"
+ * attributes. Also, set is_for_partition whose function is similar to
+ * is_from_type for MergeAttributes()'s purposes.
+ */
tupdesc = RelationGetDescr(parentRel);
for (i = 0; i < tupdesc->natts; i++)
{
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index a9e077f..dae706e 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -8224,6 +8224,84 @@ get_rule_expr(Node *node, deparse_context *context,
}
break;
+ case T_PartitionListSpec:
+ {
+ PartitionListSpec *list_spec = (PartitionListSpec *) node;
+ ListCell *cell;
+ char *sep;
+
+ appendStringInfoString(buf, "FOR VALUES");
+
+ appendStringInfoString(buf, " IN (");
+ sep = "";
+ foreach (cell, list_spec->values)
+ {
+ Const *val = lfirst(cell);
+
+ appendStringInfoString(buf, sep);
+ get_const_expr(val, context, -1);
+ sep = ", ";
+ }
+
+ appendStringInfoString(buf, ")");
+ }
+ break;
+
+ case T_PartitionRangeSpec:
+ {
+ PartitionRangeSpec *range_spec = (PartitionRangeSpec *) node;
+ ListCell *cell;
+ char *sep;
+
+ appendStringInfoString(buf, "FOR VALUES");
+
+ appendStringInfoString(buf, " START");
+ if (!range_spec->lower)
+ appendStringInfoString(buf, " UNBOUNDED");
+ else
+ {
+ appendStringInfoString(buf, " (");
+
+ sep = "";
+ foreach (cell, range_spec->lower)
+ {
+ Const *val = lfirst(cell);
+
+ appendStringInfoString(buf, sep);
+ get_const_expr(val, context, -1);
+ sep = ", ";
+ }
+ appendStringInfoString(buf, ")");
+
+ if (!range_spec->lowerinc)
+ appendStringInfoString(buf, " EXCLUSIVE");
+ }
+
+ appendStringInfoString(buf, " END");
+
+ if (!range_spec->upper)
+ appendStringInfoString(buf, " UNBOUNDED");
+ else
+ {
+ appendStringInfoString(buf, " (");
+
+ sep = "";
+ foreach (cell, range_spec->upper)
+ {
+ Const *val = lfirst(cell);
+
+ appendStringInfoString(buf, sep);
+ get_const_expr(val, context, -1);
+ sep = ", ";
+ }
+ appendStringInfoString(buf, ")");
+
+ if (range_spec->upperinc)
+ appendStringInfoString(buf, " INCLUSIVE");
+ }
+ }
+ break;
+
case T_List:
{
char *sep;
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index e48a1f4..abd2c12 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -71,6 +71,7 @@
#include "storage/smgr.h"
#include "utils/array.h"
#include "utils/builtins.h"
+#include "utils/datum.h"
#include "utils/fmgroids.h"
#include "utils/inval.h"
#include "utils/lsyscache.h"
@@ -279,6 +280,19 @@ static OpClassCacheEnt *LookupOpclassInfo(Oid operatorClassOid,
StrategyNumber numSupport);
static void RelationCacheInitFileRemoveInDir(const char *tblspcpath);
static void unlink_initfile(const char *initfilename);
+static void RelationBuildPartitionDesc(Relation relation);
+static int32 range_partition_cmp_upper(const void *a, const void *b,
+ void *arg);
+static PartitionListBound *copyPartitionListBound(PartitionListBound *src,
+ PartitionKey key);
+static PartitionRangeBound *copyPartitionRangeBound(PartitionRangeBound *src,
+ PartitionKey key);
+static bool equalPartitionDescs(PartitionKey key, PartitionDesc pdesc1,
+ PartitionDesc pdesc2);
+static bool equalListBounds(PartitionKey key, PartitionListBound *lb1,
+ PartitionListBound *lb2);
+static bool equalRangeBounds(PartitionKey key, PartitionRangeBound *rb1,
+ PartitionRangeBound *rb2);
/*
@@ -928,6 +942,87 @@ equalRSDesc(RowSecurityDesc *rsdesc1, RowSecurityDesc *rsdesc2)
}
/*
+ * equalPartitionDescs
+ * Compare two partition descriptors for logical equality
+ */
+static bool
+equalPartitionDescs(PartitionKey key, PartitionDesc pdesc1,
+ PartitionDesc pdesc2)
+{
+ int i;
+
+ if (key == NULL)
+ return false;
+
+ if (pdesc1 != NULL)
+ {
+ if (pdesc2 == NULL)
+ return false;
+ if (pdesc1->nparts != pdesc2->nparts)
+ return false;
+
+ for (i = 0; i < pdesc1->nparts; i++)
+ {
+ if (pdesc1->oids[i] != pdesc2->oids[i])
+ return false;
+
+ if (pdesc1->lists &&
+ !equalListBounds(key, pdesc1->lists[i], pdesc2->lists[i]))
+ return false;
+
+ if (pdesc1->rangelowers &&
+ (!equalRangeBounds(key, pdesc1->rangelowers[i],
+ pdesc2->rangelowers[i]) ||
+ !equalRangeBounds(key, pdesc1->rangeuppers[i],
+ pdesc2->rangeuppers[i])))
+ return false;
+ }
+ }
+ else if (pdesc2 != NULL)
+ return false;
+
+ return true;
+}
+
+/* Are two listbounds equal (contain same values)? */
+static bool
+equalListBounds(PartitionKey key, PartitionListBound *lb1,
+ PartitionListBound *lb2)
+{
+ int i,
+ j;
+
+ if (lb1->nvalues != lb2->nvalues)
+ return false;
+
+ if (lb1->contains_null != lb2->contains_null)
+ return false;
+
+ for (i = 0; i < lb1->nvalues; i++)
+ for (j = 0; j < lb2->nvalues; j++)
+ /* Non-zero result means not equal */
+ if (DatumGetInt32(FunctionCall2Coll(&key->partsupfunc[0],
+ key->tcinfo->typcoll[0],
+ lb1->values[i],
+ lb2->values[j])))
+ return false;
+
+ return true;
+}
+
+/*
+ * Are two rangebounds equal?
+ *
+ * It's clear in this case that both are either lower bounds or upper bounds.
+ */
+static bool
+equalRangeBounds(PartitionKey key, PartitionRangeBound *rb1,
+ PartitionRangeBound *rb2)
+{
+ return partition_range_cmp_bounds(key, rb1, rb2) == 0;
+}
+
+/*
* RelationBuildDesc
*
* Build a relation descriptor. The caller must hold at least
@@ -1053,9 +1148,16 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
/* if it's a partitioned table, initialize key info */
if (relation->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+ {
RelationBuildPartitionKey(relation);
+ RelationBuildPartitionDesc(relation);
+ }
else
+ {
relation->rd_partkey = NULL;
+ relation->rd_partdesc = NULL;
+ relation->rd_pdcxt = NULL;
+ }
/*
* if it's an index, initialize index-related information
@@ -1399,6 +1501,160 @@ IndexSupportInitialize(oidvector *indclass,
}
/*
+ * RelationBuildPartitionDesc
+ * Form relation's partition descriptor from information in pg_partition
+ * and store it in the relcache
+ *
+ * Not flushed from the cache unless changed.
+ */
+static void
+RelationBuildPartitionDesc(Relation relation)
+{
+ int i,
+ nparts;
+ PartitionKey key = relation->rd_partkey;
+ PartitionDesc result;
+ PartitionInfo **parts;
+ MemoryContext oldcxt;
+
+ /* Read partitions from the catalog */
+ parts = get_partition_info(relation, &nparts);
+
+ relation->rd_pdcxt = AllocSetContextCreate(CacheMemoryContext,
+ RelationGetRelationName(relation),
+ ALLOCSET_DEFAULT_MINSIZE,
+ ALLOCSET_DEFAULT_INITSIZE,
+ ALLOCSET_DEFAULT_MAXSIZE);
+
+ oldcxt = MemoryContextSwitchTo(relation->rd_pdcxt);
+ result = (PartitionDescData *) palloc0(sizeof(PartitionDescData));
+
+ result->nparts = nparts;
+ if (parts)
+ {
+ result->oids = (Oid *) palloc0(nparts * sizeof(Oid));
+
+ switch (key->strategy)
+ {
+ case PARTITION_STRAT_LIST:
+ result->lists = (PartitionListBound **) palloc0(nparts *
+ sizeof(PartitionListBound *));
+ break;
+
+ case PARTITION_STRAT_RANGE:
+ result->rangelowers = (PartitionRangeBound **) palloc0(nparts *
+ sizeof(PartitionRangeBound *));
+ result->rangeuppers = (PartitionRangeBound **) palloc0(nparts *
+ sizeof(PartitionRangeBound *));
+
+ /* Sort on range->upper */
+ qsort_arg(parts, nparts, sizeof(PartitionInfo *),
+ range_partition_cmp_upper, key);
+ break;
+ }
+
+ for (i = 0; i < nparts; i++)
+ {
+ result->oids[i] = parts[i]->oid;
+
+ if (parts[i]->list)
+ result->lists[i] = copyPartitionListBound(parts[i]->list, key);
+ else
+ {
+ result->rangelowers[i] = copyPartitionRangeBound(parts[i]->rangelower, key);
+ result->rangeuppers[i] = copyPartitionRangeBound(parts[i]->rangeupper, key);
+ }
+ }
+
+ /* Clean up */
+ free_partition_info(parts, nparts);
+ }
+
+ MemoryContextSwitchTo(oldcxt);
+ relation->rd_partdesc = result;
+}
+
+/*
+ * range_partition_cmp_upper
+ * Compare two range partition PartitionInfos using their rangeupper
+ * bounds
+ *
+ * Used as a callback to qsort_arg for sorting range partitions.
+ */
+static int32
+range_partition_cmp_upper(const void *a, const void *b, void *arg)
+{
+ int32 result;
+ int i;
+ PartitionKey key = (PartitionKey) arg;
+ PartitionRangeBound *b1 = (*(const PartitionInfo **) a)->rangeupper,
+ *b2 = (*(const PartitionInfo **) b)->rangeupper;
+
+ if (b1->infinite || b2->infinite)
+ return b1->infinite ? 1 : -1;
+
+ for (i = 0; i < key->partnatts; i++)
+ {
+ result = DatumGetInt32(FunctionCall2Coll(&key->partsupfunc[i],
+ key->tcinfo->typcoll[i],
+ b1->val[i], b2->val[i]));
+ if (!result)
+ continue;
+
+ break;
+ }
+
+ return result;
+}
+
+/*
+ * Helper routine to copy a list partition bound struct
+ */
+static PartitionListBound *
+copyPartitionListBound(PartitionListBound *src, PartitionKey key)
+{
+ int i;
+ PartitionListBound *result;
+
+ result = (PartitionListBound *) palloc0(sizeof(PartitionListBound));
+ result->nvalues = src->nvalues;
+ result->contains_null = src->contains_null;
+ result->values = (Datum *) palloc0(src->nvalues * sizeof(Datum));
+
+ for (i = 0; i < src->nvalues; i++)
+ result->values[i] = datumCopy(src->values[i],
+ key->tcinfo->typbyval[0],
+ key->tcinfo->typlen[0]);
+ return result;
+}
+
+/*
+ * Helper routine to copy a range partition bound struct
+ */
+static PartitionRangeBound *
+copyPartitionRangeBound(PartitionRangeBound *src, PartitionKey key)
+{
+ int i;
+ PartitionRangeBound *result;
+
+ result = (PartitionRangeBound *) palloc0(sizeof(PartitionRangeBound));
+ result->infinite = src->infinite;
+ result->inclusive = src->inclusive;
+ result->lower = src->lower;
+
+ if (src->val)
+ {
+ result->val = (Datum *) palloc0(key->partnatts * sizeof(Datum));
+ for (i = 0; i < key->partnatts; i++)
+ result->val[i] = datumCopy(src->val[i],
+ key->tcinfo->typbyval[i],
+ key->tcinfo->typlen[i]);
+ }
+
+ return result;
+}
+
+/*
* LookupOpclassInfo
*
* This routine maintains a per-opclass cache of the information needed
@@ -2055,6 +2311,10 @@ RelationDestroyRelation(Relation relation, bool remember_tupdesc)
if (relation->rd_rsdesc)
MemoryContextDelete(relation->rd_rsdesc->rscxt);
FreePartitionKey(relation->rd_partkey);
+ if (relation->rd_pdcxt)
+ MemoryContextDelete(relation->rd_pdcxt);
+ if (relation->rd_partcheck)
+ pfree(relation->rd_partcheck);
if (relation->rd_fdwroutine)
pfree(relation->rd_fdwroutine);
pfree(relation);
@@ -2218,6 +2478,7 @@ RelationClearRelation(Relation relation, bool rebuild)
bool keep_tupdesc;
bool keep_rules;
bool keep_policies;
+ bool keep_partdesc;
/* Build temporary entry, but don't link it into hashtable */
newrel = RelationBuildDesc(save_relid, false);
@@ -2248,6 +2509,9 @@ RelationClearRelation(Relation relation, bool rebuild)
keep_tupdesc = equalTupleDescs(relation->rd_att, newrel->rd_att);
keep_rules = equalRuleLocks(relation->rd_rules, newrel->rd_rules);
keep_policies = equalRSDesc(relation->rd_rsdesc, newrel->rd_rsdesc);
+ keep_partdesc = equalPartitionDescs(relation->rd_partkey,
+ relation->rd_partdesc,
+ newrel->rd_partdesc);
/*
* Perform swapping of the relcache entry contents. Within this
@@ -2303,6 +2567,10 @@ RelationClearRelation(Relation relation, bool rebuild)
/* pgstat_info must be preserved */
SWAPFIELD(struct PgStat_TableStatus *, pgstat_info);
+ /* preserve old partdesc if no logical change */
+ if (keep_partdesc)
+ SWAPFIELD(PartitionDesc, rd_partdesc);
+
#undef SWAPFIELD
/* And now we can throw away the temporary entry */
@@ -3529,6 +3797,20 @@ RelationCacheInitializePhase3(void)
restart = true;
}
+ /*
+ * Reload partition key and descriptor for a partitioned table.
+ */
+ if (relation->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+ {
+ RelationBuildPartitionKey(relation);
+ Assert(relation->rd_partkey != NULL);
+
+ RelationBuildPartitionDesc(relation);
+ Assert(relation->rd_partdesc != NULL);
+
+ restart = true;
+ }
+
/* Release hold on the relation */
RelationDecrementReferenceCount(relation);
@@ -4984,6 +5266,8 @@ load_relcache_init_file(bool shared)
rel->trigdesc = NULL;
rel->rd_rsdesc = NULL;
rel->rd_partkey = NULL;
+ rel->rd_partdesc = NULL;
+ rel->rd_partcheck = NULL;
rel->rd_indexprs = NIL;
rel->rd_indpred = NIL;
rel->rd_exclops = NULL;
diff --git a/src/backend/utils/cache/syscache.c b/src/backend/utils/cache/syscache.c
index a6563fe..4a5783a 100644
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -49,6 +49,7 @@
#include "catalog/pg_operator.h"
#include "catalog/pg_opfamily.h"
#include "catalog/pg_partitioned.h"
+#include "catalog/pg_partition.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_range.h"
#include "catalog/pg_rewrite.h"
@@ -569,6 +570,17 @@ static const struct cachedesc cacheinfo[] = {
},
8
},
+ {PartitionRelationId, /* PARTRELID */
+ PartitionRelidIndexId,
+ 1,
+ {
+ Anum_pg_partition_partrelid,
+ 0,
+ 0,
+ 0
+ },
+ 128
+ },
{PartitionedRelationId, /* PARTEDRELID */
PartitionedRelidIndexId,
1,
diff --git a/src/bin/pg_dump/common.c b/src/bin/pg_dump/common.c
index 1cbb987..c8e56bd 100644
--- a/src/bin/pg_dump/common.c
+++ b/src/bin/pg_dump/common.c
@@ -68,6 +68,8 @@ static int numextmembers;
static void flagInhTables(TableInfo *tbinfo, int numTables,
InhInfo *inhinfo, int numInherits);
+static void flagPartitions(TableInfo *tblinfo, int numTables,
+ PartInfo *partinfo, int numPartitions);
static void flagInhAttrs(DumpOptions *dopt, TableInfo *tblinfo, int numTables);
static DumpableObject **buildIndexArray(void *objArray, int numObjs,
Size objSize);
@@ -75,6 +77,8 @@ static int DOCatalogIdCompare(const void *p1, const void *p2);
static int ExtensionMemberIdCompare(const void *p1, const void *p2);
static void findParentsByOid(TableInfo *self,
InhInfo *inhinfo, int numInherits);
+static void findPartitionParentByOid(TableInfo *self, PartInfo *partinfo,
+ int numPartitions);
static int strInArray(const char *pattern, char **arr, int arr_size);
@@ -93,8 +97,10 @@ getSchemaData(Archive *fout, int *numTablesPtr)
NamespaceInfo *nspinfo;
ExtensionInfo *extinfo;
InhInfo *inhinfo;
+ PartInfo *partinfo;
int numAggregates;
int numInherits;
+ int numPartitions;
int numRules;
int numProcLangs;
int numCasts;
@@ -232,6 +238,10 @@ getSchemaData(Archive *fout, int *numTablesPtr)
inhinfo = getInherits(fout, &numInherits);
if (g_verbose)
+ write_msg(NULL, "reading partition information\n");
+ partinfo = getPartitions(fout, &numPartitions);
+
+ if (g_verbose)
write_msg(NULL, "reading event triggers\n");
getEventTriggers(fout, &numEventTriggers);
@@ -245,6 +255,11 @@ getSchemaData(Archive *fout, int *numTablesPtr)
write_msg(NULL, "finding inheritance relationships\n");
flagInhTables(tblinfo, numTables, inhinfo, numInherits);
+ /* Link tables to partition parents, mark parents as interesting */
+ if (g_verbose)
+ write_msg(NULL, "finding partition relationships\n");
+ flagPartitions(tblinfo, numTables, partinfo, numPartitions);
+
if (g_verbose)
write_msg(NULL, "reading column info for interesting tables\n");
getTableAttrs(fout, tblinfo, numTables);
@@ -319,6 +334,43 @@ flagInhTables(TableInfo *tblinfo, int numTables,
}
}
+/* flagPartitions -
+ * Fill in parent link fields of every target table that is partition,
+ * and mark parents of partitions as interesting
+ *
+ * modifies tblinfo
+ */
+static void
+flagPartitions(TableInfo *tblinfo, int numTables,
+ PartInfo *partinfo, int numPartitions)
+{
+ int i;
+
+ for (i = 0; i < numTables; i++)
+ {
+ /* Some kinds are never partitions */
+ if (tblinfo[i].relkind == RELKIND_SEQUENCE ||
+ tblinfo[i].relkind == RELKIND_VIEW ||
+ tblinfo[i].relkind == RELKIND_MATVIEW)
+ continue;
+
+ /* Don't bother computing anything for non-target tables, either */
+ if (!tblinfo[i].dobj.dump)
+ continue;
+
+ /* Find the parent TableInfo and save */
+ findPartitionParentByOid(&tblinfo[i], partinfo, numPartitions);
+
+ /* Mark the parent as interesting for getTableAttrs */
+ if (tblinfo[i].partitionOf)
+ {
+ tblinfo[i].partitionOf->interesting = true;
+ addObjectDependency(&tblinfo[i].dobj,
+ tblinfo[i].partitionOf->dobj.dumpId);
+ }
+ }
+}
+
/* flagInhAttrs -
* for each dumpable table in tblinfo, flag its inherited attributes
*
@@ -920,6 +972,40 @@ findParentsByOid(TableInfo *self,
}
/*
+ * findPartitionParentByOid
+ * find a partition's parent in tblinfo[]
+ */
+static void
+findPartitionParentByOid(TableInfo *self, PartInfo *partinfo,
+ int numPartitions)
+{
+ Oid oid = self->dobj.catId.oid;
+ int i;
+
+ for (i = 0; i < numPartitions; i++)
+ {
+ if (partinfo[i].partrelid == oid)
+ {
+ TableInfo *parent;
+
+ parent = findTableByOid(partinfo[i].partparent);
+ if (parent == NULL)
+ {
+ write_msg(NULL, "failed sanity check, parent OID %u of table \"%s\" (OID %u) not found\n",
+ partinfo[i].partparent,
+ self->dobj.name,
+ oid);
+ exit_nicely(1);
+ }
+ self->partitionOf = parent;
+
+ /* While we're at it, also save the partdef */
+ self->partitiondef = partinfo[i].partdef;
+ }
+ }
+}
+
+/*
* parseOidArray
* parse a string of numbers delimited by spaces into a character array
*
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index b3da2f7..97d9517 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -6085,6 +6085,63 @@ getInherits(Archive *fout, int *numInherits)
}
/*
+ * getPartitions
+ * read all the partition inheritance and partition bound information
+ * from the system catalogs return them in the PartInfo* structure
+ *
+ * numPartitions is set to the number of pairs read in
+ */
+PartInfo *
+getPartitions(Archive *fout, int *numPartitions)
+{
+ PGresult *res;
+ int ntups;
+ int i;
+ PQExpBuffer query = createPQExpBuffer();
+ PartInfo *partinfo;
+
+ int i_partrelid;
+ int i_partparent;
+ int i_partbound;
+
+ /* Make sure we are in proper schema */
+ selectSourceSchema(fout, "pg_catalog");
+
+ /* find all the inheritance information */
+
+ appendPQExpBufferStr(query,
+ "SELECT partrelid, inhparent AS partparent, "
+ " pg_get_expr(partbound, partrelid) AS partbound "
+ "FROM pg_partition, pg_inherits "
+ "WHERE partrelid = inhrelid");
+
+ res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
+
+ ntups = PQntuples(res);
+
+ *numPartitions = ntups;
+
+ partinfo = (PartInfo *) pg_malloc(ntups * sizeof(PartInfo));
+
+ i_partrelid = PQfnumber(res, "partrelid");
+ i_partparent = PQfnumber(res, "partparent");
+ i_partbound = PQfnumber(res, "partbound");
+
+ for (i = 0; i < ntups; i++)
+ {
+ partinfo[i].partrelid = atooid(PQgetvalue(res, i, i_partrelid));
+ partinfo[i].partparent = atooid(PQgetvalue(res, i, i_partparent));
+ partinfo[i].partdef = pg_strdup(PQgetvalue(res, i, i_partbound));
+ }
+
+ PQclear(res);
+
+ destroyPQExpBuffer(query);
+
+ return partinfo;
+}
+
+/*
* getIndexes
* get information about every index on a dumpable table
*
@@ -15278,6 +15335,17 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
if (tbinfo->reloftype && !dopt->binary_upgrade)
appendPQExpBuffer(q, " OF %s", tbinfo->reloftype);
+ if (tbinfo->partitionOf && !dopt->binary_upgrade)
+ {
+ TableInfo *parentRel = tbinfo->partitionOf;
+
+ appendPQExpBuffer(q, " PARTITION OF ");
+ if (parentRel->dobj.namespace != tbinfo->dobj.namespace)
+ appendPQExpBuffer(q, "%s.",
+ fmtId(parentRel->dobj.namespace->dobj.name));
+ appendPQExpBufferStr(q, fmtId(parentRel->dobj.name));
+ }
+
if (tbinfo->relkind != RELKIND_MATVIEW)
{
/* Dump the attributes */
@@ -15306,8 +15374,11 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
(!tbinfo->inhNotNull[j] ||
dopt->binary_upgrade));
- /* Skip column if fully defined by reloftype */
- if (tbinfo->reloftype &&
+ /*
+ * Skip column if fully defined by reloftype or the
+ * partition parent.
+ */
+ if ((tbinfo->reloftype || tbinfo->partitionOf) &&
!has_default && !has_notnull && !dopt->binary_upgrade)
continue;
@@ -15336,7 +15407,8 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
}
/* Attribute type */
- if (tbinfo->reloftype && !dopt->binary_upgrade)
+ if ((tbinfo->reloftype || tbinfo->partitionOf) &&
+ !dopt->binary_upgrade)
{
appendPQExpBufferStr(q, " WITH OPTIONS");
}
@@ -15401,15 +15473,22 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
if (actual_atts)
appendPQExpBufferStr(q, "\n)");
- else if (!(tbinfo->reloftype && !dopt->binary_upgrade))
+ else if (!((tbinfo->reloftype || tbinfo->partitionOf) &&
+ !dopt->binary_upgrade))
{
/*
* We must have a parenthesized attribute list, even though
- * empty, when not using the OF TYPE syntax.
+ * empty, when not using the OF TYPE or PARTITION OF syntax.
*/
appendPQExpBufferStr(q, " (\n)");
}
+ if (tbinfo->partitiondef && !dopt->binary_upgrade)
+ {
+ appendPQExpBufferStr(q, "\n");
+ appendPQExpBufferStr(q, tbinfo->partitiondef);
+ }
+
if (numParents > 0 && !dopt->binary_upgrade)
{
appendPQExpBufferStr(q, "\nINHERITS (");
@@ -15579,6 +15658,15 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
tbinfo->reloftype);
}
+ if (tbinfo->partitionOf)
+ {
+ appendPQExpBufferStr(q, "\n-- For binary upgrade, set up partitions this way.\n");
+ appendPQExpBuffer(q, "ALTER TABLE ONLY %s ATTACH PARTITION %s %s;\n",
+ fmtId(tbinfo->partitionOf->dobj.name),
+ tbinfo->dobj.name,
+ tbinfo->partitiondef);
+ }
+
appendPQExpBufferStr(q, "\n-- For binary upgrade, set heap's relfrozenxid and relminmxid\n");
appendPQExpBuffer(q, "UPDATE pg_catalog.pg_class\n"
"SET relfrozenxid = '%u', relminmxid = '%u'\n"
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 0292859..760067a 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -320,6 +320,8 @@ typedef struct _tableInfo
struct _tableDataInfo *dataObj; /* TableDataInfo, if dumping its data */
int numTriggers; /* number of triggers for table */
struct _triggerInfo *triggers; /* array of TriggerInfo structs */
+ struct _tableInfo *partitionOf; /* TableInfo for the partition parent */
+ char *partitiondef; /* partition key definition */
} TableInfo;
typedef struct _attrDefInfo
@@ -460,6 +462,15 @@ typedef struct _inhInfo
Oid inhparent; /* OID of its parent */
} InhInfo;
+/* PartInfo isn't a DumpableObject, just temporary state */
+typedef struct _partInfo
+{
+ Oid partrelid; /* OID of a partition */
+ Oid partparent; /* OID of its parent */
+ char *partdef; /* partition bound definition */
+} PartInfo;
+
+
typedef struct _prsInfo
{
DumpableObject dobj;
@@ -626,6 +637,7 @@ extern ConvInfo *getConversions(Archive *fout, int *numConversions);
extern TableInfo *getTables(Archive *fout, int *numTables);
extern void getOwnedSeqs(Archive *fout, TableInfo tblinfo[], int numTables);
extern InhInfo *getInherits(Archive *fout, int *numInherits);
+extern PartInfo *getPartitions(Archive *fout, int *numPartitions);
extern void getIndexes(Archive *fout, TableInfo tblinfo[], int numTables);
extern void getConstraints(Archive *fout, TableInfo tblinfo[], int numTables);
extern RuleInfo *getRules(Archive *fout, int *numRules);
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 78a1fdb..6bbfc9a 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -1694,6 +1694,34 @@ describeOneTableDetails(const char *schemaname,
}
/* Make footers */
+ if (pset.sversion >= 90600)
+ {
+ /* Get the partition information */
+ PGresult *result;
+ char *parent_name;
+ char *partdef;
+
+ printfPQExpBuffer(&buf,
+ "SELECT inhparent::pg_catalog.regclass, pg_get_expr(partbound, partrelid)"
+ " FROM pg_catalog.pg_partition p"
+ " JOIN pg_catalog.pg_inherits i"
+ " ON p.partrelid = i.inhrelid"
+ " WHERE p.partrelid = '%s';", oid);
+ result = PSQLexec(buf.data);
+ if (!result)
+ goto error_return;
+
+ if (PQntuples(result) > 0)
+ {
+ parent_name = PQgetvalue(result, 0, 0);
+ partdef = PQgetvalue(result, 0, 1);
+ printfPQExpBuffer(&tmpbuf, _("Partition Of: %s %s"), parent_name,
+ partdef);
+ printTableAddFooter(&cont, tmpbuf.data);
+ PQclear(result);
+ }
+ }
+
if (pset.sversion >= 90600 && tableinfo.relkind == 'P')
{
/* Get the partition key information */
@@ -2455,8 +2483,12 @@ describeOneTableDetails(const char *schemaname,
PQclear(result);
}
- /* print inherited tables */
- printfPQExpBuffer(&buf, "SELECT c.oid::pg_catalog.regclass FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i WHERE c.oid=i.inhparent AND i.inhrelid = '%s' ORDER BY inhseqno;", oid);
+ /* print inherited tables (exclude, if parent is a partitioned table) */
+ printfPQExpBuffer(&buf,
+ "SELECT c.oid::pg_catalog.regclass"
+ " FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i"
+ " WHERE c.oid=i.inhparent AND i.inhrelid = '%s'"
+ " AND c.relkind != 'P' ORDER BY inhseqno;", oid);
result = PSQLexec(buf.data);
if (!result)
@@ -2485,9 +2517,15 @@ describeOneTableDetails(const char *schemaname,
PQclear(result);
}
- /* print child tables */
+ /* print child tables (exclude, if parent is a partitioned table) */
if (pset.sversion >= 80300)
- printfPQExpBuffer(&buf, "SELECT c.oid::pg_catalog.regclass FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i WHERE c.oid=i.inhrelid AND i.inhparent = '%s' ORDER BY c.oid::pg_catalog.regclass::pg_catalog.text;", oid);
+ printfPQExpBuffer(&buf,
+ "SELECT c.oid::pg_catalog.regclass"
+ " FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i"
+ " WHERE c.oid=i.inhrelid AND"
+ " i.inhparent = '%s' AND"
+ " EXISTS (SELECT 1 FROM pg_class c WHERE c.oid = '%s' AND c.relkind != 'P')"
+ " ORDER BY c.oid::pg_catalog.regclass::pg_catalog.text;", oid, oid);
else
printfPQExpBuffer(&buf, "SELECT c.oid::pg_catalog.regclass FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i WHERE c.oid=i.inhrelid AND i.inhparent = '%s' ORDER BY c.relname;", oid);
diff --git a/src/include/catalog/indexing.h b/src/include/catalog/indexing.h
index cadb741..73786c0 100644
--- a/src/include/catalog/indexing.h
+++ b/src/include/catalog/indexing.h
@@ -322,6 +322,9 @@ DECLARE_UNIQUE_INDEX(pg_replication_origin_roname_index, 6002, on pg_replication
DECLARE_UNIQUE_INDEX(pg_partitioned_partedrelid_index, 3351, on pg_partitioned using btree(partedrelid oid_ops));
#define PartitionedRelidIndexId 3351
+DECLARE_UNIQUE_INDEX(pg_partition_partrelid_index, 3354, on pg_partition using btree(partrelid oid_ops));
+#define PartitionRelidIndexId 3354
+
/* last step of initialization script: build the indexes declared above */
BUILD_INDICES
diff --git a/src/include/catalog/partition.h b/src/include/catalog/partition.h
index 31dbe17..7d4bf2f 100644
--- a/src/include/catalog/partition.h
+++ b/src/include/catalog/partition.h
@@ -42,6 +42,76 @@ typedef struct PartitionKeyData
typedef struct PartitionKeyData *PartitionKey;
+/* Internal representation of a list partition bound */
+typedef struct PartitionListBound
+{
+ bool contains_null;
+ int nvalues; /* number of values in the following array */
+ Datum *values; /* values contained in the list */
+} PartitionListBound;
+
+/* Internal representation of a range partition bound */
+typedef struct
+{
+ Datum *val; /* composite bound value, if any */
+ bool infinite; /* bound is +/- infinity */
+ bool inclusive; /* bound is inclusive (vs exclusive) */
+ bool lower; /* this is the lower (vs upper) bound */
+} PartitionRangeBound;
+
+/*
+ * Information about a single partition
+ */
+typedef struct PartitionInfo
+{
+ Oid oid; /* partition OID */
+ PartitionListBound *list; /* list partition bound */
+ PartitionRangeBound *rangelower; /* range partition lower bound */
+ PartitionRangeBound *rangeupper; /* range partition upper bound */
+} PartitionInfo;
+
+/*
+ * Information about partitions of a partitioned table.
+ */
+typedef struct PartitionDescData
+{
+ int nparts; /* number of partitions */
+ Oid *oids; /* OIDs of the partitions */
+ PartitionListBound **lists; /* list bounds */
+ PartitionRangeBound **rangelowers; /* range lower bounds */
+ PartitionRangeBound **rangeuppers; /* range upper bounds */
+} PartitionDescData;
+
+typedef struct PartitionDescData *PartitionDesc;
+
+/*
+ * Partition tree node
+ *
+ * pkinfo PartitionKey executor state
+ * pdesc info about immediate partitions (see
+ PartitionDescData)
+ * index index within 0..(parent_pdesc->nparts - 1)
+ * num_leaf_partitions number of leaf partitions in the partition
+ * tree rooted at this node
+ * offset index (0-based) of the first leaf partition
+ * in the partition tree rooted at this node
+ * downlink link to the leftmost child node
+ * next link to the right sibling node on a given level
+ */
+typedef struct PartitionDescNodeData
+{
+ PartitionDesc pdesc;
+ Oid relid;
+ int index;
+ int offset;
+ int num_leaf_partitions;
+
+ struct PartitionDescNodeData *downlink;
+ struct PartitionDescNodeData *next;
+} PartitionDescNodeData;
+
+typedef struct PartitionDescNodeData *PartitionDescNode;
+
extern void StorePartitionKey(Relation rel,
char strategy,
int16 partnatts,
@@ -53,4 +123,19 @@ extern void RelationBuildPartitionKey(Relation relation);
extern void FreePartitionKey(PartitionKey key);
extern bool relid_is_partitioned(Oid relid);
+extern void StorePartitionBound(Oid relid, Oid parentId, Node *bound);
+extern void RemovePartitionEntryByRelId(Oid relid);
+extern bool relid_is_partition(Oid relid);
+extern Oid get_partition_parent(Oid relid);
+extern void RelationDropPartitions(Oid relid);
+extern PartitionInfo **get_partition_info(Relation rel, int *nparts);
+extern void free_partition_info(PartitionInfo **p, int num);
+extern int32 partition_range_cmp_bounds(PartitionKey key,
+ PartitionRangeBound *b1,
+ PartitionRangeBound *b2);
+extern Node *RelationGetPartitionCheckExpr(Relation rel);
+extern Expr *get_check_expr_from_partbound(Relation rel, Relation parent,
+ Node *bound);
+extern List *get_leaf_partition_oids(PartitionDescNode pdnode);
+extern PartitionDescNode RelationGetPartitionDescNode(Relation rel);
#endif /* PARTITION_H */
diff --git a/src/include/catalog/pg_partition.h b/src/include/catalog/pg_partition.h
new file mode 100644
index 0000000..d31a6b7
--- /dev/null
+++ b/src/include/catalog/pg_partition.h
@@ -0,0 +1,54 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_partition.h
+ * definition of the system "partition" relation (pg_partition)
+ * along with the relation's initial contents.
+ *
+ *
+ * Portions Copyright (c) 1996-2015, PostgreSQL Global Development Group
+ *
+ * $PostgreSQL: pgsql/src/include/catalog/pg_partition.h $
+ *
+ * NOTES
+ * the genbki.sh script reads this file and generates .bki
+ * information from the DATA() statements.
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_PARTITION_H
+#define PG_PARTITION_H
+
+#include "catalog/genbki.h"
+
+/* ----------------
+ * pg_partitioned_rel definition. cpp turns this into
+ * typedef struct FormData_pg_partitioned_rel
+ * ----------------
+ */
+#define PartitionRelationId 3353
+
+CATALOG(pg_partition,3353) BKI_WITHOUT_OIDS
+{
+ Oid partrelid; /* partition relation OID */
+
+#ifdef CATALOG_VARLEN /* variable-length fields start here */
+ pg_node_tree partbound; /* node representation of partition bound */
+#endif
+} FormData_pg_partition;
+
+/* ----------------
+ * Form_pg_partition corresponds to a pointer to a tuple with
+ * the format of pg_partition relation.
+ * ----------------
+ */
+typedef FormData_pg_partition *Form_pg_partition;
+
+/* ----------------
+ * compiler constants for pg_partition
+ * ----------------
+ */
+#define Natts_pg_partition 2
+#define Anum_pg_partition_partrelid 1
+#define Anum_pg_partition_partbound 2
+
+#endif /* PG_PARTITION_H */
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index ee4e189..74288e2 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -319,6 +319,8 @@ typedef struct JunkFilter
* projectReturning for computing a RETURNING list
* onConflictSetProj for computing ON CONFLICT DO UPDATE SET
* onConflictSetWhere list of ON CONFLICT DO UPDATE exprs (qual)
+ * PartitionCheck partition check expression
+ * PartitionCheckExpr partition check expression state
* ----------------
*/
typedef struct ResultRelInfo
@@ -343,6 +345,8 @@ typedef struct ResultRelInfo
ProjectionInfo *ri_projectReturning;
ProjectionInfo *ri_onConflictSetProj;
List *ri_onConflictSetWhere;
+ Expr *ri_PartitionCheck;
+ List *ri_PartitionCheckExpr;
} ResultRelInfo;
/* ----------------
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 2e43ebf..20c5965 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -91,6 +91,10 @@ typedef struct RelationData
struct RowSecurityDesc *rd_rsdesc; /* row security policies, or NULL */
struct PartitionKeyData *rd_partkey; /* partition key, or NULL */
+ MemoryContext rd_pdcxt; /* private context for partdesc */
+ struct PartitionDescData *rd_partdesc; /* partitions, or NULL */
+ Node *rd_partcheck; /* partition CHECK constraint expression
+ * tree */
/* data managed by RelationGetIndexList: */
List *rd_indexlist; /* list of OIDs of indexes on relation */
@@ -510,6 +514,12 @@ typedef struct ViewOptions
*/
#define RelationGetPartitionKey(relation) ((relation)->rd_partkey)
+/*
+ * RelationGetPartitionDesc
+ * Returns partition descriptor for a relation.
+ */
+#define RelationGetPartitionDesc(relation) ((relation)->rd_partdesc)
+
/* routines in utils/cache/relcache.c */
extern void RelationIncrementReferenceCount(Relation rel);
extern void RelationDecrementReferenceCount(Relation rel);
diff --git a/src/include/utils/syscache.h b/src/include/utils/syscache.h
index e727842..4e493e0 100644
--- a/src/include/utils/syscache.h
+++ b/src/include/utils/syscache.h
@@ -72,6 +72,7 @@ enum SysCacheIdentifier
OPEROID,
OPFAMILYAMNAMENSP,
OPFAMILYOID,
+ PARTRELID,
PARTEDRELID,
PROCNAMEARGSNSP,
PROCOID,
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index b1e473d..57bde9d 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -118,6 +118,7 @@ pg_namespace|t
pg_opclass|t
pg_operator|t
pg_opfamily|t
+pg_partition|t
pg_partitioned|t
pg_pltemplate|t
pg_policy|t
--
1.7.1
[text/x-diff] 0006-Introduce-tuple-routing-for-partitioned-tables-v5.patch (29.3K, ../../573ECCC3.7080104@lab.ntt.co.jp/7-0006-Introduce-tuple-routing-for-partitioned-tables-v5.patch)
download | inline diff:
From e9850f583ad1088d0c36f68b48ed9129ac3fde58 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Fri, 18 Mar 2016 19:48:49 +0900
Subject: [PATCH 6/7] Introduce tuple routing for partitioned tables.
A tuple inserted into a partitioned table using insert or copy is now
routed to the appropriate leaf partition, recursively.
State required for the same is built in advance by BeginCopy() and
ExecInitModifyTable(). It consists of N ResultRelInfos and pointers
to TupleConversionMap structs, where N is the number of leaf partitions
in the multi-level partition tree. One needs only to get a pointer to the
root PartitionDescNode node for the tree. A tuple conversion map is only
allocated if the tuple descriptor of a leaf partition differs from that
of the root table. Partition check expression is not initialized for
leaf partition result relations, as they are redundant in this case.
Whether or not to build one is now determined by new parameter
load_partition_check of InitResultRelInfo().
CopyFrom() or ExecInsert() passes the root node (set up initially, as
just mentioned) along with the tuple slot to get_partition_for_tuple()
which traverses the tree to determine the appropriate leaf partition for
the tuple. It returns the index (0..N) of the leaf partition. Caller
then switches the result relation (ResultRelInfo) appropriately and
performs the tuple conversion, if required, before following up with
the row triggers, constraints, actual heap and index insertion.
* Following requires further consideration:
Both CopyFrom() and ExecInsert() switch to the partition before invoking
BR triggers and back after AR trigger are processed. That ignores parent's
triggers completely. Note that those triggers are not required to be present
in each partition. OTOH, constraints are fine (both check and not null),
since parent's constraints are present in partitions.
---
src/backend/catalog/partition.c | 324 ++++++++++++++++++++++++++++++++
src/backend/commands/copy.c | 113 +++++++++++-
src/backend/commands/tablecmds.c | 1 +
src/backend/executor/execMain.c | 44 +++++-
src/backend/executor/nodeModifyTable.c | 96 ++++++++++
src/backend/parser/analyze.c | 8 +
src/include/catalog/partition.h | 24 +++
src/include/executor/executor.h | 6 +
src/include/nodes/execnodes.h | 10 +
9 files changed, 621 insertions(+), 5 deletions(-)
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index eb27937..f669932 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -96,6 +96,21 @@ static Node *generate_partition_check_expr(Relation rel);
static PartitionDescNode GetPartitionDescNodeRecurse(Relation rel, int offset);
static int get_leaf_partition_count(PartitionDescNode pdnode);
+static PartitionKeyExecInfo *BuildPartitionKeyExecInfo(Relation rel);
+static void FormPartitionKeyDatum(PartitionKeyExecInfo *pkinfo,
+ TupleTableSlot *slot,
+ EState *estate,
+ Datum *values,
+ bool *isnull);
+static int list_partition_for_tuple(PartitionKey key, PartitionDesc pdesc,
+ Datum value, bool isnull);
+static int range_partition_for_tuple(PartitionKey key, PartitionDesc pdesc,
+ Datum *values);
+static int range_partition_bsearch(PartitionKey key, PartitionDesc pdesc,
+ Datum *values);
+static bool rightof(PartitionKey key, Datum *values, PartitionRangeBound *bound);
+static bool leftof(PartitionKey key, Datum *values, PartitionRangeBound *bound);
+
/*
* StorePartitionKey
* Store the partition keys of rel into pg_partitioned catalog
@@ -1618,6 +1633,7 @@ GetPartitionDescNodeRecurse(Relation rel, int offset)
/* First build our own node */
root = (PartitionDescNode) palloc0(sizeof(PartitionDescNodeData));
+ root->pkinfo = BuildPartitionKeyExecInfo(rel);
root->pdesc = pdesc;
root->relid = RelationGetRelid(rel);
root->offset = offset;
@@ -1735,3 +1751,311 @@ get_leaf_partition_oids(PartitionDescNode pdnode)
return result;
}
+
+/* ----------------
+ * BuildPartitionKeyExecInfo
+ * Construct a list of PartitionKeyExecInfo records for an open
+ * relation
+ *
+ * PartitionKeyExecInfo stores the information about the partition key
+ * that's needed when inserting tuples into a partitioned table; especially,
+ * partition key expression state if there are any expression columns in
+ * the partition key. Normally we build a PartitionKeyExecInfo for a
+ * partitioned table just once per command, and then use it for (potentially)
+ * many tuples.
+ * ----------------
+ */
+static PartitionKeyExecInfo *
+BuildPartitionKeyExecInfo(Relation rel)
+{
+ PartitionKeyExecInfo *pkinfo;
+
+ pkinfo = (PartitionKeyExecInfo *) palloc0(sizeof(PartitionKeyExecInfo));
+ pkinfo->pi_Key = CopyPartitionKey(rel->rd_partkey);
+ pkinfo->pi_ExpressionState = NIL;
+
+ return pkinfo;
+}
+
+/*
+ * get_partition_for_tuple
+ * Recursively finds the "leaf" partition for tuple (slot)
+ *
+ * Returns -1 if no partition is found and sets *failed_at to the OID of
+ * the partitioned table whose partition was not found.
+ */
+int
+get_partition_for_tuple(PartitionDescNode pdnode,
+ TupleTableSlot *slot,
+ EState *estate,
+ Oid *failed_at)
+{
+ PartitionKeyExecInfo *pkinfo = pdnode->pkinfo;
+ PartitionDescNode node;
+ Datum values[PARTITION_MAX_KEYS];
+ bool isnull[PARTITION_MAX_KEYS];
+ int i;
+ int index;
+
+ /* Guard against stack overflow due to overly deep partition tree */
+ check_stack_depth();
+
+ if (pdnode->pdesc->nparts == 0)
+ {
+ *failed_at = pdnode->relid;
+ return -1;
+ }
+
+ /* Extract partition key from tuple */
+ Assert(GetPerTupleExprContext(estate)->ecxt_scantuple == slot);
+ FormPartitionKeyDatum(pkinfo, slot, estate, values, isnull);
+
+ /* Disallow nulls, if range partition key */
+ for (i = 0; i < pkinfo->pi_Key->partnatts; i++)
+ if (isnull[i] && pkinfo->pi_Key->strategy == PARTITION_STRAT_RANGE)
+ ereport(ERROR,
+ (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+ errmsg("range partition key contains null")));
+
+ switch (pkinfo->pi_Key->strategy)
+ {
+ case PARTITION_STRAT_LIST:
+ index = list_partition_for_tuple(pkinfo->pi_Key, pdnode->pdesc,
+ values[0], isnull[0]);
+ break;
+
+ case PARTITION_STRAT_RANGE:
+ index = range_partition_for_tuple(pkinfo->pi_Key, pdnode->pdesc,
+ values);
+ break;
+ }
+
+ /* Don't recurse, if no partition exists for the key at this level... */
+ if (index < 0)
+ {
+ *failed_at = pdnode->relid;
+ return index;
+ }
+
+ /* ... or, if the index'th partition is a leaf partition. */
+ if (!relid_is_partitioned(pdnode->pdesc->oids[index]))
+ {
+ PartitionDescNode prev;
+
+ /* Patiently determine own leaf partition index */
+ prev = node = pdnode->downlink;
+ if (node && node->index < index)
+ {
+ while (node)
+ {
+ if (node->index > index)
+ {
+ node = prev;
+ break;
+ }
+
+ prev = node;
+ node = node->next;
+ }
+
+ if (!node)
+ node = prev;
+
+ /*
+ * Account for siblings on left that are partition trees and those
+ * that are leaf partitions themsleves.
+ */
+ return node->offset + node->num_leaf_partitions +
+ (index - node->index - 1);
+ }
+ else
+ /* Only leaf partitions in pdnode->offset..index */
+ return pdnode->offset + index;
+
+ }
+
+ /* Recurse, by locating the index'th partition's node */
+ node = pdnode->downlink;
+ while (node->next != NULL && node->index != index)
+ node = node->next;
+ Assert (node != NULL);
+
+ return get_partition_for_tuple(node, slot, estate, failed_at);
+}
+
+/*
+ * FormPartitionKeyDatum
+ * Construct values[] and isnull[] arrays for partition key columns
+ */
+static void
+FormPartitionKeyDatum(PartitionKeyExecInfo *pkinfo,
+ TupleTableSlot *slot,
+ EState *estate,
+ Datum *values,
+ bool *isnull)
+{
+ ListCell *partexpr_item;
+ int i;
+
+ if (pkinfo->pi_Key->partexprs != NIL && pkinfo->pi_ExpressionState == NIL)
+ {
+ /* First time through, set up expression evaluation state */
+ pkinfo->pi_ExpressionState = (List *)
+ ExecPrepareExpr((Expr *) pkinfo->pi_Key->partexprs,
+ estate);
+ /* Check caller has set up context correctly */
+ Assert(GetPerTupleExprContext(estate)->ecxt_scantuple == slot);
+ }
+
+ partexpr_item = list_head(pkinfo->pi_ExpressionState);
+ for (i = 0; i < pkinfo->pi_Key->partnatts; i++)
+ {
+ AttrNumber keycol = pkinfo->pi_Key->partattrs[i];
+ Datum pkDatum;
+ bool isNull;
+
+ if (keycol != 0)
+ {
+ /* Plain column; get the value directly from the heap tuple */
+ pkDatum = slot_getattr(slot, keycol, &isNull);
+ }
+ else
+ {
+ /* Expression; need to evaluate it */
+ if (partexpr_item == NULL)
+ elog(ERROR, "wrong number of partition key expressions");
+ pkDatum = ExecEvalExprSwitchContext((ExprState *) lfirst(partexpr_item),
+ GetPerTupleExprContext(estate),
+ &isNull,
+ NULL);
+ partexpr_item = lnext(partexpr_item);
+ }
+ values[i] = pkDatum;
+ isnull[i] = isNull;
+ }
+
+ if (partexpr_item != NULL)
+ elog(ERROR, "wrong number of partition key expressions");
+}
+
+/*
+ * list_partition_for_tuple
+ * Find the list partition for a tuple
+ *
+ * Returns -1 if none found.
+ */
+static int
+list_partition_for_tuple(PartitionKey key, PartitionDesc pdesc,
+ Datum value, bool isnull)
+{
+ int i;
+
+ Assert(pdesc->nparts > 0);
+
+ for (i = 0; i < pdesc->nparts; i++)
+ {
+ int j;
+
+ if (isnull)
+ {
+ if (pdesc->lists[i]->contains_null)
+ return i;
+
+ continue;
+ }
+
+ for (j = 0; j < pdesc->lists[i]->nvalues; j++)
+ {
+ int32 cmpval;
+
+ cmpval = DatumGetInt32(FunctionCall2Coll(&key->partsupfunc[0],
+ key->tcinfo->typcoll[0],
+ pdesc->lists[i]->values[j],
+ value));
+ if (!cmpval)
+ return i;
+ }
+ }
+
+ return -1;
+}
+
+/*
+ * range_partition_for_tuple
+ * Search the range partition for a range key ('values')
+ *
+ * Returns -1 if none found.
+ */
+static int
+range_partition_for_tuple(PartitionKey key, PartitionDesc pdesc, Datum *values)
+{
+ Assert(pdesc->nparts > 0);
+
+ return range_partition_bsearch(key, pdesc, values);
+}
+
+/*
+ * range_partition_bsearch
+ * Workhorse of range_partition_for_tuple
+ */
+static int
+range_partition_bsearch(PartitionKey key, PartitionDesc pdesc,
+ Datum *values)
+{
+ int low, high;
+
+ /* Good ol' bsearch */
+ low = 0;
+ high = pdesc->nparts - 1;
+ while (low <= high)
+ {
+ int idx = (low + high) / 2;
+
+ if (pdesc->rangeuppers[idx]->infinite)
+ {
+ if (rightof(key, values, pdesc->rangelowers[idx]))
+ return idx;
+
+ break;
+ }
+ else if (leftof(key, values, pdesc->rangeuppers[idx]))
+ {
+ if (pdesc->rangelowers[idx]->infinite)
+ return idx;
+
+ if (rightof(key, values, pdesc->rangelowers[idx]))
+ return idx;
+
+ high = idx - 1;
+ continue;
+ }
+
+ low = idx + 1;
+ }
+
+ return -1;
+}
+
+/* Does range key lie to the right of partition bound */
+static bool
+rightof(PartitionKey key, Datum *values, PartitionRangeBound *bound)
+{
+ int32 cmpval = compare_range_keys(key, values, bound->val);
+
+ if (!cmpval)
+ return bound->lower ? bound->inclusive : !bound->inclusive;
+
+ return cmpval > 0;
+}
+
+/* Does range key lie to the left of partition bound */
+static bool
+leftof(PartitionKey key, Datum *values, PartitionRangeBound *bound)
+{
+ int32 cmpval = compare_range_keys(key, values, bound->val);
+
+ if (!cmpval)
+ return !bound->lower ? bound->inclusive : !bound->inclusive;
+
+ return cmpval < 0;
+}
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 14e81de..0b788b1 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -161,6 +161,10 @@ typedef struct CopyStateData
ExprState **defexprs; /* array of default att expressions */
bool volatile_defexprs; /* is any of defexprs volatile? */
List *range_table;
+ PartitionDescNode pdnode; /* partition descriptor node tree */
+ ResultRelInfo *partitions;
+ TupleConversionMap **partition_tupconv_maps;
+ int num_partitions;
/*
* These variables are used to reduce overhead in textual COPY FROM.
@@ -1364,6 +1368,52 @@ BeginCopy(bool is_from,
(errcode(ERRCODE_UNDEFINED_COLUMN),
errmsg("table \"%s\" does not have OIDs",
RelationGetRelationName(cstate->rel))));
+
+ /* Initialize state for CopyFrom tuple routing */
+ if (is_from && rel->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+ {
+ List *leaf_part_oids;
+ ListCell *cell;
+ int i;
+ int num_leaf_parts;
+ ResultRelInfo *leaf_rel_rri;
+
+ cstate->pdnode = RelationGetPartitionDescNode(rel);
+ leaf_part_oids = get_leaf_partition_oids(cstate->pdnode);
+ num_leaf_parts = list_length(leaf_part_oids);
+
+ cstate->num_partitions = num_leaf_parts;
+ cstate->partitions = (ResultRelInfo *)
+ palloc0(num_leaf_parts * sizeof(ResultRelInfo));
+ cstate->partition_tupconv_maps = (TupleConversionMap **)
+ palloc0(num_leaf_parts * sizeof(TupleConversionMap *));
+
+ leaf_rel_rri = cstate->partitions;
+ i = 0;
+ foreach(cell, leaf_part_oids)
+ {
+ Relation leaf_rel;
+
+ leaf_rel = heap_open(lfirst_oid(cell), RowExclusiveLock);
+ InitResultRelInfo(leaf_rel_rri,
+ leaf_rel,
+ 1, /* dummy */
+ false, /* no need for partition check */
+ 0);
+
+ /* Open partition indices */
+ ExecOpenIndices(leaf_rel_rri, false);
+
+ if (!equalTupleDescs(tupDesc, RelationGetDescr(leaf_rel)))
+ cstate->partition_tupconv_maps[i] =
+ convert_tuples_by_name(tupDesc,
+ RelationGetDescr(leaf_rel),
+ gettext_noop("could not convert row type"));
+
+ leaf_rel_rri++;
+ i++;
+ }
+ }
}
else
{
@@ -1659,6 +1709,8 @@ ClosePipeToProgram(CopyState cstate)
static void
EndCopy(CopyState cstate)
{
+ int i;
+
if (cstate->is_program)
{
ClosePipeToProgram(cstate);
@@ -1672,6 +1724,18 @@ EndCopy(CopyState cstate)
cstate->filename)));
}
+ /* Close all partitions and indices thereof */
+ for (i = 0; i < cstate->num_partitions; i++)
+ {
+ ResultRelInfo *resultRelInfo = cstate->partitions + i;
+
+ ExecCloseIndices(resultRelInfo);
+ heap_close(resultRelInfo->ri_RelationDesc, NoLock);
+
+ if (cstate->partition_tupconv_maps[i])
+ pfree(cstate->partition_tupconv_maps[i]);
+ }
+
MemoryContextDelete(cstate->copycontext);
pfree(cstate);
}
@@ -2216,6 +2280,7 @@ CopyFrom(CopyState cstate)
Datum *values;
bool *nulls;
ResultRelInfo *resultRelInfo;
+ ResultRelInfo *saved_resultRelInfo = NULL;
EState *estate = CreateExecutorState(); /* for ExecConstraints() */
ExprContext *econtext;
TupleTableSlot *myslot;
@@ -2236,7 +2301,8 @@ CopyFrom(CopyState cstate)
Assert(cstate->rel);
- if (cstate->rel->rd_rel->relkind != RELKIND_RELATION)
+ if (cstate->rel->rd_rel->relkind != RELKIND_RELATION &&
+ cstate->rel->rd_rel->relkind != RELKIND_PARTITIONED_REL)
{
if (cstate->rel->rd_rel->relkind == RELKIND_VIEW)
ereport(ERROR,
@@ -2344,6 +2410,7 @@ CopyFrom(CopyState cstate)
InitResultRelInfo(resultRelInfo,
cstate->rel,
1, /* dummy rangetable index */
+ true, /* do load partition check expression */
0);
ExecOpenIndices(resultRelInfo, false);
@@ -2371,6 +2438,7 @@ CopyFrom(CopyState cstate)
if ((resultRelInfo->ri_TrigDesc != NULL &&
(resultRelInfo->ri_TrigDesc->trig_insert_before_row ||
resultRelInfo->ri_TrigDesc->trig_insert_instead_row)) ||
+ cstate->pdnode != NULL ||
cstate->volatile_defexprs)
{
useHeapMultiInsert = false;
@@ -2395,7 +2463,11 @@ CopyFrom(CopyState cstate)
values = (Datum *) palloc(tupDesc->natts * sizeof(Datum));
nulls = (bool *) palloc(tupDesc->natts * sizeof(bool));
- bistate = GetBulkInsertState();
+ if (useHeapMultiInsert)
+ bistate = GetBulkInsertState();
+ else
+ bistate = NULL;
+
econtext = GetPerTupleExprContext(estate);
/* Set up callback to identify error line number */
@@ -2447,6 +2519,31 @@ CopyFrom(CopyState cstate)
slot = myslot;
ExecStoreTuple(tuple, slot, InvalidBuffer, false);
+ /* Determine the partition */
+ saved_resultRelInfo = resultRelInfo;
+ if (cstate->pdnode)
+ {
+ int i_leaf_partition;
+ TupleConversionMap *map;
+
+ econtext->ecxt_scantuple = slot;
+ i_leaf_partition = ExecFindPartition(resultRelInfo,
+ cstate->pdnode,
+ slot,
+ estate);
+ Assert(i_leaf_partition >= 0 &&
+ i_leaf_partition < cstate->num_partitions);
+
+ resultRelInfo = cstate->partitions + i_leaf_partition;
+ estate->es_result_relation_info = resultRelInfo;
+
+ map = cstate->partition_tupconv_maps[i_leaf_partition];
+ if (map)
+ tuple = do_convert_tuple(tuple, map);
+
+ tuple->t_tableOid = RelationGetRelid(resultRelInfo->ri_RelationDesc);
+ }
+
skip_tuple = false;
/* BEFORE ROW INSERT Triggers */
@@ -2497,7 +2594,8 @@ CopyFrom(CopyState cstate)
List *recheckIndexes = NIL;
/* OK, store the tuple and create index entries for it */
- heap_insert(cstate->rel, tuple, mycid, hi_options, bistate);
+ heap_insert(resultRelInfo->ri_RelationDesc,
+ tuple, mycid, hi_options, bistate);
if (resultRelInfo->ri_NumIndices > 0)
recheckIndexes = ExecInsertIndexTuples(slot, &(tuple->t_self),
@@ -2517,6 +2615,12 @@ CopyFrom(CopyState cstate)
* tuples inserted by an INSERT command.
*/
processed++;
+
+ if (saved_resultRelInfo)
+ {
+ resultRelInfo = saved_resultRelInfo;
+ estate->es_result_relation_info = resultRelInfo;
+ }
}
}
@@ -2530,7 +2634,8 @@ CopyFrom(CopyState cstate)
/* Done, clean up */
error_context_stack = errcallback.previous;
- FreeBulkInsertState(bistate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
MemoryContextSwitchTo(oldcontext);
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index b5a1136..bf76013 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -1242,6 +1242,7 @@ ExecuteTruncate(TruncateStmt *stmt)
InitResultRelInfo(resultRelInfo,
rel,
0, /* dummy rangetable index */
+ false,
0);
resultRelInfo++;
}
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index f7a23d9..0bb3fb7 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -826,6 +826,7 @@ InitPlan(QueryDesc *queryDesc, int eflags)
InitResultRelInfo(resultRelInfo,
resultRelation,
resultRelationIndex,
+ true,
estate->es_instrument);
resultRelInfo++;
}
@@ -1215,6 +1216,7 @@ void
InitResultRelInfo(ResultRelInfo *resultRelInfo,
Relation resultRelationDesc,
Index resultRelationIndex,
+ bool load_partition_check,
int instrument_options)
{
MemSet(resultRelInfo, 0, sizeof(ResultRelInfo));
@@ -1252,7 +1254,8 @@ InitResultRelInfo(ResultRelInfo *resultRelInfo,
resultRelInfo->ri_ConstraintExprs = NULL;
resultRelInfo->ri_junkFilter = NULL;
resultRelInfo->ri_projectReturning = NULL;
- resultRelInfo->ri_PartitionCheck = (Expr *)
+ if (load_partition_check)
+ resultRelInfo->ri_PartitionCheck = (Expr *)
RelationGetPartitionCheckExpr(resultRelationDesc);
}
@@ -1316,6 +1319,7 @@ ExecGetTriggerResultRel(EState *estate, Oid relid)
InitResultRelInfo(rInfo,
rel,
0, /* dummy rangetable index */
+ true,
estate->es_instrument);
estate->es_trig_target_relations =
lappend(estate->es_trig_target_relations, rInfo);
@@ -2988,3 +2992,41 @@ EvalPlanQualEnd(EPQState *epqstate)
epqstate->planstate = NULL;
epqstate->origslot = NULL;
}
+
+int
+ExecFindPartition(ResultRelInfo *resultRelInfo, PartitionDescNode pdnode,
+ TupleTableSlot *slot, EState *estate)
+{
+ int i_leaf_partition;
+ Oid failed_at;
+
+ i_leaf_partition = get_partition_for_tuple(pdnode, slot, estate,
+ &failed_at);
+
+ if (i_leaf_partition < 0)
+ {
+ Relation rel = resultRelInfo->ri_RelationDesc;
+ char *val_desc;
+ Bitmapset *insertedCols,
+ *updatedCols,
+ *modifiedCols;
+ TupleDesc tupDesc = RelationGetDescr(rel);
+
+ insertedCols = GetInsertedColumns(resultRelInfo, estate);
+ updatedCols = GetUpdatedColumns(resultRelInfo, estate);
+ modifiedCols = bms_union(insertedCols, updatedCols);
+ val_desc = ExecBuildSlotValueDescription(RelationGetRelid(rel),
+ slot,
+ tupDesc,
+ modifiedCols,
+ 64);
+ Assert(OidIsValid(failed_at));
+ ereport(ERROR,
+ (errcode(ERRCODE_CHECK_VIOLATION),
+ errmsg("no partition of relation \"%s\" found for row",
+ get_rel_name(failed_at)),
+ val_desc ? errdetail("Failing row contains %s.", val_desc) : 0));
+ }
+
+ return i_leaf_partition;
+}
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 6ecb6be..581112d 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -243,6 +243,7 @@ ExecInsert(ModifyTableState *mtstate,
{
HeapTuple tuple;
ResultRelInfo *resultRelInfo;
+ ResultRelInfo *saved_resultRelInfo = NULL;
Relation resultRelationDesc;
Oid newId;
List *recheckIndexes = NIL;
@@ -257,6 +258,31 @@ ExecInsert(ModifyTableState *mtstate,
* get information on the (current) result relation
*/
resultRelInfo = estate->es_result_relation_info;
+
+ saved_resultRelInfo = resultRelInfo;
+
+ if (mtstate->mt_partition_node)
+ {
+ int i_leaf_partition;
+ ExprContext *econtext = GetPerTupleExprContext(estate);
+ TupleConversionMap *map;
+
+ econtext->ecxt_scantuple = slot;
+ i_leaf_partition = ExecFindPartition(resultRelInfo,
+ mtstate->mt_partition_node,
+ slot,
+ estate);
+ Assert(i_leaf_partition >= 0 &&
+ i_leaf_partition < mtstate->mt_num_partitions);
+
+ resultRelInfo = mtstate->mt_partitions + i_leaf_partition;
+ estate->es_result_relation_info = resultRelInfo;
+
+ map = mtstate->mt_partition_tupconv_maps[i_leaf_partition];
+ if (map)
+ tuple = do_convert_tuple(tuple, map);
+ }
+
resultRelationDesc = resultRelInfo->ri_RelationDesc;
/*
@@ -496,6 +522,12 @@ ExecInsert(ModifyTableState *mtstate,
list_free(recheckIndexes);
+ if (saved_resultRelInfo)
+ {
+ resultRelInfo = saved_resultRelInfo;
+ estate->es_result_relation_info = resultRelInfo;
+ }
+
/*
* Check any WITH CHECK OPTION constraints from parent views. We are
* required to do this after testing all constraints and uniqueness
@@ -1550,6 +1582,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
Plan *subplan;
ListCell *l;
int i;
+ Relation rel;
/* check for unsupported flags */
Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));
@@ -1640,6 +1673,57 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
estate->es_result_relation_info = saved_resultRelInfo;
+ /* Build state for INSERT tuple routing */
+ rel = mtstate->resultRelInfo->ri_RelationDesc;
+ if (operation == CMD_INSERT && rel->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+ {
+ int i,
+ num_leaf_parts;
+ List *leaf_part_oids;
+ ListCell *cell;
+ ResultRelInfo *leaf_rel_rri;
+
+ mtstate->mt_partition_node = RelationGetPartitionDescNode(rel);
+ leaf_part_oids = get_leaf_partition_oids(mtstate->mt_partition_node);
+ num_leaf_parts = list_length(leaf_part_oids);
+
+ mtstate->mt_num_partitions = num_leaf_parts;
+ mtstate->mt_partitions = (ResultRelInfo *)
+ palloc0(num_leaf_parts * sizeof(ResultRelInfo));
+ mtstate->mt_partition_tupconv_maps = (TupleConversionMap **)
+ palloc0(num_leaf_parts * sizeof(TupleConversionMap *));
+
+ leaf_rel_rri = mtstate->mt_partitions;
+ i = 0;
+ foreach(cell, leaf_part_oids)
+ {
+ Relation leaf_rel;
+
+ leaf_rel = heap_open(lfirst_oid(cell), RowExclusiveLock);
+ InitResultRelInfo(leaf_rel_rri,
+ leaf_rel,
+ 1, /* dummy */
+ false, /* no need for partition checks */
+ eflags);
+
+ /* Open partition indices (note: ON CONFLICT unsupported)*/
+ if (leaf_rel_rri->ri_RelationDesc->rd_rel->relhasindex &&
+ operation != CMD_DELETE &&
+ leaf_rel_rri->ri_IndexRelationDescs == NULL)
+ ExecOpenIndices(leaf_rel_rri, false);
+
+ if (!equalTupleDescs(RelationGetDescr(rel),
+ RelationGetDescr(leaf_rel)))
+ mtstate->mt_partition_tupconv_maps[i] =
+ convert_tuples_by_name(RelationGetDescr(rel),
+ RelationGetDescr(leaf_rel),
+ gettext_noop("could not convert row type"));
+
+ leaf_rel_rri++;
+ i++;
+ }
+ }
+
/*
* Initialize any WITH CHECK OPTION constraints if needed.
*/
@@ -1957,6 +2041,18 @@ ExecEndModifyTable(ModifyTableState *node)
resultRelInfo);
}
+ /* Close all partitions and indices thereof */
+ for (i = 0; i < node->mt_num_partitions; i++)
+ {
+ ResultRelInfo *resultRelInfo = node->mt_partitions + i;
+
+ ExecCloseIndices(resultRelInfo);
+ heap_close(resultRelInfo->ri_RelationDesc, NoLock);
+
+ if (node->mt_partition_tupconv_maps[i])
+ pfree(node->mt_partition_tupconv_maps[i]);
+ }
+
/*
* Free the exprcontext
*/
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index f55129a..802cd68 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -755,8 +755,16 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
/* Process ON CONFLICT, if any. */
if (stmt->onConflictClause)
+ {
+ /* Bail out if target relation is partitioned table */
+ if (relid_is_partitioned(pstate->p_target_rangetblentry->relid))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("ON CONFLICT clause is not supported with partitioned tables")));
+
qry->onConflict = transformOnConflictClause(pstate,
stmt->onConflictClause);
+ }
/*
* If we have a RETURNING clause, we need to add the target relation to
diff --git a/src/include/catalog/partition.h b/src/include/catalog/partition.h
index 7d4bf2f..c987d63 100644
--- a/src/include/catalog/partition.h
+++ b/src/include/catalog/partition.h
@@ -14,6 +14,8 @@
#define PARTITION_H
#include "fmgr.h"
+#include "executor/tuptable.h"
+#include "nodes/execnodes.h"
#include "parser/parse_node.h"
#include "utils/relcache.h"
@@ -85,6 +87,22 @@ typedef struct PartitionDescData
typedef struct PartitionDescData *PartitionDesc;
/*
+ * PartitionKeyExecInfo
+ *
+ * This struct holds the information needed to extract partition
+ * column values from a heap tuple.
+ *
+ * Key copy of the rd_partkey of rel
+ * ExpressionState exec state for expressions, or NIL if none
+ */
+typedef struct PartitionKeyExecInfo
+{
+ NodeTag type;
+ PartitionKey pi_Key;
+ List *pi_ExpressionState; /* list of ExprState */
+} PartitionKeyExecInfo;
+
+/*
* Partition tree node
*
* pkinfo PartitionKey executor state
@@ -100,6 +118,7 @@ typedef struct PartitionDescData *PartitionDesc;
*/
typedef struct PartitionDescNodeData
{
+ PartitionKeyExecInfo *pkinfo;
PartitionDesc pdesc;
Oid relid;
int index;
@@ -138,4 +157,9 @@ extern Expr *get_check_expr_from_partbound(Relation rel, Relation parent,
Node *bound);
extern List *get_leaf_partition_oids(PartitionDescNode pdnode);
extern PartitionDescNode RelationGetPartitionDescNode(Relation rel);
+
+extern int get_partition_for_tuple(PartitionDescNode pdnode,
+ TupleTableSlot *slot,
+ EState *estate,
+ Oid *failed_at);
#endif /* PARTITION_H */
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index 44fac27..d4e8b35 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -14,6 +14,7 @@
#ifndef EXECUTOR_H
#define EXECUTOR_H
+#include "catalog/partition.h"
#include "executor/execdesc.h"
#include "nodes/parsenodes.h"
@@ -188,6 +189,7 @@ extern void CheckValidResultRel(Relation resultRel, CmdType operation);
extern void InitResultRelInfo(ResultRelInfo *resultRelInfo,
Relation resultRelationDesc,
Index resultRelationIndex,
+ bool load_partition_check,
int instrument_options);
extern ResultRelInfo *ExecGetTriggerResultRel(EState *estate, Oid relid);
extern bool ExecContextForcesOids(PlanState *planstate, bool *hasoids);
@@ -211,6 +213,10 @@ extern void EvalPlanQualSetPlan(EPQState *epqstate,
extern void EvalPlanQualSetTuple(EPQState *epqstate, Index rti,
HeapTuple tuple);
extern HeapTuple EvalPlanQualGetTuple(EPQState *epqstate, Index rti);
+extern int ExecFindPartition(ResultRelInfo *resultRelInfo,
+ PartitionDescNode pdnode,
+ TupleTableSlot *slot,
+ EState *estate);
#define EvalPlanQualSetSlot(epqstate, slot) ((epqstate)->origslot = (slot))
extern void EvalPlanQualFetchRowMarks(EPQState *epqstate);
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 74288e2..f2f535e 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -16,6 +16,7 @@
#include "access/genam.h"
#include "access/heapam.h"
+#include "access/tupconvert.h"
#include "executor/instrument.h"
#include "lib/pairingheap.h"
#include "nodes/params.h"
@@ -1140,6 +1141,15 @@ typedef struct ModifyTableState
* tlist */
TupleTableSlot *mt_conflproj; /* CONFLICT ... SET ... projection
* target */
+ struct PartitionDescNodeData *mt_partition_node;
+ /* Partition descriptor node tree */
+ ResultRelInfo *mt_partitions; /* Per leaf partition target
+ * relations */
+ TupleConversionMap **mt_partition_tupconv_maps;
+ /* Per leaf partition
+ * tuple conversion map */
+ int mt_num_partitions; /* Number of leaf partition target
+ * relations in the above array */
} ModifyTableState;
/* ----------------
--
1.7.1
^ permalink raw reply [nested|flat] 161+ messages in thread
* Re: Declarative partitioning
@ 2016-05-20 15:29 Ildar Musin <i.musin@postgrespro.ru>
parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
0 siblings, 1 reply; 161+ messages in thread
From: Ildar Musin @ 2016-05-20 15:29 UTC (permalink / raw)
To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; Tom Lane <tgl@sss.pgh.pa.us>; +Cc: pgsql-hackers
Hi Amit,
On 20.05.2016 11:37, Amit Langote wrote:
> Perhaps you're already aware but may I also suggest looking at how clauses
> are matched to indexes? For example, consider how
> match_clauses_to_index() in src/backend/optimizer/path/indxpath.c works.
Thanks, I'll take a closer look at it.
> Moreover, instead of pruning partitions in planner prep phase, might it
> not be better to do that when considering paths for the (partitioned) rel?
> IOW, instead of looking at parse->jointree, we should rather be working
> with rel->baserestrictinfo. Although, that would require some revisions
> to how append_rel_list, simple_rel_list, etc. are constructed and
> manipulated in a given planner invocation. Maybe it's time for that...
> Again, you may have already considered these things.
>
Yes, you're right, this is how we did it in pg_pathman extension. But
for this patch it requires further consideration and I'll do it in future!
> Could you try with the attached updated set of patches? I changed
> partition descriptor relcache code to eliminate excessive copying in
> previous versions.
>
> Thanks,
> Amit
I tried your new patch and got following results, which are quite close
to the ones using pointer to PartitionDesc structure (TPS):
# of partitions | single row | single partition
----------------+------------+------------------
100 | 3014 | 1024
1000 | 2964 | 1001
2000 | 2874 | 1000
However I've encountered a problem which is that postgres crashes
occasionally while creating partitions. Here is function that reproduces
this behaviour:
CREATE OR REPLACE FUNCTION fail()
RETURNS void
LANGUAGE plpgsql
AS $$
BEGIN
DROP TABLE IF EXISTS abc CASCADE;
CREATE TABLE abc (id SERIAL NOT NULL, a INT, dt TIMESTAMP) PARTITION BY
RANGE (a);
CREATE INDEX ON abc (a);
CREATE TABLE abc_0 PARTITION OF abc FOR VALUES START (0) END (1000);
CREATE TABLE abc_1 PARTITION OF abc FOR VALUES START (1000) END (2000);
END
$$;
SELECT fail();
It happens not every time but quite often. It doesn't happen if I
execute this commands one by one in psql. Backtrace:
#0 range_overlaps_existing_partition (key=0x7f1097504410,
range_spec=0x1d0f400, pdesc=0x1d32200, with=0x7ffe437ead00) at
partition.c:747
#1 0x000000000054c2a5 in StorePartitionBound (relid=245775,
parentId=245770, bound=0x1d0f400) at partition.c:578
#2 0x000000000061bfc4 in DefineRelation (stmt=0x1d0dfe0, relkind=114
'r', ownerId=10, typaddress=0x0) at tablecmds.c:739
#3 0x00000000007f4473 in ProcessUtilitySlow (parsetree=0x1d1a150,
queryString=0x1d1d940 "CREATE TABLE abc_0 PARTITION OF abc FOR VALUES
START (0) END (1000)", context=PROCESS_UTILITY_QUERY, params=0x0,
dest=0xdb5ca0 <spi_printtupDR>, completionTag=0x7ffe437eb500 "")
at utility.c:983
#4 0x00000000007f425e in standard_ProcessUtility (parsetree=0x1d1a150,
queryString=0x1d1d940 "CREATE TABLE abc_0 PARTITION OF abc FOR VALUES
START (0) END (1000)", context=PROCESS_UTILITY_QUERY, params=0x0,
dest=0xdb5ca0 <spi_printtupDR>,
completionTag=0x7ffe437eb500 "") at utility.c:907
#5 0x00000000007f3354 in ProcessUtility (parsetree=0x1d1a150,
queryString=0x1d1d940 "CREATE TABLE abc_0 PARTITION OF abc FOR VALUES
START (0) END (1000)", context=PROCESS_UTILITY_QUERY, params=0x0,
dest=0xdb5ca0 <spi_printtupDR>, completionTag=0x7ffe437eb500 "")
at utility.c:336
#6 0x000000000069f8b2 in _SPI_execute_plan (plan=0x1d19cf0,
paramLI=0x0, snapshot=0x0, crosscheck_snapshot=0x0, read_only=0 '\000',
fire_triggers=1 '\001', tcount=0) at spi.c:2200
#7 0x000000000069c735 in SPI_execute_plan_with_paramlist
(plan=0x1d19cf0, params=0x0, read_only=0 '\000', tcount=0) at spi.c:450
#8 0x00007f108cc6266f in exec_stmt_execsql (estate=0x7ffe437eb8e0,
stmt=0x1d05318) at pl_exec.c:3517
#9 0x00007f108cc5e5fc in exec_stmt (estate=0x7ffe437eb8e0,
stmt=0x1d05318) at pl_exec.c:1503
#10 0x00007f108cc5e318 in exec_stmts (estate=0x7ffe437eb8e0,
stmts=0x1d04c98) at pl_exec.c:1398
#11 0x00007f108cc5e1af in exec_stmt_block (estate=0x7ffe437eb8e0,
block=0x1d055e0) at pl_exec.c:1336
#12 0x00007f108cc5c35d in plpgsql_exec_function (func=0x1cc2a90,
fcinfo=0x1cf7f50, simple_eval_estate=0x0) at pl_exec.c:434
...
Thanks
--
Ildar Musin
i.musin@postgrespro.ru
^ permalink raw reply [nested|flat] 161+ messages in thread
* Re: Declarative partitioning
@ 2016-05-23 10:05 Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
parent: Ildar Musin <i.musin@postgrespro.ru>
0 siblings, 1 reply; 161+ messages in thread
From: Amit Langote @ 2016-05-23 10:05 UTC (permalink / raw)
To: Ildar Musin <i.musin@postgrespro.ru>; Tom Lane <tgl@sss.pgh.pa.us>; +Cc: pgsql-hackers
Hi Ildar,
On 2016/05/21 0:29, Ildar Musin wrote:
> On 20.05.2016 11:37, Amit Langote wrote:
>> Moreover, instead of pruning partitions in planner prep phase, might it
>> not be better to do that when considering paths for the (partitioned) rel?
>> IOW, instead of looking at parse->jointree, we should rather be working
>> with rel->baserestrictinfo. Although, that would require some revisions
>> to how append_rel_list, simple_rel_list, etc. are constructed and
>> manipulated in a given planner invocation. Maybe it's time for that...
>> Again, you may have already considered these things.
>>
> Yes, you're right, this is how we did it in pg_pathman extension. But for
> this patch it requires further consideration and I'll do it in future!
OK, sure.
>> Could you try with the attached updated set of patches? I changed
>> partition descriptor relcache code to eliminate excessive copying in
>> previous versions.
[...]
> However I've encountered a problem which is that postgres crashes
> occasionally while creating partitions. Here is function that reproduces
> this behaviour:
>
> CREATE OR REPLACE FUNCTION fail()
> RETURNS void
> LANGUAGE plpgsql
> AS $$
> BEGIN
> DROP TABLE IF EXISTS abc CASCADE;
> CREATE TABLE abc (id SERIAL NOT NULL, a INT, dt TIMESTAMP) PARTITION BY
> RANGE (a);
> CREATE INDEX ON abc (a);
> CREATE TABLE abc_0 PARTITION OF abc FOR VALUES START (0) END (1000);
> CREATE TABLE abc_1 PARTITION OF abc FOR VALUES START (1000) END (2000);
> END
> $$;
>
> SELECT fail();
>
> It happens not every time but quite often. It doesn't happen if I execute
> this commands one by one in psql. Backtrace:
>
> #0 range_overlaps_existing_partition (key=0x7f1097504410,
> range_spec=0x1d0f400, pdesc=0x1d32200, with=0x7ffe437ead00) at
> partition.c:747
[...]
I made a mistake in the last version of the patch which caused a relcache
field to be pfree'd unexpectedly. Attached updated patches.
Thanks,
Amit
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Attachments:
[text/x-diff] 0001-Add-syntax-to-specify-partition-key-v5.patch (41.5K, ../../5742D5F5.3020101@lab.ntt.co.jp/2-0001-Add-syntax-to-specify-partition-key-v5.patch)
download | inline diff:
From 936e1c538b22a1d5bc33a5f144bb0c60c180ee42 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Mon, 22 Feb 2016 14:06:33 +0900
Subject: [PATCH 1/7] Add syntax to specify partition key.
This adds the following clause to CREATE TABLE:
PARTITION BY method (col [USING opclass] [, ...])
where 'method' is either RANGE or LIST. 'col' could be either a simple
column reference or a parenthesized expression. 'opclass' is name
of btree operator class to use with the column. If method is LIST,
only one column is allowed, whereas, for RANGE method up to 32 columns
can be specified. If this clause is specified, the following constraints
viz. PARTITION KEY, UNIQUE, FOREIGN KEY, EXCLUSION are not allowed
because they cannot be supported on partitioned tables.
This adds 2 new parser nodes viz. PartitionElem and PartitionBy. The former
is similar to IndexElem and the latter consists of details of partition
key such as the method and list of PartitionElems. The new member partby
of CreateStmt is a pointer to the PartitionBy struct created when PARTITION
BY clause is present.
---
doc/src/sgml/ref/create_table.sgml | 35 +++++
src/backend/commands/indexcmds.c | 4 +-
src/backend/commands/tablecmds.c | 215 ++++++++++++++++++++++++++++
src/backend/nodes/copyfuncs.c | 33 +++++
src/backend/nodes/equalfuncs.c | 28 ++++
src/backend/nodes/outfuncs.c | 27 ++++
src/backend/parser/gram.y | 110 ++++++++++++---
src/backend/parser/parse_agg.c | 11 ++
src/backend/parser/parse_expr.c | 5 +
src/backend/parser/parse_utilcmd.c | 63 ++++++++
src/include/commands/defrem.h | 2 +
src/include/nodes/nodes.h | 2 +
src/include/nodes/parsenodes.h | 35 +++++
src/include/parser/kwlist.h | 1 +
src/include/parser/parse_node.h | 3 +-
src/include/pg_config_manual.h | 5 +
src/test/regress/expected/create_table.out | 79 ++++++++++
src/test/regress/sql/create_table.sql | 68 +++++++++
18 files changed, 704 insertions(+), 22 deletions(-)
diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml
index d1807ed..70a7579 100644
--- a/doc/src/sgml/ref/create_table.sgml
+++ b/doc/src/sgml/ref/create_table.sgml
@@ -28,6 +28,7 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
[, ... ]
] )
[ INHERITS ( <replaceable>parent_table</replaceable> [, ... ] ) ]
+[ PARTITION BY {RANGE | LIST} ( { <replaceable class="parameter">column_name</replaceable> | ( <replaceable class="parameter">expression</replaceable> ) } [ <replaceable class="parameter">opclass</replaceable> ] [, ...] )
[ WITH ( <replaceable class="PARAMETER">storage_parameter</replaceable> [= <replaceable class="PARAMETER">value</replaceable>] [, ... ] ) | WITH OIDS | WITHOUT OIDS ]
[ ON COMMIT { PRESERVE ROWS | DELETE ROWS | DROP } ]
[ TABLESPACE <replaceable class="PARAMETER">tablespace_name</replaceable> ]
@@ -38,6 +39,7 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
| <replaceable>table_constraint</replaceable> }
[, ... ]
) ]
+[ PARTITION BY {RANGE | LIST} ( { <replaceable class="parameter">column_name</replaceable> | ( <replaceable class="parameter">expression</replaceable> ) } [ <replaceable class="parameter">opclass</replaceable> ] [, ...] )
[ WITH ( <replaceable class="PARAMETER">storage_parameter</replaceable> [= <replaceable class="PARAMETER">value</replaceable>] [, ... ] ) | WITH OIDS | WITHOUT OIDS ]
[ ON COMMIT { PRESERVE ROWS | DELETE ROWS | DROP } ]
[ TABLESPACE <replaceable class="PARAMETER">tablespace_name</replaceable> ]
@@ -314,6 +316,39 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
</varlistentry>
<varlistentry>
+ <term><literal>PARTITION BY {RANGE | LIST} ( { <replaceable class="parameter">column_name</replaceable> | ( <replaceable class="parameter">expression</replaceable> ) } [ <replaceable class="parameter">opclass</replaceable> ] [, ...] ) </literal></term>
+ <listitem>
+ <para>
+ The optional <literal>PARTITION BY</> clause specifies a method of
+ partitioning the table and the corresponding partition key. Table
+ thus created is called <firstterm>partitioned</firstterm> table. Key
+ consists of an ordered list of column names and/or expressions when
+ using the <literal>RANGE</> method, whereas only a single column or
+ expression can be specified when using the <literal>LIST</> method.
+ The type of a key column or an expresion must have an associated
+ btree operator class or one must be specified along with the column
+ or the expression.
+ </para>
+
+ <para>
+ A partitioned table is divided into sub-tables (called partitions), which
+ in turn, are created using separate <literal>CREATE TABLE</> commands.
+ The table itself is empty. A data row inserted into the table is mapped
+ to and stored in one of the partitions (if one exists) based on the
+ values of columns and/or expressions in the partition key and partition
+ rules associated with the partitions.
+ </para>
+
+ <para>
+ Currently, there are following limitations on definition of partitioned
+ tables: one cannot specify any UNIQUE, PRIMARY KEY, EXCLUDE and/or
+ FOREIGN KEY constraints.
+ </para>
+
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><literal>LIKE <replaceable>source_table</replaceable> [ <replaceable>like_option</replaceable> ... ]</literal></term>
<listitem>
<para>
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index d14d540..4ef6b80 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -69,8 +69,6 @@ static void ComputeIndexAttrs(IndexInfo *indexInfo,
char *accessMethodName, Oid accessMethodId,
bool amcanorder,
bool isconstraint);
-static Oid GetIndexOpClass(List *opclass, Oid attrType,
- char *accessMethodName, Oid accessMethodId);
static char *ChooseIndexName(const char *tabname, Oid namespaceId,
List *colnames, List *exclusionOpNames,
bool primary, bool isconstraint);
@@ -1256,7 +1254,7 @@ ComputeIndexAttrs(IndexInfo *indexInfo,
/*
* Resolve possibly-defaulted operator class specification
*/
-static Oid
+Oid
GetIndexOpClass(List *opclass, Oid attrType,
char *accessMethodName, Oid accessMethodId)
{
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 86e9814..d03147d 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -433,6 +433,11 @@ static void RangeVarCallbackForDropRelation(const RangeVar *rel, Oid relOid,
Oid oldRelOid, void *arg);
static void RangeVarCallbackForAlterRelation(const RangeVar *rv, Oid relid,
Oid oldrelid, void *arg);
+static PartitionBy *transformPartitionBy(Relation rel, PartitionBy *partitionby);
+static void ComputePartitionAttrs(Oid relid, List *partParams,
+ AttrNumber *partattrs,
+ List **partexprs,
+ Oid *partoplass);
/* ----------------------------------------------------------------
@@ -710,6 +715,21 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
AddRelationNewConstraints(rel, rawDefaults, stmt->constraints,
true, true, false);
+ /* Process partition key, if any */
+ if (stmt->partby)
+ {
+ PartitionBy *partby;
+ int16 partnatts;
+ AttrNumber partattrs[PARTITION_MAX_KEYS];
+ Oid partopclass[PARTITION_MAX_KEYS];
+ List *partexprs = NIL;
+
+ partnatts = list_length(stmt->partby->partParams);
+ partby = transformPartitionBy(rel, stmt->partby);
+ ComputePartitionAttrs(relationId, stmt->partby->partParams,
+ partattrs, &partexprs, partopclass);
+ }
+
ObjectAddressSet(address, RelationRelationId, relationId);
/*
@@ -12059,3 +12079,198 @@ RangeVarCallbackForAlterRelation(const RangeVar *rv, Oid relid, Oid oldrelid,
ReleaseSysCache(tuple);
}
+
+/*
+ * transformPartitionBy
+ * Transform any expressions present in the partition key
+ */
+static PartitionBy *
+transformPartitionBy(Relation rel, PartitionBy *partitionby)
+{
+ PartitionBy *partby;
+ ParseState *pstate;
+ RangeTblEntry *rte;
+ ListCell *l;
+
+ partby = (PartitionBy *) makeNode(PartitionBy);
+
+ partby->strategy = partitionby->strategy;
+ partby->location = partitionby->location;
+ partby->partParams = NIL;
+
+ /*
+ * Create a dummy ParseState and insert the target relation as its sole
+ * rangetable entry. We need a ParseState for transformExpr.
+ */
+ pstate = make_parsestate(NULL);
+ rte = addRangeTableEntryForRelation(pstate, rel, NULL, false, true);
+ addRTEtoQuery(pstate, rte, true, true, true);
+
+ /* take care of any partition expressions */
+ foreach(l, partitionby->partParams)
+ {
+ ListCell *column;
+ PartitionElem *pelem = (PartitionElem *) lfirst(l);
+
+ /* Check for PARTITION BY ... ON (foo, foo) */
+ foreach(column, partby->partParams)
+ {
+ PartitionElem *pparam = (PartitionElem *) lfirst(column);
+
+ if (pelem->name && pparam->name &&
+ !strcmp(pelem->name, pparam->name))
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_COLUMN),
+ errmsg("column \"%s\" appears twice in partition key", pelem->name),
+ parser_errposition(pstate, pelem->location)));
+ }
+
+ if (pelem->expr)
+ {
+ /* Now do parse transformation of the expression */
+ pelem->expr = transformExpr(pstate, pelem->expr,
+ EXPR_KIND_PARTITION_KEY);
+
+ /* we have to fix its collations too */
+ assign_expr_collations(pstate, pelem->expr);
+
+ /*
+ * transformExpr() should have already rejected subqueries,
+ * aggregates, and window functions, based on the EXPR_KIND_ for
+ * a partition key expression.
+ *
+ * Also reject expressions returning sets; this is for consistency
+ * DefineRelation() will make more checks.
+ */
+ if (expression_returns_set(pelem->expr))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("partition key expression cannot return a set"),
+ parser_errposition(pstate, pelem->location)));
+ }
+
+ partby->partParams = lappend(partby->partParams, pelem);
+ }
+
+ return partby;
+}
+
+/*
+ * ComputePartitionAttrs
+ * Compute per-partition-column information from partParams
+ */
+static void
+ComputePartitionAttrs(Oid relid, List *partParams, AttrNumber *partattrs,
+ List **partexprs, Oid *partopclass)
+{
+ int attn;
+ ListCell *lc;
+
+ attn = 0;
+ foreach(lc, partParams)
+ {
+ PartitionElem *pelem = (PartitionElem *) lfirst(lc);
+ Oid atttype;
+ Oid opclassOid;
+
+ if (pelem->name != NULL)
+ {
+ HeapTuple atttuple;
+ Form_pg_attribute attform;
+
+ atttuple = SearchSysCacheAttName(relid, pelem->name);
+ if (!HeapTupleIsValid(atttuple))
+ {
+ /* difference in error message spellings is historical */
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_COLUMN),
+ errmsg("column \"%s\" named in partition key does not exist",
+ pelem->name)));
+ }
+ attform = (Form_pg_attribute) GETSTRUCT(atttuple);
+
+ if (attform->attnum <= 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_COLUMN),
+ errmsg("cannot use system column \"%s\" in partition key",
+ pelem->name)));
+
+ partattrs[attn] = attform->attnum;
+ atttype = attform->atttypid;
+ ReleaseSysCache(atttuple);
+ }
+ else
+ {
+ /* Partition key expression */
+ Node *expr = pelem->expr;
+
+ Assert(expr != NULL);
+ atttype = exprType(expr);
+
+ if (IsA(expr, CollateExpr))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("cannot use COLLATE in partition key expression")));
+
+ if (IsA(expr, Var) &&
+ ((Var *) expr)->varattno != InvalidAttrNumber)
+ {
+ /*
+ * User wrote "(column)" or "(column COLLATE something)".
+ * Treat it like simple attribute anyway.
+ */
+ partattrs[attn] = ((Var *) expr)->varattno;
+ }
+ else
+ {
+ partattrs[attn] = 0; /* marks expression */
+ *partexprs = lappend(*partexprs, expr);
+
+ /*
+ * transformExpr() should have already rejected subqueries,
+ * aggregates, and window functions, based on the EXPR_KIND_
+ * for a partition key expression.
+ */
+
+ /*
+ * An expression using mutable functions is probably wrong even
+ * even to use in a partition key
+ */
+ expr = (Node *) expression_planner((Expr *) expr);
+
+ if (IsA(expr, Const))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("cannot use a constant expression as partition key")));
+
+ if (contain_mutable_functions(expr))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("functions in partition key expression must be marked IMMUTABLE")));
+ }
+ }
+
+ /*
+ * Identify the opclass to use. At the moment, we use "btree" operators
+ * that seems enough for list and range partitioning.
+ */
+ if (!pelem->opclass)
+ {
+ opclassOid = GetDefaultOpClass(atttype, BTREE_AM_OID);
+
+ if (!OidIsValid(opclassOid))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("data type %s has no default btree operator class",
+ format_type_be(atttype)),
+ errhint("You must specify an existing btree operator class or define one for the type.")));
+ }
+ else
+ opclassOid = GetIndexOpClass(pelem->opclass,
+ atttype,
+ "btree",
+ BTREE_AM_OID);
+
+ partopclass[attn++] = opclassOid;
+ }
+}
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 20e38f0..968a3f4 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3001,6 +3001,7 @@ CopyCreateStmtFields(const CreateStmt *from, CreateStmt *newnode)
COPY_NODE_FIELD(relation);
COPY_NODE_FIELD(tableElts);
COPY_NODE_FIELD(inhRelations);
+ COPY_NODE_FIELD(partby);
COPY_NODE_FIELD(ofTypename);
COPY_NODE_FIELD(constraints);
COPY_NODE_FIELD(options);
@@ -4156,6 +4157,32 @@ _copyAlterPolicyStmt(const AlterPolicyStmt *from)
return newnode;
}
+static PartitionBy *
+_copyPartitionBy(const PartitionBy *from)
+{
+
+ PartitionBy *newnode = makeNode(PartitionBy);
+
+ COPY_SCALAR_FIELD(strategy);
+ COPY_NODE_FIELD(partParams);
+ COPY_LOCATION_FIELD(location);
+
+ return newnode;
+}
+
+static PartitionElem *
+_copyPartitionElem(const PartitionElem *from)
+{
+ PartitionElem *newnode = makeNode(PartitionElem);
+
+ COPY_STRING_FIELD(name);
+ COPY_NODE_FIELD(expr);
+ COPY_NODE_FIELD(opclass);
+ COPY_LOCATION_FIELD(location);
+
+ return newnode;
+}
+
/* ****************************************************************
* pg_list.h copy functions
* ****************************************************************
@@ -5049,6 +5076,12 @@ copyObject(const void *from)
case T_RoleSpec:
retval = _copyRoleSpec(from);
break;
+ case T_PartitionBy:
+ retval = _copyPartitionBy(from);
+ break;
+ case T_PartitionElem:
+ retval = _copyPartitionElem(from);
+ break;
default:
elog(ERROR, "unrecognized node type: %d", (int) nodeTag(from));
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index c5ccc42..56325fe 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1156,6 +1156,7 @@ _equalCreateStmt(const CreateStmt *a, const CreateStmt *b)
COMPARE_NODE_FIELD(relation);
COMPARE_NODE_FIELD(tableElts);
COMPARE_NODE_FIELD(inhRelations);
+ COMPARE_NODE_FIELD(partby);
COMPARE_NODE_FIELD(ofTypename);
COMPARE_NODE_FIELD(constraints);
COMPARE_NODE_FIELD(options);
@@ -2619,6 +2620,27 @@ _equalRoleSpec(const RoleSpec *a, const RoleSpec *b)
return true;
}
+static bool
+_equalPartitionBy(const PartitionBy *a, const PartitionBy *b)
+{
+ COMPARE_SCALAR_FIELD(strategy);
+ COMPARE_NODE_FIELD(partParams);
+ COMPARE_LOCATION_FIELD(location);
+
+ return true;
+}
+
+static bool
+_equalPartitionElem(const PartitionElem *a, const PartitionElem *b)
+{
+ COMPARE_STRING_FIELD(name);
+ COMPARE_NODE_FIELD(expr);
+ COMPARE_NODE_FIELD(opclass);
+ COMPARE_LOCATION_FIELD(location);
+
+ return true;
+}
+
/*
* Stuff from pg_list.h
*/
@@ -3369,6 +3391,12 @@ equal(const void *a, const void *b)
case T_RoleSpec:
retval = _equalRoleSpec(a, b);
break;
+ case T_PartitionBy:
+ retval = _equalPartitionBy(a, b);
+ break;
+ case T_PartitionElem:
+ retval = _equalPartitionElem(a, b);
+ break;
default:
elog(ERROR, "unrecognized node type: %d",
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index c2f0e0f..5623471 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2369,6 +2369,7 @@ _outCreateStmtInfo(StringInfo str, const CreateStmt *node)
WRITE_NODE_FIELD(relation);
WRITE_NODE_FIELD(tableElts);
WRITE_NODE_FIELD(inhRelations);
+ WRITE_NODE_FIELD(partby);
WRITE_NODE_FIELD(ofTypename);
WRITE_NODE_FIELD(constraints);
WRITE_NODE_FIELD(options);
@@ -3217,6 +3218,26 @@ _outConstraint(StringInfo str, const Constraint *node)
}
}
+static void
+_outPartitionBy(StringInfo str, const PartitionBy *node)
+{
+ WRITE_NODE_TYPE("PARTITIONBY");
+
+ WRITE_CHAR_FIELD(strategy);
+ WRITE_NODE_FIELD(partParams);
+ WRITE_LOCATION_FIELD(location);
+}
+
+static void
+_outPartitionElem(StringInfo str, const PartitionElem *node)
+{
+ WRITE_NODE_TYPE("PARTITIONELEM");
+
+ WRITE_STRING_FIELD(name);
+ WRITE_NODE_FIELD(expr);
+ WRITE_NODE_FIELD(opclass);
+ WRITE_LOCATION_FIELD(location);
+}
/*
* outNode -
@@ -3796,6 +3817,12 @@ outNode(StringInfo str, const void *obj)
case T_XmlSerialize:
_outXmlSerialize(str, obj);
break;
+ case T_PartitionBy:
+ _outPartitionBy(str, obj);
+ break;
+ case T_PartitionElem:
+ _outPartitionElem(str, obj);
+ break;
default:
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 18ec5f0..0607439 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -227,6 +227,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
struct ImportQual *importqual;
InsertStmt *istmt;
VariableSetStmt *vsetstmt;
+ PartitionElem *partelem;
+ PartitionBy *partby;
}
%type <node> stmt schema_stmt
@@ -539,6 +541,9 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
opt_frame_clause frame_extent frame_bound
%type <str> opt_existing_window_name
%type <boolean> opt_if_not_exists
+%type <partby> PartitionBy OptPartitionBy
+%type <partelem> part_elem
+%type <list> part_params
/*
* Non-keyword token types. These are hard-wired into the "flex" lexer.
@@ -603,7 +608,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
KEY
LABEL LANGUAGE LARGE_P LAST_P LATERAL_P
- LEADING LEAKPROOF LEAST LEFT LEVEL LIKE LIMIT LISTEN LOAD LOCAL
+ LEADING LEAKPROOF LEAST LEFT LEVEL LIKE LIMIT LIST LISTEN LOAD LOCAL
LOCALTIME LOCALTIMESTAMP LOCATION LOCK_P LOCKED LOGGED
MAPPING MATCH MATERIALIZED MAXVALUE METHOD MINUTE_P MINVALUE MODE MONTH_P MOVE
@@ -2808,69 +2813,75 @@ copy_generic_opt_arg_list_item:
*****************************************************************************/
CreateStmt: CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
- OptInherit OptWith OnCommitOption OptTableSpace
+ OptInherit OptPartitionBy OptWith OnCommitOption OptTableSpace
{
CreateStmt *n = makeNode(CreateStmt);
$4->relpersistence = $2;
n->relation = $4;
n->tableElts = $6;
n->inhRelations = $8;
+ n->partby = $9;
n->ofTypename = NULL;
n->constraints = NIL;
- n->options = $9;
- n->oncommit = $10;
- n->tablespacename = $11;
+ n->options = $10;
+ n->oncommit = $11;
+ n->tablespacename = $12;
n->if_not_exists = false;
$$ = (Node *)n;
}
| CREATE OptTemp TABLE IF_P NOT EXISTS qualified_name '('
- OptTableElementList ')' OptInherit OptWith OnCommitOption
- OptTableSpace
+ OptTableElementList ')' OptInherit OptPartitionBy OptWith
+ OnCommitOption OptTableSpace
{
CreateStmt *n = makeNode(CreateStmt);
$7->relpersistence = $2;
n->relation = $7;
n->tableElts = $9;
n->inhRelations = $11;
+ n->partby = $12;
n->ofTypename = NULL;
n->constraints = NIL;
- n->options = $12;
- n->oncommit = $13;
- n->tablespacename = $14;
+ n->options = $13;
+ n->oncommit = $14;
+ n->tablespacename = $15;
n->if_not_exists = true;
$$ = (Node *)n;
}
| CREATE OptTemp TABLE qualified_name OF any_name
- OptTypedTableElementList OptWith OnCommitOption OptTableSpace
+ OptTypedTableElementList OptPartitionBy OptWith OnCommitOption
+ OptTableSpace
{
CreateStmt *n = makeNode(CreateStmt);
$4->relpersistence = $2;
n->relation = $4;
n->tableElts = $7;
n->inhRelations = NIL;
+ n->partby = $8;
n->ofTypename = makeTypeNameFromNameList($6);
n->ofTypename->location = @6;
n->constraints = NIL;
- n->options = $8;
- n->oncommit = $9;
- n->tablespacename = $10;
+ n->options = $9;
+ n->oncommit = $10;
+ n->tablespacename = $11;
n->if_not_exists = false;
$$ = (Node *)n;
}
| CREATE OptTemp TABLE IF_P NOT EXISTS qualified_name OF any_name
- OptTypedTableElementList OptWith OnCommitOption OptTableSpace
+ OptTypedTableElementList OptPartitionBy OptWith OnCommitOption
+ OptTableSpace
{
CreateStmt *n = makeNode(CreateStmt);
$7->relpersistence = $2;
n->relation = $7;
n->tableElts = $10;
n->inhRelations = NIL;
+ n->partby = $11;
n->ofTypename = makeTypeNameFromNameList($9);
n->ofTypename->location = @9;
n->constraints = NIL;
- n->options = $11;
- n->oncommit = $12;
- n->tablespacename = $13;
+ n->options = $12;
+ n->oncommit = $13;
+ n->tablespacename = $14;
n->if_not_exists = true;
$$ = (Node *)n;
}
@@ -3415,6 +3426,68 @@ OptInherit: INHERITS '(' qualified_name_list ')' { $$ = $3; }
| /*EMPTY*/ { $$ = NIL; }
;
+/* Optional partition key definition */
+OptPartitionBy: PartitionBy { $$ = $1; }
+ | /*EMPTY*/ { $$ = NULL; }
+ ;
+
+PartitionBy: PARTITION BY RANGE '(' part_params ')'
+ {
+ PartitionBy *n = makeNode(PartitionBy);
+
+ n->strategy = PARTITION_STRAT_RANGE;
+ n->partParams = $5;
+ n->location = @1;
+
+ $$ = n;
+ }
+ | PARTITION BY LIST '(' part_params ')'
+ {
+ PartitionBy *n = makeNode(PartitionBy);
+
+ n->strategy = PARTITION_STRAT_LIST;
+ n->partParams = $5;
+ n->location = @1;
+
+ $$ = n;
+ }
+ ;
+
+part_params: part_elem { $$ = list_make1($1); }
+ | part_params ',' part_elem { $$ = lappend($1, $3); }
+ ;
+
+part_elem: ColId opt_class
+ {
+ PartitionElem *n = makeNode(PartitionElem);
+
+ n->name = $1;
+ n->expr = NULL;
+ n->opclass = $2;
+ n->location = @1;
+ $$ = n;
+ }
+ | func_expr_windowless opt_class
+ {
+ PartitionElem *n = makeNode(PartitionElem);
+
+ n->name = NULL;
+ n->expr = $1;
+ n->opclass = $2;
+ n->location = @1;
+ $$ = n;
+ }
+ | '(' a_expr ')' opt_class
+ {
+ PartitionElem *n = makeNode(PartitionElem);
+
+ n->name = NULL;
+ n->expr = $2;
+ n->opclass = $4;
+ n->location = @1;
+ $$ = n;
+ }
+ ;
/* WITH (options) is preferred, WITH OIDS and WITHOUT OIDS are legacy forms */
OptWith:
WITH reloptions { $$ = $2; }
@@ -13839,6 +13912,7 @@ unreserved_keyword:
| LAST_P
| LEAKPROOF
| LEVEL
+ | LIST
| LISTEN
| LOAD
| LOCAL
diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c
index 91bfe66..b56fd4f 100644
--- a/src/backend/parser/parse_agg.c
+++ b/src/backend/parser/parse_agg.c
@@ -486,6 +486,14 @@ check_agglevels_and_constraints(ParseState *pstate, Node *expr)
err = _("grouping operations are not allowed in trigger WHEN conditions");
break;
+ case EXPR_KIND_PARTITION_KEY:
+ if (isAgg)
+ err = _("aggregate functions are not allowed in partition key expressions");
+ else
+ err = _("grouping operations are not allowed in partition key expressions");
+
+ break;
+
/*
* There is intentionally no default: case here, so that the
@@ -843,6 +851,9 @@ transformWindowFuncCall(ParseState *pstate, WindowFunc *wfunc,
case EXPR_KIND_TRIGGER_WHEN:
err = _("window functions are not allowed in trigger WHEN conditions");
break;
+ case EXPR_KIND_PARTITION_KEY:
+ err = _("window functions are not allowed in partition key expressions");
+ break;
/*
* There is intentionally no default: case here, so that the
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 8b28516..b53b7f1 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -1723,6 +1723,9 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
case EXPR_KIND_TRIGGER_WHEN:
err = _("cannot use subquery in trigger WHEN condition");
break;
+ case EXPR_KIND_PARTITION_KEY:
+ err = _("cannot use subquery in partition key expressions");
+ break;
/*
* There is intentionally no default: case here, so that the
@@ -3249,6 +3252,8 @@ ParseExprKindName(ParseExprKind exprKind)
return "EXECUTE";
case EXPR_KIND_TRIGGER_WHEN:
return "WHEN";
+ case EXPR_KIND_PARTITION_KEY:
+ return "partition key expression";
/*
* There is intentionally no default: case here, so that the
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 6528494..e267f74 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -87,6 +87,7 @@ typedef struct
List *alist; /* "after list" of things to do after creating
* the table */
IndexStmt *pkey; /* PRIMARY KEY index, if any */
+ bool ispartitioned; /* true if table is partitioned */
} CreateStmtContext;
/* State shared by transformCreateSchemaStmt and its subroutines */
@@ -229,6 +230,7 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
cxt.blist = NIL;
cxt.alist = NIL;
cxt.pkey = NULL;
+ cxt.ispartitioned = stmt->partby != NULL;
/*
* Notice that we allow OIDs here only for plain tables, even though
@@ -247,6 +249,24 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
if (stmt->ofTypename)
transformOfType(&cxt, stmt->ofTypename);
+ if (stmt->partby)
+ {
+ int partnatts = list_length(stmt->partby->partParams);
+
+ if (partnatts > PARTITION_MAX_KEYS)
+ ereport(ERROR,
+ (errcode(ERRCODE_TOO_MANY_COLUMNS),
+ errmsg("cannot use more than %d columns in partition key",
+ PARTITION_MAX_KEYS)));
+
+ if (stmt->partby->strategy == PARTITION_STRAT_LIST &&
+ partnatts > 1)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("cannot use more than one column in partition key"),
+ errdetail("Only one column allowed with list partitioning.")));
+ }
+
/*
* Run through each primary element in the table creation clause. Separate
* column defs from constraints, and do preliminary analysis. We have to
@@ -582,6 +602,12 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
errmsg("primary key constraints are not supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
+ if (cxt->ispartitioned)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("primary key constraints are not supported on partitioned tables"),
+ parser_errposition(cxt->pstate,
+ constraint->location)));
/* FALL THRU */
case CONSTR_UNIQUE:
@@ -591,6 +617,12 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
errmsg("unique constraints are not supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
+ if (cxt->ispartitioned)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("unique constraints are not supported on partitioned tables"),
+ parser_errposition(cxt->pstate,
+ constraint->location)));
if (constraint->keys == NIL)
constraint->keys = list_make1(makeString(column->colname));
cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
@@ -608,6 +640,12 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
errmsg("foreign key constraints are not supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
+ if (cxt->ispartitioned)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("foreign key constraints are not supported on partitioned tables"),
+ parser_errposition(cxt->pstate,
+ constraint->location)));
/*
* Fill in the current attribute's name and throw it into the
@@ -673,6 +711,12 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
errmsg("primary key constraints are not supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
+ if (cxt->ispartitioned)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("primary key constraints are not supported on partitioned tables"),
+ parser_errposition(cxt->pstate,
+ constraint->location)));
cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
break;
@@ -683,6 +727,12 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
errmsg("unique constraints are not supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
+ if (cxt->ispartitioned)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("unique constraints are not supported on partitioned tables"),
+ parser_errposition(cxt->pstate,
+ constraint->location)));
cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
break;
@@ -693,6 +743,12 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
errmsg("exclusion constraints are not supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
+ if (cxt->ispartitioned)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("exclusion constraints are not supported on partitioned tables"),
+ parser_errposition(cxt->pstate,
+ constraint->location)));
cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
break;
@@ -707,6 +763,12 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
errmsg("foreign key constraints are not supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
+ if (cxt->ispartitioned)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("foreign key constraints are not supported on partitioned tables"),
+ parser_errposition(cxt->pstate,
+ constraint->location)));
cxt->fkconstraints = lappend(cxt->fkconstraints, constraint);
break;
@@ -2515,6 +2577,7 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
cxt.blist = NIL;
cxt.alist = NIL;
cxt.pkey = NULL;
+ cxt.ispartitioned = false;
/*
* The only subtypes that currently require parse transformation handling
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index b064eb4..28024f8 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -42,6 +42,8 @@ extern bool CheckIndexCompatible(Oid oldId,
List *attributeList,
List *exclusionOpNames);
extern Oid GetDefaultOpClass(Oid type_id, Oid am_id);
+extern Oid GetIndexOpClass(List *opclass, Oid attrType,
+ char *accessMethodName, Oid accessMethodId);
/* commands/functioncmds.c */
extern ObjectAddress CreateFunction(CreateFunctionStmt *stmt, const char *queryString);
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index ea65f2e..9e582a5 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -452,6 +452,8 @@ typedef enum NodeTag
T_OnConflictClause,
T_CommonTableExpr,
T_RoleSpec,
+ T_PartitionElem,
+ T_PartitionBy,
/*
* TAGS FOR REPLICATION GRAMMAR PARSE NODES (replnodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 714cf15..4f697b8 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -696,6 +696,40 @@ typedef struct XmlSerialize
int location; /* token location, or -1 if unknown */
} XmlSerialize;
+/* Partitioning related definitions */
+
+/*
+ * PartitionElem - a partition key column
+ *
+ * 'name' Name of the table column included in the key
+ * 'expr' Expression node tree of expressional key column
+ * 'opclass' Operator class name associated with the column
+ */
+typedef struct PartitionElem
+{
+ NodeTag type;
+ char *name; /* name of column to partition on, or NULL */
+ Node *expr; /* expression to partition on, or NULL */
+ List *opclass; /* name of desired opclass; NIL = default */
+ int location; /* token location, or -1 if unknown */
+} PartitionElem;
+
+/*
+ * PartitionBy - partition key definition including the strategy
+ *
+ * 'strategy' partition strategy to use (one of the below defined)
+ * 'partParams' List of PartitionElems, one for each key column
+ */
+#define PARTITION_STRAT_LIST 'l'
+#define PARTITION_STRAT_RANGE 'r'
+
+typedef struct PartitionBy
+{
+ NodeTag type;
+ char strategy;
+ List *partParams;
+ int location; /* token location, or -1 if unknown */
+} PartitionBy;
/****************************************************************************
* Nodes for a Query tree
@@ -1750,6 +1784,7 @@ typedef struct CreateStmt
List *tableElts; /* column definitions (list of ColumnDef) */
List *inhRelations; /* relations to inherit from (list of
* inhRelation) */
+ PartitionBy *partby; /* PARTITION BY clause */
TypeName *ofTypename; /* OF typename */
List *constraints; /* constraints (list of Constraint nodes) */
List *options; /* options from WITH clause */
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 17ffef5..40da67a 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -227,6 +227,7 @@ PG_KEYWORD("left", LEFT, TYPE_FUNC_NAME_KEYWORD)
PG_KEYWORD("level", LEVEL, UNRESERVED_KEYWORD)
PG_KEYWORD("like", LIKE, TYPE_FUNC_NAME_KEYWORD)
PG_KEYWORD("limit", LIMIT, RESERVED_KEYWORD)
+PG_KEYWORD("list", LIST, UNRESERVED_KEYWORD)
PG_KEYWORD("listen", LISTEN, UNRESERVED_KEYWORD)
PG_KEYWORD("load", LOAD, UNRESERVED_KEYWORD)
PG_KEYWORD("local", LOCAL, UNRESERVED_KEYWORD)
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index e3e359c..a13c6fb 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -64,7 +64,8 @@ typedef enum ParseExprKind
EXPR_KIND_ALTER_COL_TRANSFORM, /* transform expr in ALTER COLUMN TYPE */
EXPR_KIND_EXECUTE_PARAMETER, /* parameter value in EXECUTE */
EXPR_KIND_TRIGGER_WHEN, /* WHEN condition in CREATE TRIGGER */
- EXPR_KIND_POLICY /* USING or WITH CHECK expr in policy */
+ EXPR_KIND_POLICY, /* USING or WITH CHECK expr in policy */
+ EXPR_KIND_PARTITION_KEY /* partition key expression */
} ParseExprKind;
diff --git a/src/include/pg_config_manual.h b/src/include/pg_config_manual.h
index 8d5a378..850251b 100644
--- a/src/include/pg_config_manual.h
+++ b/src/include/pg_config_manual.h
@@ -46,6 +46,11 @@
#define INDEX_MAX_KEYS 32
/*
+ * Maximum number of columns in a partition key
+ */
+#define PARTITION_MAX_KEYS 32
+
+/*
* Set the upper and lower bounds of sequence values.
*/
#define SEQ_MAXVALUE PG_INT64_MAX
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 41ceb87..aaae9f9 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -253,3 +253,82 @@ DROP TABLE as_select1;
-- check that the oid column is added before the primary key is checked
CREATE TABLE oid_pk (f1 INT, PRIMARY KEY(oid)) WITH OIDS;
DROP TABLE oid_pk;
+--
+-- partitioned tables
+--
+-- cannot use system columns in partition key
+CREATE TABLE fail_oid_pk (
+ a int
+) PARTITION BY RANGE (oid) WITH OIDS; -- fail
+ERROR: cannot use system column "oid" in partition key
+-- cannot use more than 1 column as partition key for list partitioned table
+CREATE TABLE two_col_tab (
+ a1 int,
+ a2 int
+) PARTITION BY LIST (a1, a2); -- fail
+ERROR: cannot use more than one column in partition key
+DETAIL: Only one column allowed with list partitioning.
+-- cannot use more than 32 columns as partition key for range partitioned table
+CREATE TABLE wide_tab (
+ a01 int, a02 int, a03 int, a04 int, a05 int, a06 int, a07 int, a08 int,
+ a09 int, a10 int, a11 int, a12 int, a13 int, a14 int, a15 int, a16 int,
+ a17 int, a18 int, a19 int, a20 int, a21 int, a22 int, a23 int, a24 int,
+ a25 int, a26 int, a27 int, a28 int, a29 int, a30 int, a31 int, a32 int,
+ a33 int
+) PARTITION BY RANGE (a01, a02, a03, a04, a05, a06, a07, a08,
+ a09, a10, a11, a12, a13, a14, a15, a16,
+ a17, a18, a19, a20, a21, a22, a23, a24,
+ a25, a26, a27, a28, a29, a30, a31, a32, a33); -- fail
+ERROR: cannot use more than 32 columns in partition key
+-- Partitioning requires to use columns with data type having a suitable
+-- btree operator class
+CREATE TABLE point_tab (
+ a point
+) PARTITION BY LIST (a); -- fail
+ERROR: data type point has no default btree operator class
+HINT: You must specify an existing btree operator class or define one for the type.
+CREATE TABLE point_tab (
+ a point
+) PARTITION BY LIST (a USING point_ops); -- fail
+ERROR: operator class "point_ops" does not exist for access method "btree"
+CREATE TABLE point_tab (
+ a point
+) PARTITION BY RANGE (a); -- fail
+ERROR: data type point has no default btree operator class
+HINT: You must specify an existing btree operator class or define one for the type.
+CREATE TABLE point_tab (
+ a point
+) PARTITION BY RANGE (a USING point_ops); -- fail
+ERROR: operator class "point_ops" does not exist for access method "btree"
+-- fail to create a partitioned table with PRIMARY KEY constraint
+CREATE TABLE pk_fail (
+ a int PRIMARY KEY
+) PARTITION BY RANGE (a);
+ERROR: primary key constraints are not supported on partitioned tables
+LINE 2: a int PRIMARY KEY
+ ^
+-- fail to create a partitioned table with foreign key constraint
+CREATE TABLE pk_rel(
+ a int PRIMARY KEY
+);
+CREATE TABLE fk_fail (
+ a int REFERENCES pk_rel(a)
+) PARTITION BY RANGE (a);
+ERROR: foreign key constraints are not supported on partitioned tables
+LINE 2: a int REFERENCES pk_rel(a)
+ ^
+DROP TABLE pk_rel;
+-- fail to create a partitioned table with prohibited expressions in key
+CREATE TABLE fail_const_key (
+ a int
+) PARTITION BY RANGE (('a'));
+ERROR: cannot use a constant expression as partition key
+CREATE TABLE fail_agg_in_key (
+ a int
+) PARTITION BY RANGE ((avg(a)));
+ERROR: aggregate functions are not allowed in partition key expressions
+CREATE TABLE fail_window_fun_in_key (
+ a int,
+ b int
+) PARTITION BY RANGE ((avg(a) OVER (PARTITION BY b)));
+ERROR: window functions are not allowed in partition key expressions
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 78bdc8b..df47656 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -269,3 +269,71 @@ DROP TABLE as_select1;
-- check that the oid column is added before the primary key is checked
CREATE TABLE oid_pk (f1 INT, PRIMARY KEY(oid)) WITH OIDS;
DROP TABLE oid_pk;
+
+--
+-- partitioned tables
+--
+
+-- cannot use system columns in partition key
+CREATE TABLE fail_oid_pk (
+ a int
+) PARTITION BY RANGE (oid) WITH OIDS; -- fail
+
+-- cannot use more than 1 column as partition key for list partitioned table
+CREATE TABLE two_col_tab (
+ a1 int,
+ a2 int
+) PARTITION BY LIST (a1, a2); -- fail
+
+-- cannot use more than 32 columns as partition key for range partitioned table
+CREATE TABLE wide_tab (
+ a01 int, a02 int, a03 int, a04 int, a05 int, a06 int, a07 int, a08 int,
+ a09 int, a10 int, a11 int, a12 int, a13 int, a14 int, a15 int, a16 int,
+ a17 int, a18 int, a19 int, a20 int, a21 int, a22 int, a23 int, a24 int,
+ a25 int, a26 int, a27 int, a28 int, a29 int, a30 int, a31 int, a32 int,
+ a33 int
+) PARTITION BY RANGE (a01, a02, a03, a04, a05, a06, a07, a08,
+ a09, a10, a11, a12, a13, a14, a15, a16,
+ a17, a18, a19, a20, a21, a22, a23, a24,
+ a25, a26, a27, a28, a29, a30, a31, a32, a33); -- fail
+
+-- Partitioning requires to use columns with data type having a suitable
+-- btree operator class
+CREATE TABLE point_tab (
+ a point
+) PARTITION BY LIST (a); -- fail
+CREATE TABLE point_tab (
+ a point
+) PARTITION BY LIST (a USING point_ops); -- fail
+CREATE TABLE point_tab (
+ a point
+) PARTITION BY RANGE (a); -- fail
+CREATE TABLE point_tab (
+ a point
+) PARTITION BY RANGE (a USING point_ops); -- fail
+
+-- fail to create a partitioned table with PRIMARY KEY constraint
+CREATE TABLE pk_fail (
+ a int PRIMARY KEY
+) PARTITION BY RANGE (a);
+
+-- fail to create a partitioned table with foreign key constraint
+CREATE TABLE pk_rel(
+ a int PRIMARY KEY
+);
+CREATE TABLE fk_fail (
+ a int REFERENCES pk_rel(a)
+) PARTITION BY RANGE (a);
+DROP TABLE pk_rel;
+
+-- fail to create a partitioned table with prohibited expressions in key
+CREATE TABLE fail_const_key (
+ a int
+) PARTITION BY RANGE (('a'));
+CREATE TABLE fail_agg_in_key (
+ a int
+) PARTITION BY RANGE ((avg(a)));
+CREATE TABLE fail_window_fun_in_key (
+ a int,
+ b int
+) PARTITION BY RANGE ((avg(a) OVER (PARTITION BY b)));
--
1.7.1
[text/x-diff] 0002-Add-a-IGNORE-dependency-type-v5.patch (3.0K, ../../5742D5F5.3020101@lab.ntt.co.jp/3-0002-Add-a-IGNORE-dependency-type-v5.patch)
download | inline diff:
From 2efcd65f5dd1ddb8b06565871661175023092584 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Fri, 13 May 2016 12:10:10 +0900
Subject: [PATCH 2/7] Add a IGNORE dependency type.
This new type of dependency is mainly to be used internally as a signal
that no dependency should be created between the objects in question.
Use this, for example, when there is a possibility that redundant self-
dependencies could be created in certain situations - imagine creation
of dependencies for objects referenced in expressions in the partition
key of a table; it is inevitable that vars of the table in question
would be encountered which must be ignored.
---
src/backend/catalog/dependency.c | 2 ++
src/backend/catalog/pg_depend.c | 3 +++
src/include/catalog/dependency.h | 8 +++++++-
3 files changed, 12 insertions(+), 1 deletions(-)
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index 04d7840..607274d 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -717,6 +717,7 @@ findDependentObjects(const ObjectAddress *object,
getObjectDescription(object));
break;
default:
+ Assert(foundDep->deptype != DEPENDENCY_IGNORE);
elog(ERROR, "unrecognized dependency type '%c' for %s",
foundDep->deptype, getObjectDescription(object));
break;
@@ -813,6 +814,7 @@ findDependentObjects(const ObjectAddress *object,
subflags = 0; /* keep compiler quiet */
break;
default:
+ Assert(foundDep->deptype != DEPENDENCY_IGNORE);
elog(ERROR, "unrecognized dependency type '%c' for %s",
foundDep->deptype, getObjectDescription(object));
subflags = 0; /* keep compiler quiet */
diff --git a/src/backend/catalog/pg_depend.c b/src/backend/catalog/pg_depend.c
index 7a0713e..6e71b44 100644
--- a/src/backend/catalog/pg_depend.c
+++ b/src/backend/catalog/pg_depend.c
@@ -65,6 +65,9 @@ recordMultipleDependencies(const ObjectAddress *depender,
bool nulls[Natts_pg_depend];
Datum values[Natts_pg_depend];
+ if (behavior == DEPENDENCY_IGNORE)
+ return; /* nothing to do */
+
if (nreferenced <= 0)
return; /* nothing to do */
diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h
index 09b36c5..d44edc6 100644
--- a/src/include/catalog/dependency.h
+++ b/src/include/catalog/dependency.h
@@ -67,6 +67,11 @@
* created only during initdb. The fields for the dependent object
* contain zeroes.
*
+ * DEPENDENCY_IGNORE ('g'): like DEPENDENCY_PIN, there is no dependent
+ * object; this type of entry is a signal that no dependency should be
+ * created between the objects in question. However, unlike pin,
+ * dependencies, these never make it to pg_depend.
+ *
* Other dependency flavors may be needed in future.
*/
@@ -77,7 +82,8 @@ typedef enum DependencyType
DEPENDENCY_INTERNAL = 'i',
DEPENDENCY_EXTENSION = 'e',
DEPENDENCY_AUTO_EXTENSION = 'x',
- DEPENDENCY_PIN = 'p'
+ DEPENDENCY_PIN = 'p',
+ DEPENDENCY_IGNORE = 'g'
} DependencyType;
/*
--
1.7.1
[text/x-diff] 0003-Infrastructure-for-creation-of-partitioned-tables-v5.patch (86.8K, ../../5742D5F5.3020101@lab.ntt.co.jp/4-0003-Infrastructure-for-creation-of-partitioned-tables-v5.patch)
download | inline diff:
From e0592095c0f054ecc938129176c25e74a3afa88e Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Mon, 22 Feb 2016 17:38:55 +0900
Subject: [PATCH 3/7] Infrastructure for creation of partitioned tables.
First, this adds a new system catalog pg_partitioned with:
partedrelid oid
partstrat char
partnatts int16
partattrs int2vector
partclass oidvector
partexprs pg_node_tree
There is a unique index on partedrelid and associated syscache PARTEDRELID.
Second, a new relkind RELKIND_PARTITIONED_REL is added to pg_class. Tables
created with CREATE TABLE PARTITION BY are of this relkind. Some special
properties:
* Has a pg_partitioned entry
* Access to them unconditionally overrides specified inheritance option
* Dropping or altering type of columns that are part of the partition key
is disallowed
* Changing inheritance (ALTER TABLE table INHERIT parent) and linking to
a type (ALTER TABLE table OF type) is disallowed
Partition key is represented using struct PartitionKey and built during the
first relcache build of a partitioned relation. It is stored in rd_partkey
using the cache context.
---
doc/src/sgml/catalogs.sgml | 99 ++++++
src/backend/access/common/reloptions.c | 2 +
src/backend/catalog/Makefile | 4 +-
src/backend/catalog/aclchk.c | 2 +
src/backend/catalog/heap.c | 19 +-
src/backend/catalog/objectaddress.c | 10 +-
src/backend/catalog/partition.c | 450 ++++++++++++++++++++++++++++
src/backend/commands/analyze.c | 2 +
src/backend/commands/copy.c | 6 +
src/backend/commands/indexcmds.c | 3 +-
src/backend/commands/lockcmds.c | 2 +-
src/backend/commands/policy.c | 2 +-
src/backend/commands/seclabel.c | 1 +
src/backend/commands/sequence.c | 1 +
src/backend/commands/tablecmds.c | 168 ++++++++++-
src/backend/commands/trigger.c | 7 +-
src/backend/commands/vacuum.c | 1 +
src/backend/executor/execMain.c | 2 +
src/backend/executor/nodeModifyTable.c | 1 +
src/backend/parser/parse_agg.c | 6 +-
src/backend/parser/parse_clause.c | 8 +
src/backend/parser/parse_expr.c | 2 +-
src/backend/parser/parse_relation.c | 6 +-
src/backend/parser/parse_utilcmd.c | 4 +-
src/backend/rewrite/rewriteDefine.c | 1 +
src/backend/rewrite/rewriteHandler.c | 1 +
src/backend/tcop/utility.c | 5 +-
src/backend/utils/adt/ruleutils.c | 147 +++++++++
src/backend/utils/cache/relcache.c | 14 +-
src/backend/utils/cache/syscache.c | 12 +
src/bin/pg_dump/pg_dump.c | 36 ++-
src/bin/pg_dump/pg_dump.h | 1 +
src/bin/psql/describe.c | 61 +++-
src/bin/psql/tab-complete.c | 6 +-
src/include/catalog/indexing.h | 3 +
src/include/catalog/partition.h | 55 ++++
src/include/catalog/pg_class.h | 1 +
src/include/catalog/pg_partitioned.h | 67 ++++
src/include/catalog/pg_proc.h | 2 +
src/include/commands/defrem.h | 4 +-
src/include/utils/builtins.h | 1 +
src/include/utils/rel.h | 8 +
src/include/utils/syscache.h | 1 +
src/test/regress/expected/create_table.out | 30 ++-
src/test/regress/expected/sanity_check.out | 1 +
src/test/regress/sql/create_table.sql | 21 ++
46 files changed, 1228 insertions(+), 58 deletions(-)
create mode 100644 src/backend/catalog/partition.c
create mode 100644 src/include/catalog/partition.h
create mode 100644 src/include/catalog/pg_partitioned.h
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index eac6671..652f779 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -224,6 +224,11 @@
</row>
<row>
+ <entry><link linkend="catalog-pg-partitioned"><structname>pg_partitioned</structname></link></entry>
+ <entry>information about partitioned tables, including the partition key</entry>
+ </row>
+
+ <row>
<entry><link linkend="catalog-pg-policy"><structname>pg_policy</structname></link></entry>
<entry>row-security policies</entry>
</row>
@@ -4672,6 +4677,100 @@
</sect1>
+ <sect1 id="catalog-pg-partitioned">
+ <title><structname>pg_partitioned</structname></title>
+
+ <indexterm zone="catalog-pg-partitioned">
+ <primary>pg_partitioned</primary>
+ </indexterm>
+
+ <para>
+ The catalog <structname>pg_partitioned</structname> stores information
+ about the partition key of partitioned tables.
+ </para>
+
+ <table>
+ <title><structname>pg_partitioned</> Columns</title>
+
+ <tgroup cols="4">
+ <thead>
+ <row>
+ <entry>Name</entry>
+ <entry>Type</entry>
+ <entry>References</entry>
+ <entry>Description</entry>
+ </row>
+ </thead>
+
+ <tbody>
+
+ <row>
+ <entry><structfield>partedrelid</structfield></entry>
+ <entry><type>oid</type></entry>
+ <entry><literal><link linkend="catalog-pg-class"><structname>pg_class</structname></link>.oid</literal></entry>
+ <entry>The OID of the <structname>pg_class</> entry for this partitioned table</entry>
+ </row>
+
+ <row>
+ <entry><structfield>partstrat</structfield></entry>
+ <entry><type>char</type></entry>
+ <entry></entry>
+ <entry>
+ Partitioning strategy (or method); <literal>l</> = list partitioned table,
+ <literal>r</> = range partitioned table
+ </entry>
+ </row>
+
+ <row>
+ <entry><structfield>partnatts</structfield></entry>
+ <entry><type>int2</type></entry>
+ <entry></entry>
+ <entry>The number of columns in partition key</entry>
+ </row>
+
+ <row>
+ <entry><structfield>partattrs</structfield></entry>
+ <entry><type>int2vector</type></entry>
+ <entry><literal><link linkend="catalog-pg-attribute"><structname>pg_attribute</structname></link>.attnum</literal></entry>
+ <entry>
+ This is an array of <structfield>partnatts</structfield> values that
+ indicate which table columns are used as partition key. For example,
+ a value of <literal>1 3</literal> would mean that the first and the
+ third table columns make up the partition key. A zero in this array
+ indicates that the corresponding partition key column is an expression
+ over the table columns, rather than a simple column reference.
+ </entry>
+ </row>
+
+ <row>
+ <entry><structfield>partclass</structfield></entry>
+ <entry><type>oidvector</type></entry>
+ <entry><literal><link linkend="catalog-pg-opclass"><structname>pg_opclass</structname></link>.oid</literal></entry>
+ <entry>
+ For each column in the partition key, this contains the OID of
+ the operator class to use. See
+ <link linkend="catalog-pg-opclass"><structname>pg_opclass</structname></link> for details.
+ </entry>
+ </row>
+
+ <row>
+ <entry><structfield>partexprs</structfield></entry>
+ <entry><type>pg_node_tree</type></entry>
+ <entry></entry>
+ <entry>
+ Expression trees (in <function>nodeToString()</function>
+ representation) for partition key columns that are not simple column
+ references. This is a list with one element for each zero
+ entry in <structfield>partkey</>. Null if all partition key columns
+ are simple references.
+ </entry>
+ </row>
+
+ </tbody>
+ </tgroup>
+ </table>
+ </sect1>
+
<sect1 id="catalog-pg-policy">
<title><structname>pg_policy</structname></title>
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 7448c7f..5a6777a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -925,6 +925,7 @@ extractRelOptions(HeapTuple tuple, TupleDesc tupdesc,
case RELKIND_RELATION:
case RELKIND_TOASTVALUE:
case RELKIND_MATVIEW:
+ case RELKIND_PARTITIONED_REL:
options = heap_reloptions(classForm->relkind, datum, false);
break;
case RELKIND_VIEW:
@@ -1376,6 +1377,7 @@ heap_reloptions(char relkind, Datum reloptions, bool validate)
return (bytea *) rdopts;
case RELKIND_RELATION:
case RELKIND_MATVIEW:
+ case RELKIND_PARTITIONED_REL:
return default_reloptions(reloptions, validate, RELOPT_KIND_HEAP);
default:
/* other relkinds are not supported */
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index 1ce7610..4dc6300 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -11,7 +11,7 @@ top_builddir = ../../..
include $(top_builddir)/src/Makefile.global
OBJS = catalog.o dependency.o heap.o index.o indexing.o namespace.o aclchk.o \
- objectaccess.o objectaddress.o pg_aggregate.o pg_collation.o \
+ objectaccess.o objectaddress.o partition.o pg_aggregate.o pg_collation.o \
pg_constraint.o pg_conversion.o \
pg_depend.o pg_enum.o pg_inherits.o pg_largeobject.o pg_namespace.o \
pg_operator.o pg_proc.o pg_range.o pg_db_role_setting.o pg_shdepend.o \
@@ -41,7 +41,7 @@ POSTGRES_BKI_SRCS = $(addprefix $(top_srcdir)/src/include/catalog/,\
pg_foreign_data_wrapper.h pg_foreign_server.h pg_user_mapping.h \
pg_foreign_table.h pg_policy.h pg_replication_origin.h \
pg_default_acl.h pg_init_privs.h pg_seclabel.h pg_shseclabel.h \
- pg_collation.h pg_range.h pg_transform.h \
+ pg_collation.h pg_range.h pg_transform.h pg_partitioned.h\
toasting.h indexing.h \
)
diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
index d074e85..1818506 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -762,6 +762,8 @@ objectsInSchemaToOids(GrantObjectType objtype, List *nspnames)
case ACL_OBJECT_RELATION:
objs = getRelationsInNamespace(namespaceId, RELKIND_RELATION);
objects = list_concat(objects, objs);
+ objs = getRelationsInNamespace(namespaceId, RELKIND_PARTITIONED_REL);
+ objects = list_concat(objects, objs);
objs = getRelationsInNamespace(namespaceId, RELKIND_VIEW);
objects = list_concat(objects, objs);
objs = getRelationsInNamespace(namespaceId, RELKIND_MATVIEW);
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index e997b57..932a48b 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -41,6 +41,7 @@
#include "catalog/heap.h"
#include "catalog/index.h"
#include "catalog/objectaccess.h"
+#include "catalog/partition.h"
#include "catalog/pg_attrdef.h"
#include "catalog/pg_collation.h"
#include "catalog/pg_constraint.h"
@@ -1101,9 +1102,10 @@ heap_create_with_catalog(const char *relname,
{
/* Use binary-upgrade override for pg_class.oid/relfilenode? */
if (IsBinaryUpgrade &&
- (relkind == RELKIND_RELATION || relkind == RELKIND_SEQUENCE ||
- relkind == RELKIND_VIEW || relkind == RELKIND_MATVIEW ||
- relkind == RELKIND_COMPOSITE_TYPE || relkind == RELKIND_FOREIGN_TABLE))
+ (relkind == RELKIND_RELATION || relkind == RELKIND_PARTITIONED_REL ||
+ relkind == RELKIND_SEQUENCE || relkind == RELKIND_VIEW ||
+ relkind == RELKIND_MATVIEW || relkind == RELKIND_COMPOSITE_TYPE ||
+ relkind == RELKIND_FOREIGN_TABLE))
{
if (!OidIsValid(binary_upgrade_next_heap_pg_class_oid))
ereport(ERROR,
@@ -1134,6 +1136,7 @@ heap_create_with_catalog(const char *relname,
switch (relkind)
{
case RELKIND_RELATION:
+ case RELKIND_PARTITIONED_REL:
case RELKIND_VIEW:
case RELKIND_MATVIEW:
case RELKIND_FOREIGN_TABLE:
@@ -1178,6 +1181,7 @@ heap_create_with_catalog(const char *relname,
* such is an implementation detail: toast tables, sequences and indexes.
*/
if (IsUnderPostmaster && (relkind == RELKIND_RELATION ||
+ relkind == RELKIND_PARTITIONED_REL ||
relkind == RELKIND_VIEW ||
relkind == RELKIND_MATVIEW ||
relkind == RELKIND_FOREIGN_TABLE ||
@@ -1353,7 +1357,8 @@ heap_create_with_catalog(const char *relname,
if (relpersistence == RELPERSISTENCE_UNLOGGED)
{
Assert(relkind == RELKIND_RELATION || relkind == RELKIND_MATVIEW ||
- relkind == RELKIND_TOASTVALUE);
+ relkind == RELKIND_TOASTVALUE || relkind == RELKIND_PARTITIONED_REL);
+
heap_create_init_fork(new_rel_desc);
}
@@ -1800,6 +1805,12 @@ heap_drop_with_catalog(Oid relid)
}
/*
+ * If a partitioned table, delete the pg_partitioned tuples.
+ */
+ if (rel->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+ RemovePartitionKeyByRelId(relid);
+
+ /*
* Schedule unlinking of the relation's physical files at commit.
*/
if (rel->rd_rel->relkind != RELKIND_VIEW &&
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index 1324461..215375e 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -1204,7 +1204,8 @@ get_relation_by_qualified_name(ObjectType objtype, List *objname,
RelationGetRelationName(relation))));
break;
case OBJECT_TABLE:
- if (relation->rd_rel->relkind != RELKIND_RELATION)
+ if (relation->rd_rel->relkind != RELKIND_RELATION &&
+ relation->rd_rel->relkind != RELKIND_PARTITIONED_REL)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not a table",
@@ -3252,6 +3253,10 @@ getRelationDescription(StringInfo buffer, Oid relid)
appendStringInfo(buffer, _("table %s"),
relname);
break;
+ case RELKIND_PARTITIONED_REL:
+ appendStringInfo(buffer, _("partitioned table %s"),
+ relname);
+ break;
case RELKIND_INDEX:
appendStringInfo(buffer, _("index %s"),
relname);
@@ -3708,6 +3713,9 @@ getRelationTypeDescription(StringInfo buffer, Oid relid, int32 objectSubId)
case RELKIND_RELATION:
appendStringInfoString(buffer, "table");
break;
+ case RELKIND_PARTITIONED_REL:
+ appendStringInfoString(buffer, "partitioned table");
+ break;
case RELKIND_INDEX:
appendStringInfoString(buffer, "index");
break;
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
new file mode 100644
index 0000000..c6c8dad
--- /dev/null
+++ b/src/backend/catalog/partition.c
@@ -0,0 +1,450 @@
+/*-------------------------------------------------------------------------
+ *
+ * partition.c
+ * Partitioning related utility functions.
+ *
+ * Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ * src/backend/utils/misc/partition.c
+ *
+ *-------------------------------------------------------------------------
+*/
+
+#include "postgres.h"
+
+#include "access/heapam.h"
+#include "access/htup_details.h"
+#include "access/nbtree.h"
+#include "catalog/dependency.h"
+#include "catalog/indexing.h"
+#include "catalog/objectaddress.h"
+#include "catalog/partition.h"
+#include "catalog/pg_collation.h"
+#include "catalog/pg_opclass.h"
+#include "catalog/pg_partitioned.h"
+#include "catalog/pg_type.h"
+#include "executor/executor.h"
+#include "miscadmin.h"
+#include "nodes/nodeFuncs.h"
+#include "optimizer/clauses.h"
+#include "optimizer/planmain.h"
+#include "storage/lmgr.h"
+#include "utils/array.h"
+#include "utils/builtins.h"
+#include "utils/datum.h"
+#include "utils/memutils.h"
+#include "utils/fmgroids.h"
+#include "utils/inval.h"
+#include "utils/lsyscache.h"
+#include "utils/syscache.h"
+
+static PartitionKey CopyPartitionKey(PartitionKey fromkey);
+static KeyTypeCollInfo *copy_key_type_coll_info(int nkeycols,
+ KeyTypeCollInfo *tcinfo);
+static void free_key_type_coll_info(KeyTypeCollInfo *tcinfo);
+
+/*
+ * StorePartitionKey
+ * Store the partition keys of rel into pg_partitioned catalog
+ */
+void
+StorePartitionKey(Relation rel,
+ char strategy,
+ int16 partnatts,
+ AttrNumber *partattrs,
+ List *partexprs,
+ Oid *partopclass)
+{
+ int i;
+ int2vector *partattrs_vec;
+ oidvector *partopclass_vec;
+ Datum partexprsDatum;
+ Relation pg_partitioned;
+ HeapTuple tuple;
+ Datum values[Natts_pg_partitioned];
+ bool nulls[Natts_pg_partitioned];
+ ObjectAddress myself;
+ ObjectAddress referenced;
+
+ Assert(rel->rd_rel->relkind == RELKIND_PARTITIONED_REL);
+
+ tuple = SearchSysCache1(PARTEDRELID,
+ ObjectIdGetDatum(RelationGetRelid(rel)));
+ /* Cannot already exist */
+ Assert(!HeapTupleIsValid(tuple));
+
+ /*
+ * Copy the partition key, opclass info into arrays (should we
+ * make the caller pass them like this to start with?)
+ */
+ partattrs_vec = buildint2vector(partattrs, partnatts);
+ partopclass_vec = buildoidvector(partopclass, partnatts);
+
+ /* Convert the partition key expressions (if any) to a text datum */
+ if (partexprs)
+ {
+ char *exprsString;
+
+ exprsString = nodeToString(partexprs);
+ partexprsDatum = CStringGetTextDatum(exprsString);
+ pfree(exprsString);
+ }
+ else
+ partexprsDatum = (Datum) 0;
+
+ pg_partitioned = heap_open(PartitionedRelationId, RowExclusiveLock);
+
+ MemSet(nulls, false, sizeof(nulls));
+
+ /* Only this can ever be NULL */
+ if (!partexprsDatum)
+ nulls[Anum_pg_partitioned_partexprs - 1] = true;
+
+ values[Anum_pg_partitioned_partedrelid - 1] = ObjectIdGetDatum(RelationGetRelid(rel));
+ values[Anum_pg_partitioned_partstrat - 1] = CharGetDatum(strategy);
+ values[Anum_pg_partitioned_partnatts - 1] = Int16GetDatum(partnatts);
+ values[Anum_pg_partitioned_partattrs - 1] = PointerGetDatum(partattrs_vec);
+ values[Anum_pg_partitioned_partclass - 1] = PointerGetDatum(partopclass_vec);
+ values[Anum_pg_partitioned_partexprs - 1] = partexprsDatum;
+
+ tuple = heap_form_tuple(RelationGetDescr(pg_partitioned), values, nulls);
+
+ simple_heap_insert(pg_partitioned, tuple);
+
+ /* Update the indexes on pg_partitioned */
+ CatalogUpdateIndexes(pg_partitioned, tuple);
+
+ /* Make this relation dependent on a few things: */
+ myself.classId = RelationRelationId;
+ myself.objectId = RelationGetRelid(rel);;
+ myself.objectSubId = 0;
+
+ /* Operator class per key column */
+ for (i = 0; i < partnatts; i++)
+ {
+ referenced.classId = OperatorClassRelationId;
+ referenced.objectId = partopclass[i];
+ referenced.objectSubId = 0;
+
+ recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+ }
+
+ /*
+ * Things inside key expressions ignoring the vars of the table which
+ * is achieved by asking the dependency-tracking system to ignore any
+ * dependencies to self (table) or more appropriately own attributes.
+ */
+ if (partexprs)
+ recordDependencyOnSingleRelExpr(&myself,
+ (Node *) partexprs,
+ RelationGetRelid(rel),
+ DEPENDENCY_NORMAL,
+ DEPENDENCY_IGNORE);
+ /* Tell world about the key */
+ CacheInvalidateRelcache(rel);
+
+ heap_close(pg_partitioned, RowExclusiveLock);
+ heap_freetuple(tuple);
+}
+
+/*
+ * RemovePartitionKeyByRelId
+ * Remove pg_partitioned entry for a relation
+ */
+void
+RemovePartitionKeyByRelId(Oid relid)
+{
+ Relation rel;
+ HeapTuple tuple;
+
+ rel = heap_open(PartitionedRelationId, RowExclusiveLock);
+
+ tuple = SearchSysCache1(PARTEDRELID, ObjectIdGetDatum(relid));
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "cache lookup failed for partition key of relation %u", relid);
+
+ simple_heap_delete(rel, &tuple->t_self);
+
+ /* Update the indexes on pg_partitioned */
+ CatalogUpdateIndexes(rel, tuple);
+
+ ReleaseSysCache(tuple);
+ heap_close(rel, RowExclusiveLock);
+}
+
+/*
+ * RelationBuildPartitionKey
+ * Build and attach to relcache partition key data of relation
+ *
+ * Note that the partition key data attached to a relcache entry must be
+ * stored CacheMemoryContext to ensure it survives as long as the relcache
+ * entry. But we should be running in a less long-lived working context.
+ * To avoid leaking cache memory if this routine fails partway through,
+ * we build in working memory and then copy the completed structure into
+ * cache memory.
+ */
+void
+RelationBuildPartitionKey(Relation relation)
+{
+ Form_pg_partitioned form;
+ Relation catalog;
+ HeapTuple tuple;
+ bool isnull;
+ int i;
+ PartitionKey key;
+ int2vector *partattrs;
+ oidvector *opclass;
+ KeyTypeCollInfo *tcinfo;
+ ListCell *partexprs_item;
+ Datum datum;
+ MemoryContext oldcxt;
+
+ tuple = SearchSysCache1(PARTEDRELID,
+ ObjectIdGetDatum(RelationGetRelid(relation)));
+ /*
+ * Happend when relcache is built before the partition key is stored in
+ * system catalog
+ */
+ if (!HeapTupleIsValid(tuple))
+ return;
+
+ form = (Form_pg_partitioned) GETSTRUCT(tuple);
+
+ /* Allocate in the hopefully short-lived working context */
+ key = (PartitionKey) palloc0(sizeof(PartitionKeyData));
+ key->strategy = form->partstrat;
+ key->partnatts = form->partnatts;
+
+ /* Open the catalog for its tuple descriptor */
+ catalog = heap_open(PartitionedRelationId, AccessShareLock);
+ datum = fastgetattr(tuple, Anum_pg_partitioned_partattrs,
+ RelationGetDescr(catalog),
+ &isnull);
+ Assert(!isnull);
+ partattrs = (int2vector *) DatumGetPointer(datum);
+
+ datum = fastgetattr(tuple, Anum_pg_partitioned_partclass,
+ RelationGetDescr(catalog),
+ &isnull);
+ Assert(!isnull);
+ opclass = (oidvector *) DatumGetPointer(datum);
+
+ datum = heap_getattr(tuple,
+ Anum_pg_partitioned_partexprs,
+ RelationGetDescr(catalog),
+ &isnull);
+
+ if (!isnull)
+ {
+ char *exprsString;
+ Node *exprs;
+
+ exprsString = TextDatumGetCString(datum);
+ exprs = stringToNode(exprsString);
+ pfree(exprsString);
+
+ /*
+ * Run the expressions through eval_const_expressions. This is
+ * not just an optimization, but is necessary, because eventually
+ * the planner will be comparing them to similarly-processed qual
+ * clauses, and may fail to detect valid matches without this.
+ * We don't bother with canonicalize_qual, however.
+ */
+ exprs = eval_const_expressions(NULL, (Node *) exprs);
+
+ /* May as well fix opfuncids too */
+ fix_opfuncids((Node *) exprs);
+ key->partexprs = (List *) exprs;
+ }
+
+ key->partattrs = (AttrNumber *) palloc0(key->partnatts * sizeof(AttrNumber));
+ key->partopfamily = (Oid *) palloc0(key->partnatts * sizeof(Oid));
+ key->partsupfunc = (FmgrInfo *) palloc0(key->partnatts * sizeof(FmgrInfo));
+
+ /* Gather type and collation info as well */
+ key->tcinfo = tcinfo = (KeyTypeCollInfo *) palloc0(sizeof(KeyTypeCollInfo));
+ tcinfo->typid = (Oid *) palloc0(key->partnatts * sizeof(Oid));
+ tcinfo->typmod = (int32 *) palloc0(key->partnatts * sizeof(int32));
+ tcinfo->typlen = (int16 *) palloc0(key->partnatts * sizeof(int16));
+ tcinfo->typbyval = (bool *) palloc0(key->partnatts * sizeof(bool));
+ tcinfo->typalign = (char *) palloc0(key->partnatts * sizeof(char));
+ tcinfo->typcoll = (Oid *) palloc0(key->partnatts * sizeof(Oid));
+
+ /*
+ * Copy partattrs. Further, determine and store the opfamily and
+ * btree support function per attribute.
+ */
+ partexprs_item = list_head(key->partexprs);
+ for (i = 0; i < key->partnatts; i++)
+ {
+ HeapTuple tuple;
+ AttrNumber attno;
+ Form_pg_opclass form;
+ Oid funcid;
+
+ key->partattrs[i] = attno = partattrs->values[i];
+
+ /* Collect type information */
+ if (attno != InvalidAttrNumber)
+ {
+ tcinfo->typid[i] = relation->rd_att->attrs[attno - 1]->atttypid;
+ tcinfo->typmod[i] = relation->rd_att->attrs[attno - 1]->atttypmod;
+ tcinfo->typcoll[i] = relation->rd_att->attrs[attno - 1]->attcollation;
+ }
+ else
+ {
+ tcinfo->typid[i] = exprType(lfirst(partexprs_item));
+ tcinfo->typmod[i] = exprTypmod(lfirst(partexprs_item));
+ tcinfo->typcoll[i] = exprCollation(lfirst(partexprs_item));
+ partexprs_item = lnext(partexprs_item);
+ }
+ get_typlenbyvalalign(tcinfo->typid[i],
+ &tcinfo->typlen[i],
+ &tcinfo->typbyval[i],
+ &tcinfo->typalign[i]);
+
+ tuple = SearchSysCache1(CLAOID, ObjectIdGetDatum(opclass->values[i]));
+
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "cache lookup failed for opclass %u", opclass->values[i]);
+
+ form = (Form_pg_opclass) GETSTRUCT(tuple);
+ key->partopfamily[i] = form->opcfamily;
+
+ /*
+ * Btree support function is good to cover the list and range
+ * partitionig related needs.
+ */
+ funcid = get_opfamily_proc(form->opcfamily,
+ form->opcintype, form->opcintype,
+ BTORDER_PROC);
+
+ fmgr_info(funcid, &key->partsupfunc[i]);
+ ReleaseSysCache(tuple);
+ }
+
+ ReleaseSysCache(tuple);
+ heap_close(catalog, AccessShareLock);
+
+ /* Success --- now copy to the cache memory */
+ oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
+ relation->rd_partkey = CopyPartitionKey(key);
+ MemoryContextSwitchTo(oldcxt);
+
+ FreePartitionKey(key);
+}
+
+/*
+ * CopyPartitionKey
+ *
+ * The copy is allocated in the current memory context.
+ */
+static PartitionKey
+CopyPartitionKey(PartitionKey fromkey)
+{
+ PartitionKey newkey;
+
+ newkey = (PartitionKey) palloc0(sizeof(PartitionKeyData));
+
+ newkey->strategy = fromkey->strategy;
+ newkey->partnatts = fromkey->partnatts;
+
+ newkey->partattrs = (AttrNumber *)
+ palloc(newkey->partnatts * sizeof(AttrNumber));
+ memcpy(newkey->partattrs, fromkey->partattrs,
+ newkey->partnatts * sizeof(AttrNumber));
+
+ newkey->partopfamily = (Oid *) palloc(newkey->partnatts * sizeof(Oid));
+ memcpy(newkey->partopfamily, fromkey->partopfamily,
+ newkey->partnatts * sizeof(Oid));
+
+ newkey->partsupfunc = (FmgrInfo *)
+ palloc(newkey->partnatts * sizeof(FmgrInfo));
+ memcpy(newkey->partsupfunc, fromkey->partsupfunc,
+ newkey->partnatts * sizeof(FmgrInfo));
+
+ newkey->partexprs = copyObject(fromkey->partexprs);
+ newkey->tcinfo = copy_key_type_coll_info(newkey->partnatts,
+ fromkey->tcinfo);
+
+ return newkey;
+}
+
+/*
+ * copy_key_type_coll_info
+ *
+ * The copy is allocated in the current memory context.
+ */
+static KeyTypeCollInfo *
+copy_key_type_coll_info(int nkeycols, KeyTypeCollInfo *tcinfo)
+{
+ KeyTypeCollInfo *result = (KeyTypeCollInfo *)
+ palloc0(sizeof(KeyTypeCollInfo));
+
+ result->typid = (Oid *) palloc0(nkeycols * sizeof(Oid));
+ memcpy(result->typid, tcinfo->typid, nkeycols * sizeof(Oid));
+
+ result->typmod = (int32 *) palloc0(nkeycols * sizeof(int32));
+ memcpy(result->typmod, tcinfo->typmod, nkeycols * sizeof(int32));
+
+ result->typlen = (int16 *) palloc0(nkeycols * sizeof(int16));
+ memcpy(result->typlen, tcinfo->typlen, nkeycols * sizeof(int16));
+
+ result->typbyval = (bool *) palloc0(nkeycols * sizeof(bool));
+ memcpy(result->typbyval, tcinfo->typbyval, nkeycols * sizeof(bool));
+
+ result->typalign = (char *) palloc0(nkeycols * sizeof(bool));
+ memcpy(result->typalign, tcinfo->typalign, nkeycols * sizeof(char));
+
+ result->typcoll = (Oid *) palloc0(nkeycols * sizeof(Oid));
+ memcpy(result->typcoll, tcinfo->typcoll, nkeycols * sizeof(Oid));
+
+ return result;
+}
+
+/*
+ * free_key_type_info
+ */
+static void
+free_key_type_coll_info(KeyTypeCollInfo *tcinfo)
+{
+ Assert(tcinfo != NULL);
+
+ pfree(tcinfo->typid);
+ pfree(tcinfo->typmod);
+ pfree(tcinfo->typlen);
+ pfree(tcinfo->typbyval);
+ pfree(tcinfo->typalign);
+ pfree(tcinfo->typcoll);
+ pfree(tcinfo);
+}
+
+/*
+ * Does relid have a pg_partitioned entry?
+ */
+bool
+relid_is_partitioned(Oid relid)
+{
+ return SearchSysCacheExists1(PARTEDRELID, ObjectIdGetDatum(relid));
+}
+
+/*
+ * FreePartitionKeys
+ */
+void
+FreePartitionKey(PartitionKey key)
+{
+ if (key == NULL)
+ return;
+
+ pfree(key->partattrs);
+ pfree(key->partopfamily);
+ pfree(key->partsupfunc);
+ if (key->partexprs)
+ pfree(key->partexprs);
+ free_key_type_coll_info(key->tcinfo);
+ pfree(key);
+}
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index cf8c816..8b6b570 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -201,6 +201,7 @@ analyze_rel(Oid relid, RangeVar *relation, int options,
* locked the relation.
*/
if (onerel->rd_rel->relkind == RELKIND_RELATION ||
+ onerel->rd_rel->relkind == RELKIND_PARTITIONED_REL ||
onerel->rd_rel->relkind == RELKIND_MATVIEW)
{
/* Regular table, so we'll use the regular row acquisition function */
@@ -1320,6 +1321,7 @@ acquire_inherited_sample_rows(Relation onerel, int elevel,
/* Check table type (MATVIEW can't happen, but might as well allow) */
if (childrel->rd_rel->relkind == RELKIND_RELATION ||
+ childrel->rd_rel->relkind == RELKIND_PARTITIONED_REL ||
childrel->rd_rel->relkind == RELKIND_MATVIEW)
{
/* Regular table, so use the regular row acquisition function */
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 3201476..403738d 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -1718,6 +1718,12 @@ BeginCopyTo(Relation rel,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot copy from sequence \"%s\"",
RelationGetRelationName(rel))));
+ else if (rel->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot copy from partitioned table \"%s\"",
+ RelationGetRelationName(rel)),
+ errhint("Try the COPY (SELECT ...) TO variant.")));
else
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 4ef6b80..e8d25e8 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -369,7 +369,8 @@ DefineIndex(Oid relationId,
namespaceId = RelationGetNamespace(rel);
if (rel->rd_rel->relkind != RELKIND_RELATION &&
- rel->rd_rel->relkind != RELKIND_MATVIEW)
+ rel->rd_rel->relkind != RELKIND_MATVIEW &&
+ rel->rd_rel->relkind != RELKIND_PARTITIONED_REL)
{
if (rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
diff --git a/src/backend/commands/lockcmds.c b/src/backend/commands/lockcmds.c
index 175d1f3..62671a8 100644
--- a/src/backend/commands/lockcmds.c
+++ b/src/backend/commands/lockcmds.c
@@ -88,7 +88,7 @@ RangeVarCallbackForLockTable(const RangeVar *rv, Oid relid, Oid oldrelid,
* check */
/* Currently, we only allow plain tables to be locked */
- if (relkind != RELKIND_RELATION)
+ if (relkind != RELKIND_RELATION && relkind != RELKIND_PARTITIONED_REL)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not a table",
diff --git a/src/backend/commands/policy.c b/src/backend/commands/policy.c
index 93d15e4..de75da1 100644
--- a/src/backend/commands/policy.c
+++ b/src/backend/commands/policy.c
@@ -88,7 +88,7 @@ RangeVarCallbackForPolicy(const RangeVar *rv, Oid relid, Oid oldrelid,
rv->relname)));
/* Relation type MUST be a table. */
- if (relkind != RELKIND_RELATION)
+ if (relkind != RELKIND_RELATION && relkind != RELKIND_PARTITIONED_REL)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not a table", rv->relname)));
diff --git a/src/backend/commands/seclabel.c b/src/backend/commands/seclabel.c
index 5bd7e12..8bc8638 100644
--- a/src/backend/commands/seclabel.c
+++ b/src/backend/commands/seclabel.c
@@ -107,6 +107,7 @@ ExecSecLabelStmt(SecLabelStmt *stmt)
* are the only relkinds for which pg_dump will dump labels).
*/
if (relation->rd_rel->relkind != RELKIND_RELATION &&
+ relation->rd_rel->relkind != RELKIND_PARTITIONED_REL &&
relation->rd_rel->relkind != RELKIND_VIEW &&
relation->rd_rel->relkind != RELKIND_MATVIEW &&
relation->rd_rel->relkind != RELKIND_COMPOSITE_TYPE &&
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index c98f981..5dcd4cf 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -1467,6 +1467,7 @@ process_owned_by(Relation seqrel, List *owned_by)
/* Must be a regular or foreign table */
if (!(tablerel->rd_rel->relkind == RELKIND_RELATION ||
+ tablerel->rd_rel->relkind == RELKIND_PARTITIONED_REL ||
tablerel->rd_rel->relkind == RELKIND_FOREIGN_TABLE))
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index d03147d..87bf466 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -29,6 +29,7 @@
#include "catalog/indexing.h"
#include "catalog/namespace.h"
#include "catalog/objectaccess.h"
+#include "catalog/partition.h"
#include "catalog/pg_am.h"
#include "catalog/pg_collation.h"
#include "catalog/pg_constraint.h"
@@ -262,6 +263,12 @@ struct DropRelationCallbackState
bool concurrent;
};
+/* for find_attr_reference_walker */
+typedef struct
+{
+ AttrNumber attnum;
+} find_attr_reference_context;
+
/* Alter table target-type flags for ATSimplePermissions */
#define ATT_TABLE 0x0001
#define ATT_VIEW 0x0002
@@ -438,6 +445,9 @@ static void ComputePartitionAttrs(Oid relid, List *partParams,
AttrNumber *partattrs,
List **partexprs,
Oid *partoplass);
+static bool find_attr_reference_walker(Node *node,
+ find_attr_reference_context *context);
+static bool is_partition_attr(Relation rel, AttrNumber attnum, bool *is_expr);
/* ----------------------------------------------------------------
@@ -601,7 +611,8 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
* affect other relkinds, but it would complicate interpretOidsOption().
*/
localHasOids = interpretOidsOption(stmt->options,
- (relkind == RELKIND_RELATION));
+ (relkind == RELKIND_RELATION ||
+ relkind == RELKIND_PARTITIONED_REL));
descriptor->tdhasoid = (localHasOids || parentOidCount > 0);
/*
@@ -715,7 +726,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
AddRelationNewConstraints(rel, rawDefaults, stmt->constraints,
true, true, false);
- /* Process partition key, if any */
+ /* Process and store partition key, if any */
if (stmt->partby)
{
PartitionBy *partby;
@@ -728,6 +739,8 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
partby = transformPartitionBy(rel, stmt->partby);
ComputePartitionAttrs(relationId, stmt->partby->partParams,
partattrs, &partexprs, partopclass);
+ StorePartitionKey(rel, partby->strategy, partnatts, partattrs,
+ partexprs, partopclass);
}
ObjectAddressSet(address, RelationRelationId, relationId);
@@ -975,7 +988,15 @@ RangeVarCallbackForDropRelation(const RangeVar *rel, Oid relOid, Oid oldRelOid,
return; /* concurrently dropped, so nothing to do */
classform = (Form_pg_class) GETSTRUCT(tuple);
- if (classform->relkind != relkind)
+ /*
+ * Both normal and partitioned tables are dropped using DROP TABLE.
+ * RemoveRelations however never passes RELKIND_PARTITIONED_REL as
+ * relkind for OBJECT_TABLE relations. A mismatch below may have
+ * to do with that. So, check.
+ */
+ if (classform->relkind != relkind &&
+ (relkind == RELKIND_RELATION &&
+ classform->relkind != RELKIND_PARTITIONED_REL))
DropErrorMsgWrongType(rel->relname, classform->relkind, relkind);
/* Allow DROP to either table owner or schema owner */
@@ -1313,7 +1334,8 @@ truncate_check_rel(Relation rel)
AclResult aclresult;
/* Only allow truncate on regular tables */
- if (rel->rd_rel->relkind != RELKIND_RELATION)
+ if (rel->rd_rel->relkind != RELKIND_RELATION &&
+ rel->rd_rel->relkind != RELKIND_PARTITIONED_REL)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not a table",
@@ -1541,8 +1563,16 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
*/
relation = heap_openrv(parent, ShareUpdateExclusiveLock);
+ /* Cannot inherit from partitioned tables */
+ if (relation->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot inherit from partitioned table \"%s\"",
+ parent->relname)));
+
if (relation->rd_rel->relkind != RELKIND_RELATION &&
- relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
+ relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE &&
+ relation->rd_rel->relkind != RELKIND_PARTITIONED_REL)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("inherited relation \"%s\" is not a table or foreign table",
@@ -2182,6 +2212,7 @@ renameatt_check(Oid myrelid, Form_pg_class classform, bool recursing)
* restriction.
*/
if (relkind != RELKIND_RELATION &&
+ relkind != RELKIND_PARTITIONED_REL &&
relkind != RELKIND_VIEW &&
relkind != RELKIND_MATVIEW &&
relkind != RELKIND_COMPOSITE_TYPE &&
@@ -4311,6 +4342,7 @@ ATSimplePermissions(Relation rel, int allowed_targets)
switch (rel->rd_rel->relkind)
{
case RELKIND_RELATION:
+ case RELKIND_PARTITIONED_REL:
actual_target = ATT_TABLE;
break;
case RELKIND_VIEW:
@@ -4547,6 +4579,7 @@ find_composite_type_dependencies(Oid typeOid, Relation origRelation,
att = rel->rd_att->attrs[pg_depend->objsubid - 1];
if (rel->rd_rel->relkind == RELKIND_RELATION ||
+ rel->rd_rel->relkind == RELKIND_PARTITIONED_REL ||
rel->rd_rel->relkind == RELKIND_MATVIEW)
{
if (origTypeName)
@@ -5437,6 +5470,7 @@ ATPrepSetStatistics(Relation rel, const char *colName, Node *newValue, LOCKMODE
* allowSystemTableMods to be turned on.
*/
if (rel->rd_rel->relkind != RELKIND_RELATION &&
+ rel->rd_rel->relkind != RELKIND_PARTITIONED_REL &&
rel->rd_rel->relkind != RELKIND_MATVIEW &&
rel->rd_rel->relkind != RELKIND_INDEX &&
rel->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
@@ -5711,6 +5745,73 @@ ATPrepDropColumn(List **wqueue, Relation rel, bool recurse, bool recursing,
cmd->subtype = AT_DropColumnRecurse;
}
+/* Checks if a Var node is for a given attnum */
+static bool
+find_attr_reference_walker(Node *node, find_attr_reference_context *context)
+{
+ if (node == NULL)
+ return false;
+
+ if (IsA(node, Var))
+ {
+ Var *variable = (Var *) node;
+ AttrNumber attnum = variable->varattno;
+
+ if (attnum == context->attnum)
+ return true;
+ }
+
+ return expression_tree_walker(node, find_attr_reference_walker, context);
+}
+
+/*
+ * Checks if attnum is a partition attribute for rel
+ *
+ * Sets *is_expr if attnum is found to be referenced in some partition key
+ * expression.
+ */
+static bool
+is_partition_attr(Relation rel, AttrNumber attnum, bool *is_expr)
+{
+ PartitionKey key;
+ ListCell *partexpr_item;
+ int i;
+
+ if (rel->rd_rel->relkind != RELKIND_PARTITIONED_REL)
+ {
+ *is_expr = false;
+ return false;
+ }
+
+ key = RelationGetPartitionKey(rel);
+
+ partexpr_item = list_head(key->partexprs);
+ for (i = 0; i < key->partnatts; i++)
+ {
+ AttrNumber partatt = key->partattrs[i];
+
+ if(partatt != 0)
+ {
+ *is_expr = false;
+ if (attnum == partatt)
+ return true;
+ }
+ else
+ {
+ find_attr_reference_context context;
+
+ *is_expr = true;
+ context.attnum = attnum;
+ if (find_attr_reference_walker(lfirst(partexpr_item), &context))
+ return true;
+
+ partexpr_item = lnext(partexpr_item);
+ }
+ }
+
+ return false;
+}
+
/*
* Return value is the address of the dropped column.
*/
@@ -5725,6 +5826,7 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
AttrNumber attnum;
List *children;
ObjectAddress object;
+ bool is_expr;
/* At top level, permission check was done in ATPrepCmd, else do it */
if (recursing)
@@ -5769,6 +5871,19 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
errmsg("cannot drop inherited column \"%s\"",
colName)));
+ /* Don't drop columns used in partition key */
+ if (is_partition_attr(rel, attnum, &is_expr))
+ {
+ if (!is_expr)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("cannot drop column named in partition key")));
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("cannot drop column referenced in partition key expression")));
+ }
+
ReleaseSysCache(tuple);
/*
@@ -6287,6 +6402,12 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
* Validity checks (permission checks wait till we have the column
* numbers)
*/
+ if (pkrel->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot reference relation \"%s\"", RelationGetRelationName(pkrel)),
+ errdetail("Referencing partitioned tables in foreign key constraints is not supported.")));
+
if (pkrel->rd_rel->relkind != RELKIND_RELATION)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
@@ -7881,6 +8002,7 @@ ATPrepAlterColumnType(List **wqueue,
NewColumnValue *newval;
ParseState *pstate = make_parsestate(NULL);
AclResult aclresult;
+ bool is_expr;
if (rel->rd_rel->reloftype && !recursing)
ereport(ERROR,
@@ -7911,6 +8033,19 @@ ATPrepAlterColumnType(List **wqueue,
errmsg("cannot alter inherited column \"%s\"",
colName)));
+ /* Don't alter columns used in partition key */
+ if (is_partition_attr(rel, attnum, &is_expr))
+ {
+ if (!is_expr)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("cannot alter column named in partition key")));
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("cannot alter column referenced in partition key expression")));
+ }
+
/* Look up the target type */
typenameTypeIdAndMod(NULL, typeName, &targettype, &targettypmod);
@@ -7926,7 +8061,8 @@ ATPrepAlterColumnType(List **wqueue,
list_make1_oid(rel->rd_rel->reltype),
false);
- if (tab->relkind == RELKIND_RELATION)
+ if (tab->relkind == RELKIND_RELATION ||
+ tab->relkind == RELKIND_PARTITIONED_REL)
{
/*
* Set up an expression to transform the old data value to the new
@@ -8953,6 +9089,7 @@ ATExecChangeOwner(Oid relationOid, Oid newOwnerId, bool recursing, LOCKMODE lock
switch (tuple_class->relkind)
{
case RELKIND_RELATION:
+ case RELKIND_PARTITIONED_REL:
case RELKIND_VIEW:
case RELKIND_MATVIEW:
case RELKIND_FOREIGN_TABLE:
@@ -9415,6 +9552,7 @@ ATExecSetRelOptions(Relation rel, List *defList, AlterTableType operation,
switch (rel->rd_rel->relkind)
{
case RELKIND_RELATION:
+ case RELKIND_PARTITIONED_REL:
case RELKIND_TOASTVALUE:
case RELKIND_MATVIEW:
(void) heap_reloptions(rel->rd_rel->relkind, newOptions, true);
@@ -9837,7 +9975,8 @@ AlterTableMoveAll(AlterTableMoveAllStmt *stmt)
/* Only move the object type requested */
if ((stmt->objtype == OBJECT_TABLE &&
- relForm->relkind != RELKIND_RELATION) ||
+ relForm->relkind != RELKIND_RELATION &&
+ relForm->relkind != RELKIND_PARTITIONED_REL) ||
(stmt->objtype == OBJECT_INDEX &&
relForm->relkind != RELKIND_INDEX) ||
(stmt->objtype == OBJECT_MATVIEW &&
@@ -10036,6 +10175,11 @@ ATPrepAddInherit(Relation child_rel)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot change inheritance of typed table")));
+
+ if (child_rel->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot change inheritance of partitioned table")));
}
/*
@@ -10087,6 +10231,12 @@ ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode)
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot inherit to temporary relation of another session")));
+ /* Prevent partitioned tables from becoming inheritance parents */
+ if (parent_rel->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot inherit from partitioned table \"%s\"", parent->relname)));
+
/*
* Check for duplicates in the list of parents, and determine the highest
* inhseqno already present; we'll use the next one for the new parent.
@@ -11465,6 +11615,7 @@ AlterTableNamespaceInternal(Relation rel, Oid oldNspOid, Oid nspOid,
/* Fix other dependent stuff */
if (rel->rd_rel->relkind == RELKIND_RELATION ||
+ rel->rd_rel->relkind == RELKIND_PARTITIONED_REL ||
rel->rd_rel->relkind == RELKIND_MATVIEW)
{
AlterIndexNamespaces(classRel, rel, oldNspOid, nspOid, objsMoved);
@@ -11914,7 +12065,7 @@ RangeVarCallbackOwnsTable(const RangeVar *relation,
if (!relkind)
return;
if (relkind != RELKIND_RELATION && relkind != RELKIND_TOASTVALUE &&
- relkind != RELKIND_MATVIEW)
+ relkind != RELKIND_MATVIEW && relkind != RELKIND_PARTITIONED_REL)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not a table or materialized view", relation->relname)));
@@ -12068,6 +12219,7 @@ RangeVarCallbackForAlterRelation(const RangeVar *rv, Oid relid, Oid oldrelid,
*/
if (IsA(stmt, AlterObjectSchemaStmt) &&
relkind != RELKIND_RELATION &&
+ relkind != RELKIND_PARTITIONED_REL &&
relkind != RELKIND_VIEW &&
relkind != RELKIND_MATVIEW &&
relkind != RELKIND_SEQUENCE &&
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index 6f728ff..c1e1c4d 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -174,7 +174,8 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
* Triggers must be on tables or views, and there are additional
* relation-type-specific restrictions.
*/
- if (rel->rd_rel->relkind == RELKIND_RELATION)
+ if (rel->rd_rel->relkind == RELKIND_RELATION ||
+ rel->rd_rel->relkind == RELKIND_PARTITIONED_REL)
{
/* Tables can't have INSTEAD OF triggers */
if (stmt->timing != TRIGGER_TYPE_BEFORE &&
@@ -1112,6 +1113,7 @@ RemoveTriggerById(Oid trigOid)
rel = heap_open(relid, AccessExclusiveLock);
if (rel->rd_rel->relkind != RELKIND_RELATION &&
+ rel->rd_rel->relkind != RELKIND_PARTITIONED_REL &&
rel->rd_rel->relkind != RELKIND_VIEW &&
rel->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
ereport(ERROR,
@@ -1218,7 +1220,8 @@ RangeVarCallbackForRenameTrigger(const RangeVar *rv, Oid relid, Oid oldrelid,
/* only tables and views can have triggers */
if (form->relkind != RELKIND_RELATION && form->relkind != RELKIND_VIEW &&
- form->relkind != RELKIND_FOREIGN_TABLE)
+ form->relkind != RELKIND_FOREIGN_TABLE &&
+ form->relkind != RELKIND_PARTITIONED_REL)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not a table, view, or foreign table",
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 93361a0..a30b8b6 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -1298,6 +1298,7 @@ vacuum_rel(Oid relid, RangeVar *relation, int options, VacuumParams *params)
* relation.
*/
if (onerel->rd_rel->relkind != RELKIND_RELATION &&
+ onerel->rd_rel->relkind != RELKIND_PARTITIONED_REL &&
onerel->rd_rel->relkind != RELKIND_MATVIEW &&
onerel->rd_rel->relkind != RELKIND_TOASTVALUE)
{
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index ac02304..29073c0 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -1019,6 +1019,7 @@ CheckValidResultRel(Relation resultRel, CmdType operation)
switch (resultRel->rd_rel->relkind)
{
case RELKIND_RELATION:
+ case RELKIND_PARTITIONED_REL:
/* OK */
break;
case RELKIND_SEQUENCE:
@@ -1152,6 +1153,7 @@ CheckValidRowMarkRel(Relation rel, RowMarkType markType)
switch (rel->rd_rel->relkind)
{
case RELKIND_RELATION:
+ case RELKIND_PARTITIONED_REL:
/* OK */
break;
case RELKIND_SEQUENCE:
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index e62c8aa..7ad1140 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1871,6 +1871,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
relkind = resultRelInfo->ri_RelationDesc->rd_rel->relkind;
if (relkind == RELKIND_RELATION ||
+ relkind == RELKIND_PARTITIONED_REL ||
relkind == RELKIND_MATVIEW)
{
j->jf_junkAttNo = ExecFindJunkAttribute(j, "ctid");
diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c
index b56fd4f..77ffd84 100644
--- a/src/backend/parser/parse_agg.c
+++ b/src/backend/parser/parse_agg.c
@@ -488,9 +488,9 @@ check_agglevels_and_constraints(ParseState *pstate, Node *expr)
break;
case EXPR_KIND_PARTITION_KEY:
if (isAgg)
- err = _("aggregate functions are not allowed in partition key expressions");
+ err = _("aggregate functions are not allowed in partition key expression");
else
- err = _("grouping operations are not allowed in partition key expressions");
+ err = _("grouping operations are not allowed in partition key expression");
break;
@@ -852,7 +852,7 @@ transformWindowFuncCall(ParseState *pstate, WindowFunc *wfunc,
err = _("window functions are not allowed in trigger WHEN conditions");
break;
case EXPR_KIND_PARTITION_KEY:
- err = _("window functions are not allowed in partition key expressions");
+ err = _("window functions are not allowed in partition key expression");
break;
/*
diff --git a/src/backend/parser/parse_clause.c b/src/backend/parser/parse_clause.c
index 751de4b..307e383 100644
--- a/src/backend/parser/parse_clause.c
+++ b/src/backend/parser/parse_clause.c
@@ -21,6 +21,7 @@
#include "access/tsmapi.h"
#include "catalog/catalog.h"
#include "catalog/heap.h"
+#include "catalog/partition.h"
#include "catalog/pg_am.h"
#include "catalog/pg_constraint_fn.h"
#include "catalog/pg_type.h"
@@ -200,6 +201,13 @@ setTargetTable(ParseState *pstate, RangeVar *relation,
relation->alias, inh, false);
pstate->p_target_rangetblentry = rte;
+ /*
+ * Override specified inheritance option, if relation is a partitioned
+ * table and it's a target of INSERT (ie, alsoSource == false).
+ */
+ if (alsoSource)
+ rte->inh |= relid_is_partitioned(rte->relid);
+
/* assume new rte is at end */
rtindex = list_length(pstate->p_rtable);
Assert(rte == rt_fetch(rtindex, pstate->p_rtable));
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index b53b7f1..147c6da 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -1724,7 +1724,7 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
err = _("cannot use subquery in trigger WHEN condition");
break;
case EXPR_KIND_PARTITION_KEY:
- err = _("cannot use subquery in partition key expressions");
+ err = _("cannot use subquery in partition key expression");
break;
/*
diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c
index 81332b5..abcc1d6 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -20,6 +20,7 @@
#include "access/sysattr.h"
#include "catalog/heap.h"
#include "catalog/namespace.h"
+#include "catalog/partition.h"
#include "catalog/pg_type.h"
#include "funcapi.h"
#include "nodes/makefuncs.h"
@@ -1216,9 +1217,12 @@ addRangeTableEntry(ParseState *pstate,
*
* The initial default on access checks is always check-for-READ-access,
* which is the right thing for all except target tables.
+ *
+ * Note: we override the specified inheritance option, if relation is a
+ * partitioned table.
*/
rte->lateral = false;
- rte->inh = inh;
+ rte->inh = inh || relid_is_partitioned(RelationGetRelid(rel));
rte->inFromCl = inFromCl;
rte->requiredPerms = ACL_SELECT;
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e267f74..465597d 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -33,6 +33,7 @@
#include "catalog/heap.h"
#include "catalog/index.h"
#include "catalog/namespace.h"
+#include "catalog/partition.h"
#include "catalog/pg_am.h"
#include "catalog/pg_collation.h"
#include "catalog/pg_constraint.h"
@@ -821,6 +822,7 @@ transformTableLikeClause(CreateStmtContext *cxt, TableLikeClause *table_like_cla
relation = relation_openrv(table_like_clause->relation, AccessShareLock);
if (relation->rd_rel->relkind != RELKIND_RELATION &&
+ relation->rd_rel->relkind != RELKIND_PARTITIONED_REL &&
relation->rd_rel->relkind != RELKIND_VIEW &&
relation->rd_rel->relkind != RELKIND_MATVIEW &&
relation->rd_rel->relkind != RELKIND_COMPOSITE_TYPE &&
@@ -2577,7 +2579,7 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
cxt.blist = NIL;
cxt.alist = NIL;
cxt.pkey = NULL;
- cxt.ispartitioned = false;
+ cxt.ispartitioned = relid_is_partitioned(relid);
/*
* The only subtypes that currently require parse transformation handling
diff --git a/src/backend/rewrite/rewriteDefine.c b/src/backend/rewrite/rewriteDefine.c
index 96f3f37..db80c90 100644
--- a/src/backend/rewrite/rewriteDefine.c
+++ b/src/backend/rewrite/rewriteDefine.c
@@ -260,6 +260,7 @@ DefineQueryRewrite(char *rulename,
* blocks them for users. Don't mention them in the error message.
*/
if (event_relation->rd_rel->relkind != RELKIND_RELATION &&
+ event_relation->rd_rel->relkind != RELKIND_PARTITIONED_REL &&
event_relation->rd_rel->relkind != RELKIND_MATVIEW &&
event_relation->rd_rel->relkind != RELKIND_VIEW)
ereport(ERROR,
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index a22a11e..f429dd6 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -1222,6 +1222,7 @@ rewriteTargetListUD(Query *parsetree, RangeTblEntry *target_rte,
TargetEntry *tle;
if (target_relation->rd_rel->relkind == RELKIND_RELATION ||
+ target_relation->rd_rel->relkind == RELKIND_PARTITIONED_REL ||
target_relation->rd_rel->relkind == RELKIND_MATVIEW)
{
/*
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index ac50c2a..220cbfc 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -975,10 +975,13 @@ ProcessUtilitySlow(Node *parsetree,
{
Datum toast_options;
static char *validnsps[] = HEAP_RELOPT_NAMESPACES;
+ char relkind = ((CreateStmt *) stmt)->partby != NULL
+ ? RELKIND_PARTITIONED_REL
+ : RELKIND_RELATION;
/* Create the table itself */
address = DefineRelation((CreateStmt *) stmt,
- RELKIND_RELATION,
+ relkind,
InvalidOid, NULL);
EventTriggerCollectSimpleCommand(address,
secondaryObject,
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 8cb3075..a9e077f 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -33,6 +33,7 @@
#include "catalog/pg_language.h"
#include "catalog/pg_opclass.h"
#include "catalog/pg_operator.h"
+#include "catalog/pg_partitioned.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_trigger.h"
#include "catalog/pg_type.h"
@@ -315,6 +316,7 @@ static char *pg_get_indexdef_worker(Oid indexrelid, int colno,
const Oid *excludeOps,
bool attrsOnly, bool showTblSpc,
int prettyFlags);
+static char *pg_get_partkeydef_worker(Oid relId, int prettyFlags);
static char *pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
int prettyFlags);
static text *pg_get_expr_worker(text *expr, Oid relid, const char *relname,
@@ -1297,6 +1299,151 @@ pg_get_indexdef_worker(Oid indexrelid, int colno,
return buf.data;
}
+/*
+ * pg_get_partkeydef
+ *
+ * Returns the partition key specification, ie, the following:
+ *
+ * PARTITION BY { RANGE | LIST } (column [USING opclass_name] [, ...])
+ */
+Datum
+pg_get_partkeydef(PG_FUNCTION_ARGS)
+{
+ Oid relationId = PG_GETARG_OID(0);
+ int prettyFlags;
+
+ prettyFlags = PRETTYFLAG_INDENT;
+ PG_RETURN_TEXT_P(string_to_text(pg_get_partkeydef_worker(relationId,
+ prettyFlags)));
+}
+
+/*
+ * Internal workhorse to decompile a partition key definition.
+ */
+static char *
+pg_get_partkeydef_worker(Oid relId, int prettyFlags)
+{
+ Form_pg_partitioned form;
+ HeapTuple tuple;
+ oidvector *partclass;
+ List *partexprs;
+ ListCell *partexpr_item;
+ List *context;
+ Datum datum;
+ bool isnull;
+ StringInfoData buf;
+ int keyno;
+ char *str;
+ char *sep;
+
+ tuple = SearchSysCache1(PARTEDRELID, ObjectIdGetDatum(relId));
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "cache lookup failed for partition key of %u", relId);
+
+ form = (Form_pg_partitioned) GETSTRUCT(tuple);
+
+ Assert(form->partedrelid == relId);
+
+ /* Must get partclass, and partexprs the hard way */
+ datum = SysCacheGetAttr(PARTEDRELID, tuple,
+ Anum_pg_partitioned_partclass, &isnull);
+ Assert(!isnull);
+ partclass = (oidvector *) DatumGetPointer(datum);
+
+ /*
+ * Get the partition key expressions, if any. (NOTE: we do not use the
+ * relcache versions of the expressions, because we want to display
+ * non-const-folded expressions.)
+ */
+ if (!heap_attisnull(tuple, Anum_pg_partitioned_partexprs))
+ {
+ Datum exprsDatum;
+ bool isnull;
+ char *exprsString;
+
+ exprsDatum = SysCacheGetAttr(PARTEDRELID, tuple,
+ Anum_pg_partitioned_partexprs, &isnull);
+ Assert(!isnull);
+ exprsString = TextDatumGetCString(exprsDatum);
+ partexprs = (List *) stringToNode(exprsString);
+ pfree(exprsString);
+ }
+ else
+ partexprs = NIL;
+
+ partexpr_item = list_head(partexprs);
+ context = deparse_context_for(get_relation_name(relId), relId);
+
+ /*
+ * Start the partition key definition.
+ */
+ initStringInfo(&buf);
+
+ appendStringInfo(&buf, "PARTITION BY ");
+
+ switch (form->partstrat)
+ {
+ case 'l':
+ appendStringInfo(&buf, "LIST");
+ break;
+ case 'r':
+ appendStringInfo(&buf, "RANGE");
+ break;
+ }
+
+ /*
+ * Report the partition key columns
+ */
+ appendStringInfo(&buf, " (");
+ sep = "";
+ for (keyno = 0; keyno < form->partnatts; keyno++)
+ {
+ AttrNumber attnum = form->partattrs.values[keyno];
+ Oid keycoltype;
+
+ appendStringInfoString(&buf, sep);
+ sep = ", ";
+ if (attnum != 0)
+ {
+ /* Simple partition key column */
+ char *attname;
+
+ attname = get_relid_attribute_name(relId, attnum);
+ appendStringInfoString(&buf, quote_identifier(attname));
+ keycoltype = get_atttype(relId, attnum);
+ }
+ else
+ {
+ /* partition key expression */
+ Node *partkey;
+
+ if (partexpr_item == NULL)
+ elog(ERROR, "too few entries in partexprs list");
+ partkey = (Node *) lfirst(partexpr_item);
+ partexpr_item = lnext(partexpr_item);
+ /* Deparse */
+ str = deparse_expression_pretty(partkey, context, false, false,
+ prettyFlags, 0);
+
+ /* Need parens if it's not a bare function call */
+ if (partkey && IsA(partkey, FuncExpr) &&
+ ((FuncExpr *) partkey)->funcformat == COERCE_EXPLICIT_CALL)
+ appendStringInfoString(&buf, str);
+ else
+ appendStringInfo(&buf, "(%s)", str);
+ keycoltype = exprType(partkey);
+ }
+
+ /* Add the operator class name, if not default */
+ get_opclass_name(partclass->values[keyno], keycoltype, &buf);
+ }
+ appendStringInfoChar(&buf, ')');
+
+ /* Clean up */
+ ReleaseSysCache(tuple);
+
+ return buf.data;
+}
/*
* pg_get_constraintdef
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 79cc3df..e48a1f4 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -40,6 +40,7 @@
#include "catalog/index.h"
#include "catalog/indexing.h"
#include "catalog/namespace.h"
+#include "catalog/partition.h"
#include "catalog/pg_am.h"
#include "catalog/pg_amproc.h"
#include "catalog/pg_attrdef.h"
@@ -431,6 +432,7 @@ RelationParseRelOptions(Relation relation, HeapTuple tuple)
switch (relation->rd_rel->relkind)
{
case RELKIND_RELATION:
+ case RELKIND_PARTITIONED_REL:
case RELKIND_TOASTVALUE:
case RELKIND_INDEX:
case RELKIND_VIEW:
@@ -1049,6 +1051,12 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
else
relation->rd_rsdesc = NULL;
+ /* if it's a partitioned table, initialize key info */
+ if (relation->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+ RelationBuildPartitionKey(relation);
+ else
+ relation->rd_partkey = NULL;
+
/*
* if it's an index, initialize index-related information
*/
@@ -2046,6 +2054,7 @@ RelationDestroyRelation(Relation relation, bool remember_tupdesc)
MemoryContextDelete(relation->rd_rulescxt);
if (relation->rd_rsdesc)
MemoryContextDelete(relation->rd_rsdesc->rscxt);
+ FreePartitionKey(relation->rd_partkey);
if (relation->rd_fdwroutine)
pfree(relation->rd_fdwroutine);
pfree(relation);
@@ -2987,7 +2996,9 @@ RelationBuildLocalRelation(const char *relname,
/* system relations and non-table objects don't have one */
if (!IsSystemNamespace(relnamespace) &&
- (relkind == RELKIND_RELATION || relkind == RELKIND_MATVIEW))
+ (relkind == RELKIND_RELATION ||
+ relkind == RELKIND_PARTITIONED_REL ||
+ relkind == RELKIND_MATVIEW))
rel->rd_rel->relreplident = REPLICA_IDENTITY_DEFAULT;
else
rel->rd_rel->relreplident = REPLICA_IDENTITY_NOTHING;
@@ -4972,6 +4983,7 @@ load_relcache_init_file(bool shared)
rel->rd_rulescxt = NULL;
rel->trigdesc = NULL;
rel->rd_rsdesc = NULL;
+ rel->rd_partkey = NULL;
rel->rd_indexprs = NIL;
rel->rd_indpred = NIL;
rel->rd_exclops = NULL;
diff --git a/src/backend/utils/cache/syscache.c b/src/backend/utils/cache/syscache.c
index 65ffe84..a6563fe 100644
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -48,6 +48,7 @@
#include "catalog/pg_opclass.h"
#include "catalog/pg_operator.h"
#include "catalog/pg_opfamily.h"
+#include "catalog/pg_partitioned.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_range.h"
#include "catalog/pg_rewrite.h"
@@ -568,6 +569,17 @@ static const struct cachedesc cacheinfo[] = {
},
8
},
+ {PartitionedRelationId, /* PARTEDRELID */
+ PartitionedRelidIndexId,
+ 1,
+ {
+ Anum_pg_partitioned_partedrelid,
+ 0,
+ 0,
+ 0
+ },
+ 128
+ },
{ProcedureRelationId, /* PROCNAMEARGSNSP */
ProcedureNameArgsNspIndexId,
3,
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 1267afb..b3da2f7 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -1234,9 +1234,10 @@ expand_table_name_patterns(Archive *fout,
"SELECT c.oid"
"\nFROM pg_catalog.pg_class c"
"\n LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace"
- "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c')\n",
+ "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c')\n",
RELKIND_RELATION, RELKIND_SEQUENCE, RELKIND_VIEW,
- RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+ RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+ RELKIND_PARTITIONED_REL);
processSQLNamePattern(GetConnection(fout), query, cell->val, true,
false, "n.nspname", "c.relname", NULL,
"pg_catalog.pg_table_is_visible(c.oid)");
@@ -2093,6 +2094,9 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo, bool oids)
/* Skip FOREIGN TABLEs (no data to dump) */
if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
return;
+ /* Skip partitioned tables (data in partitions) */
+ if (tbinfo->relkind == RELKIND_PARTITIONED_REL)
+ return;
/* Don't dump data in unlogged tables, if so requested */
if (tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED &&
@@ -5171,6 +5175,7 @@ getTables(Archive *fout, int *numTables)
int i_reloftype;
int i_relpages;
int i_changed_acl;
+ int i_partkeydef;
/* Make sure we are in proper schema */
selectSourceSchema(fout, "pg_catalog");
@@ -5255,7 +5260,8 @@ getTables(Archive *fout, int *numTables)
"OR %s IS NOT NULL "
"OR %s IS NOT NULL"
"))"
- "AS changed_acl "
+ "AS changed_acl, "
+ "CASE WHEN c.relkind = 'P' THEN pg_catalog.pg_get_partkeydef(c.oid) ELSE NULL END AS partkeydef "
"FROM pg_class c "
"LEFT JOIN pg_depend d ON "
"(c.relkind = '%c' AND "
@@ -5267,7 +5273,7 @@ getTables(Archive *fout, int *numTables)
"(c.oid = pip.objoid AND pip.classoid = "
"(SELECT oid FROM pg_class "
"WHERE relname = 'pg_class') AND pip.objsubid = 0) "
- "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c') "
+ "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c', '%c') "
"ORDER BY c.oid",
acl_subquery->data,
racl_subquery->data,
@@ -5281,7 +5287,8 @@ getTables(Archive *fout, int *numTables)
RELKIND_SEQUENCE,
RELKIND_RELATION, RELKIND_SEQUENCE,
RELKIND_VIEW, RELKIND_COMPOSITE_TYPE,
- RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+ RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+ RELKIND_PARTITIONED_REL);
destroyPQExpBuffer(acl_subquery);
destroyPQExpBuffer(racl_subquery);
@@ -5843,6 +5850,7 @@ getTables(Archive *fout, int *numTables)
i_toastreloptions = PQfnumber(res, "toast_reloptions");
i_reloftype = PQfnumber(res, "reloftype");
i_changed_acl = PQfnumber(res, "changed_acl");
+ i_partkeydef = PQfnumber(res, "partkeydef");
if (dopt->lockWaitTimeout && fout->remoteVersion >= 70300)
{
@@ -5913,6 +5921,7 @@ getTables(Archive *fout, int *numTables)
else
tblinfo[i].checkoption = pg_strdup(PQgetvalue(res, i, i_checkoption));
tblinfo[i].toast_reloptions = pg_strdup(PQgetvalue(res, i, i_toastreloptions));
+ tblinfo[i].partkeydef = pg_strdup(PQgetvalue(res, i, i_partkeydef));
/* other fields were zeroed above */
@@ -5956,7 +5965,9 @@ getTables(Archive *fout, int *numTables)
*
* We only need to lock the table for certain components; see pg_dump.h
*/
- if (tblinfo[i].dobj.dump && tblinfo[i].relkind == RELKIND_RELATION &&
+ if (tblinfo[i].dobj.dump &&
+ (tblinfo[i].relkind == RELKIND_RELATION ||
+ tblinfo->relkind == RELKIND_PARTITIONED_REL) &&
(tblinfo[i].dobj.dump & DUMP_COMPONENTS_REQUIRING_LOCK))
{
resetPQExpBuffer(query);
@@ -6044,7 +6055,10 @@ getInherits(Archive *fout, int *numInherits)
/* find all the inheritance information */
- appendPQExpBufferStr(query, "SELECT inhrelid, inhparent FROM pg_inherits");
+ appendPQExpBufferStr(query,
+ "SELECT inhrelid, inhparent "
+ "FROM pg_inherits "
+ "WHERE inhparent NOT IN (SELECT oid FROM pg_class WHERE relkind = 'P')");
res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
@@ -15413,6 +15427,9 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
appendPQExpBufferChar(q, ')');
}
+ if (tbinfo->relkind == RELKIND_PARTITIONED_REL)
+ appendPQExpBuffer(q, "\n%s", tbinfo->partkeydef);
+
if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
appendPQExpBuffer(q, "\nSERVER %s", fmtId(srvname));
}
@@ -15473,6 +15490,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
*/
if (dopt->binary_upgrade &&
(tbinfo->relkind == RELKIND_RELATION ||
+ tbinfo->relkind == RELKIND_PARTITIONED_REL ||
tbinfo->relkind == RELKIND_FOREIGN_TABLE))
{
for (j = 0; j < tbinfo->numatts; j++)
@@ -15491,7 +15509,8 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
appendStringLiteralAH(q, fmtId(tbinfo->dobj.name), fout);
appendPQExpBufferStr(q, "::pg_catalog.regclass;\n");
- if (tbinfo->relkind == RELKIND_RELATION)
+ if (tbinfo->relkind == RELKIND_RELATION ||
+ tbinfo->relkind == RELKIND_PARTITIONED_REL)
appendPQExpBuffer(q, "ALTER TABLE ONLY %s ",
fmtId(tbinfo->dobj.name));
else
@@ -15708,6 +15727,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
* dump properties we only have ALTER TABLE syntax for
*/
if ((tbinfo->relkind == RELKIND_RELATION ||
+ tbinfo->relkind == RELKIND_PARTITIONED_REL ||
tbinfo->relkind == RELKIND_MATVIEW) &&
tbinfo->relreplident != REPLICA_IDENTITY_DEFAULT)
{
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 2bfa2d9..0292859 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -310,6 +310,7 @@ typedef struct _tableInfo
bool *inhNotNull; /* true if NOT NULL is inherited */
struct _attrDefInfo **attrdefs; /* DEFAULT expressions */
struct _constraintInfo *checkexprs; /* CHECK constraints */
+ char *partkeydef; /* partition key definition */
/*
* Stuff computed only for dumpable tables.
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 0a771bd..78a1fdb 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -764,6 +764,7 @@ permissionsList(const char *pattern)
" WHEN 'm' THEN '%s'"
" WHEN 'S' THEN '%s'"
" WHEN 'f' THEN '%s'"
+ " WHEN 'P' THEN '%s'"
" END as \"%s\",\n"
" ",
gettext_noop("Schema"),
@@ -773,6 +774,7 @@ permissionsList(const char *pattern)
gettext_noop("materialized view"),
gettext_noop("sequence"),
gettext_noop("foreign table"),
+ gettext_noop("partitioned table"),
gettext_noop("Type"));
printACLColumn(&buf, "c.relacl");
@@ -819,7 +821,7 @@ permissionsList(const char *pattern)
appendPQExpBufferStr(&buf, "\nFROM pg_catalog.pg_class c\n"
" LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace\n"
- "WHERE c.relkind IN ('r', 'v', 'm', 'S', 'f')\n");
+ "WHERE c.relkind IN ('r', 'v', 'm', 'S', 'f', 'P')\n");
/*
* Unless a schema pattern is specified, we suppress system and temp
@@ -1463,8 +1465,8 @@ describeOneTableDetails(const char *schemaname,
* types, and foreign tables (c.f. CommentObject() in comment.c).
*/
if (tableinfo.relkind == 'r' || tableinfo.relkind == 'v' ||
- tableinfo.relkind == 'm' ||
- tableinfo.relkind == 'f' || tableinfo.relkind == 'c')
+ tableinfo.relkind == 'm' || tableinfo.relkind == 'f' ||
+ tableinfo.relkind == 'c' || tableinfo.relkind == 'P')
appendPQExpBufferStr(&buf, ", pg_catalog.col_description(a.attrelid, a.attnum)");
}
@@ -1529,6 +1531,14 @@ describeOneTableDetails(const char *schemaname,
printfPQExpBuffer(&title, _("Foreign table \"%s.%s\""),
schemaname, relationname);
break;
+ case 'P':
+ if (tableinfo.relpersistence == 'u')
+ printfPQExpBuffer(&title, _("Unlogged partitioned table \"%s.%s\""),
+ schemaname, relationname);
+ else
+ printfPQExpBuffer(&title, _("Partitioned table \"%s.%s\""),
+ schemaname, relationname);
+ break;
default:
/* untranslated unknown relkind */
printfPQExpBuffer(&title, "?%c? \"%s.%s\"",
@@ -1542,8 +1552,8 @@ describeOneTableDetails(const char *schemaname,
cols = 2;
if (tableinfo.relkind == 'r' || tableinfo.relkind == 'v' ||
- tableinfo.relkind == 'm' ||
- tableinfo.relkind == 'f' || tableinfo.relkind == 'c')
+ tableinfo.relkind == 'm' || tableinfo.relkind == 'f' ||
+ tableinfo.relkind == 'c' || tableinfo.relkind == 'P')
{
show_modifiers = true;
headers[cols++] = gettext_noop("Modifiers");
@@ -1563,12 +1573,12 @@ describeOneTableDetails(const char *schemaname,
{
headers[cols++] = gettext_noop("Storage");
if (tableinfo.relkind == 'r' || tableinfo.relkind == 'm' ||
- tableinfo.relkind == 'f')
+ tableinfo.relkind == 'f' || tableinfo.relkind == 'P')
headers[cols++] = gettext_noop("Stats target");
/* Column comments, if the relkind supports this feature. */
if (tableinfo.relkind == 'r' || tableinfo.relkind == 'v' ||
- tableinfo.relkind == 'm' ||
- tableinfo.relkind == 'c' || tableinfo.relkind == 'f')
+ tableinfo.relkind == 'm' || tableinfo.relkind == 'c' ||
+ tableinfo.relkind == 'f' || tableinfo.relkind == 'P')
headers[cols++] = gettext_noop("Description");
}
@@ -1668,7 +1678,7 @@ describeOneTableDetails(const char *schemaname,
/* Statistics target, if the relkind supports this feature */
if (tableinfo.relkind == 'r' || tableinfo.relkind == 'm' ||
- tableinfo.relkind == 'f')
+ tableinfo.relkind == 'f' || tableinfo.relkind == 'P')
{
printTableAddCell(&cont, PQgetvalue(res, i, firstvcol + 1),
false, false);
@@ -1676,14 +1686,33 @@ describeOneTableDetails(const char *schemaname,
/* Column comments, if the relkind supports this feature. */
if (tableinfo.relkind == 'r' || tableinfo.relkind == 'v' ||
- tableinfo.relkind == 'm' ||
- tableinfo.relkind == 'c' || tableinfo.relkind == 'f')
+ tableinfo.relkind == 'm' || tableinfo.relkind == 'c' ||
+ tableinfo.relkind == 'f' || tableinfo.relkind == 'P')
printTableAddCell(&cont, PQgetvalue(res, i, firstvcol + 2),
false, false);
}
}
/* Make footers */
+ if (pset.sversion >= 90600 && tableinfo.relkind == 'P')
+ {
+ /* Get the partition key information */
+ PGresult *result;
+ char *partkeydef;
+
+ printfPQExpBuffer(&buf,
+ "SELECT pg_catalog.pg_get_partkeydef('%s'::pg_catalog.oid);",
+ oid);
+ result = PSQLexec(buf.data);
+ if (!result || PQntuples(result) != 1)
+ goto error_return;
+
+ partkeydef = PQgetvalue(result, 0, 0);
+ printfPQExpBuffer(&tmpbuf, _("Partition Key: %s"), partkeydef);
+ printTableAddFooter(&cont, tmpbuf.data);
+ PQclear(result);
+ }
+
if (tableinfo.relkind == 'i')
{
/* Footer information about an index */
@@ -1822,7 +1851,7 @@ describeOneTableDetails(const char *schemaname,
PQclear(result);
}
else if (tableinfo.relkind == 'r' || tableinfo.relkind == 'm' ||
- tableinfo.relkind == 'f')
+ tableinfo.relkind == 'f' || tableinfo.relkind == 'P')
{
/* Footer information about a table */
PGresult *result = NULL;
@@ -2381,7 +2410,7 @@ describeOneTableDetails(const char *schemaname,
* Finish printing the footer information about a table.
*/
if (tableinfo.relkind == 'r' || tableinfo.relkind == 'm' ||
- tableinfo.relkind == 'f')
+ tableinfo.relkind == 'f' || tableinfo.relkind == 'P')
{
PGresult *result;
int tuples;
@@ -2592,7 +2621,7 @@ add_tablespace_footer(printTableContent *const cont, char relkind,
Oid tablespace, const bool newline)
{
/* relkinds for which we support tablespaces */
- if (relkind == 'r' || relkind == 'm' || relkind == 'i')
+ if (relkind == 'r' || relkind == 'm' || relkind == 'i' || relkind == 'P')
{
/*
* We ignore the database default tablespace so that users not using
@@ -2926,6 +2955,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
" WHEN 'S' THEN '%s'"
" WHEN 's' THEN '%s'"
" WHEN 'f' THEN '%s'"
+ " WHEN 'P' THEN '%s'"
" END as \"%s\",\n"
" pg_catalog.pg_get_userbyid(c.relowner) as \"%s\"",
gettext_noop("Schema"),
@@ -2937,6 +2967,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
gettext_noop("sequence"),
gettext_noop("special"),
gettext_noop("foreign table"),
+ gettext_noop("partitioned table"),
gettext_noop("Type"),
gettext_noop("Owner"));
@@ -2975,7 +3006,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
appendPQExpBufferStr(&buf, "\nWHERE c.relkind IN (");
if (showTables)
- appendPQExpBufferStr(&buf, "'r',");
+ appendPQExpBufferStr(&buf, "'r', 'P',");
if (showViews)
appendPQExpBufferStr(&buf, "'v',");
if (showMatViews)
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index a62ffe6..741ac46 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -427,7 +427,7 @@ static const SchemaQuery Query_for_list_of_tables = {
/* catname */
"pg_catalog.pg_class c",
/* selcondition */
- "c.relkind IN ('r')",
+ "c.relkind IN ('r', 'P')",
/* viscondition */
"pg_catalog.pg_table_is_visible(c.oid)",
/* namespace */
@@ -458,7 +458,7 @@ static const SchemaQuery Query_for_list_of_updatables = {
/* catname */
"pg_catalog.pg_class c",
/* selcondition */
- "c.relkind IN ('r', 'f', 'v')",
+ "c.relkind IN ('r', 'f', 'v', 'P')",
/* viscondition */
"pg_catalog.pg_table_is_visible(c.oid)",
/* namespace */
@@ -488,7 +488,7 @@ static const SchemaQuery Query_for_list_of_tsvmf = {
/* catname */
"pg_catalog.pg_class c",
/* selcondition */
- "c.relkind IN ('r', 'S', 'v', 'm', 'f')",
+ "c.relkind IN ('r', 'S', 'v', 'm', 'f', 'P')",
/* viscondition */
"pg_catalog.pg_table_is_visible(c.oid)",
/* namespace */
diff --git a/src/include/catalog/indexing.h b/src/include/catalog/indexing.h
index ca5eb3d..cadb741 100644
--- a/src/include/catalog/indexing.h
+++ b/src/include/catalog/indexing.h
@@ -319,6 +319,9 @@ DECLARE_UNIQUE_INDEX(pg_replication_origin_roiident_index, 6001, on pg_replicati
DECLARE_UNIQUE_INDEX(pg_replication_origin_roname_index, 6002, on pg_replication_origin using btree(roname text_pattern_ops));
#define ReplicationOriginNameIndex 6002
+DECLARE_UNIQUE_INDEX(pg_partitioned_partedrelid_index, 3351, on pg_partitioned using btree(partedrelid oid_ops));
+#define PartitionedRelidIndexId 3351
+
/* last step of initialization script: build the indexes declared above */
BUILD_INDICES
diff --git a/src/include/catalog/partition.h b/src/include/catalog/partition.h
new file mode 100644
index 0000000..2c876c4
--- /dev/null
+++ b/src/include/catalog/partition.h
@@ -0,0 +1,55 @@
+/*-------------------------------------------------------------------------
+ *
+ * partition.h
+ * Header file for structures and utility functions related to
+ * partitioning
+ *
+ * Copyright (c) 2007-2016, PostgreSQL Global Development Group
+ *
+ * src/include/utils/partition.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PARTITION_H
+#define PARTITION_H
+
+#include "fmgr.h"
+#include "utils/relcache.h"
+
+/* Type and collation information for partition key columns */
+typedef struct
+{
+ Oid *typid;
+ int32 *typmod;
+ int16 *typlen;
+ bool *typbyval;
+ char *typalign;
+ Oid *typcoll;
+} KeyTypeCollInfo;
+
+/* Partition key information for one level */
+typedef struct PartitionKeyData
+{
+ char strategy; /* partition strategy */
+ int16 partnatts; /* number of partition attributes */
+ AttrNumber *partattrs; /* partition attnums */
+ Oid *partopfamily; /* OIDs of opfamily per col */
+ FmgrInfo *partsupfunc; /* lookup info for support funcs */
+ List *partexprs; /* partition key expressions, if any */
+ KeyTypeCollInfo *tcinfo; /* type and collation info (all columns) */
+} PartitionKeyData;
+
+typedef struct PartitionKeyData *PartitionKey;
+
+extern void StorePartitionKey(Relation rel,
+ char strategy,
+ int16 partnatts,
+ AttrNumber *partattrs,
+ List *partexprs,
+ Oid *partopclass);
+extern void RemovePartitionKeyByRelId(Oid relid);
+extern void RelationBuildPartitionKey(Relation relation);
+extern void FreePartitionKey(PartitionKey key);
+extern bool relid_is_partitioned(Oid relid);
+
+#endif /* PARTITION_H */
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index fdc67c2..149f619 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -153,6 +153,7 @@ DESCR("");
#define RELKIND_RELATION 'r' /* ordinary table */
+#define RELKIND_PARTITIONED_REL 'P' /* partitioned table */
#define RELKIND_INDEX 'i' /* secondary index */
#define RELKIND_SEQUENCE 'S' /* sequence object */
#define RELKIND_TOASTVALUE 't' /* for out-of-line values */
diff --git a/src/include/catalog/pg_partitioned.h b/src/include/catalog/pg_partitioned.h
new file mode 100644
index 0000000..edc52db
--- /dev/null
+++ b/src/include/catalog/pg_partitioned.h
@@ -0,0 +1,67 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_partitioned.h
+ * definition of the system "partitioned" relation (pg_partitioned)
+ * along with the relation's initial contents.
+ *
+ *
+ * Portions Copyright (c) 1996-2015, PostgreSQL Global Development Group
+ *
+ * $PostgreSQL: pgsql/src/include/catalog/pg_partitioned.h $
+ *
+ * NOTES
+ * the genbki.sh script reads this file and generates .bki
+ * information from the DATA() statements.
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_PARTITIONED_REL_H
+#define PG_PARTITIONED_REL_H
+
+#include "catalog/genbki.h"
+
+/* ----------------
+ * pg_partitioned definition. cpp turns this into
+ * typedef struct FormData_pg_partitioned
+ * ----------------
+ */
+#define PartitionedRelationId 3350
+
+CATALOG(pg_partitioned,3350) BKI_WITHOUT_OIDS
+{
+ Oid partedrelid; /* partitioned table oid */
+ char partstrat; /* partition key strategy */
+ int16 partnatts; /* number of partition key columns */
+
+ /* variable-length fields start here, but we allow direct access to partattrs */
+ int2vector partattrs; /* attribute numbers of partition key
+ * columns */
+
+#ifdef CATALOG_VARLEN
+ oidvector partclass; /* operator class to compare keys */
+ pg_node_tree partexprs; /* expression trees for partition key members
+ * that are not simple column references; one
+ * for each zero entry in partkey[] */
+#endif
+} FormData_pg_partitioned;
+
+/* ----------------
+ * Form_pg_partitioned corresponds to a pointer to a tuple with
+ * the format of pg_partitioned relation.
+ * ----------------
+ */
+typedef FormData_pg_partitioned *Form_pg_partitioned;
+
+/* ----------------
+ * compiler constants for pg_partitioned
+ * ----------------
+ */
+#define Natts_pg_partitioned 6
+#define Anum_pg_partitioned_partedrelid 1
+#define Anum_pg_partitioned_partstrat 2
+#define Anum_pg_partitioned_partnatts 3
+#define Anum_pg_partitioned_partattrs 4
+#define Anum_pg_partitioned_partclass 5
+#define Anum_pg_partitioned_partexprs 6
+
+#endif /* PG_PARTITIONED_REL_H */
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index d94943b..24d7c5f 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -1969,6 +1969,8 @@ DATA(insert OID = 1642 ( pg_get_userbyid PGNSP PGUID 12 1 0 0 0 f f f f t f
DESCR("role name by OID (with fallback)");
DATA(insert OID = 1643 ( pg_get_indexdef PGNSP PGUID 12 1 0 0 0 f f f f t f s s 1 0 25 "26" _null_ _null_ _null_ _null_ _null_ pg_get_indexdef _null_ _null_ _null_ ));
DESCR("index description");
+DATA(insert OID = 3352 ( pg_get_partkeydef PGNSP PGUID 12 1 0 0 0 f f f f t f s s 1 0 25 "26" _null_ _null_ _null_ _null_ _null_ pg_get_partkeydef _null_ _null_ _null_ ));
+DESCR("partition key description");
DATA(insert OID = 1662 ( pg_get_triggerdef PGNSP PGUID 12 1 0 0 0 f f f f t f s s 1 0 25 "26" _null_ _null_ _null_ _null_ _null_ pg_get_triggerdef _null_ _null_ _null_ ));
DESCR("trigger description");
DATA(insert OID = 1387 ( pg_get_constraintdef PGNSP PGUID 12 1 0 0 0 f f f f t f s s 1 0 25 "26" _null_ _null_ _null_ _null_ _null_ pg_get_constraintdef _null_ _null_ _null_ ));
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index 28024f8..ceeb41f 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -42,8 +42,8 @@ extern bool CheckIndexCompatible(Oid oldId,
List *attributeList,
List *exclusionOpNames);
extern Oid GetDefaultOpClass(Oid type_id, Oid am_id);
-extern Oid GetIndexOpClass(List *opclass, Oid attrType,
- char *accessMethodName, Oid accessMethodId);
+extern Oid GetIndexOpClass(List *opclass, Oid attrType,
+ char *accessMethodName, Oid accessMethodId);
/* commands/functioncmds.c */
extern ObjectAddress CreateFunction(CreateFunctionStmt *stmt, const char *queryString);
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
index 01976a1..027665c 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -720,6 +720,7 @@ extern Datum pg_get_viewdef_wrap(PG_FUNCTION_ARGS);
extern Datum pg_get_viewdef_name(PG_FUNCTION_ARGS);
extern Datum pg_get_viewdef_name_ext(PG_FUNCTION_ARGS);
extern Datum pg_get_indexdef(PG_FUNCTION_ARGS);
+extern Datum pg_get_partkeydef(PG_FUNCTION_ARGS);
extern Datum pg_get_indexdef_ext(PG_FUNCTION_ARGS);
extern Datum pg_get_triggerdef(PG_FUNCTION_ARGS);
extern Datum pg_get_triggerdef_ext(PG_FUNCTION_ARGS);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index a0ba417..2e43ebf 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -90,6 +90,8 @@ typedef struct RelationData
/* use "struct" here to avoid needing to include rowsecurity.h: */
struct RowSecurityDesc *rd_rsdesc; /* row security policies, or NULL */
+ struct PartitionKeyData *rd_partkey; /* partition key, or NULL */
+
/* data managed by RelationGetIndexList: */
List *rd_indexlist; /* list of OIDs of indexes on relation */
Oid rd_oidindex; /* OID of unique index on OID, if any */
@@ -502,6 +504,12 @@ typedef struct ViewOptions
RelationNeedsWAL(relation) && \
!IsCatalogRelation(relation))
+/*
+ * RelationGetPartitionKey
+ * Returns partition key for a relation.
+ */
+#define RelationGetPartitionKey(relation) ((relation)->rd_partkey)
+
/* routines in utils/cache/relcache.c */
extern void RelationIncrementReferenceCount(Relation rel);
extern void RelationDecrementReferenceCount(Relation rel);
diff --git a/src/include/utils/syscache.h b/src/include/utils/syscache.h
index 256615b..e727842 100644
--- a/src/include/utils/syscache.h
+++ b/src/include/utils/syscache.h
@@ -72,6 +72,7 @@ enum SysCacheIdentifier
OPEROID,
OPFAMILYAMNAMENSP,
OPFAMILYOID,
+ PARTEDRELID,
PROCNAMEARGSNSP,
PROCOID,
RANGETYPE,
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index aaae9f9..a4cc948 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -326,9 +326,35 @@ ERROR: cannot use a constant expression as partition key
CREATE TABLE fail_agg_in_key (
a int
) PARTITION BY RANGE ((avg(a)));
-ERROR: aggregate functions are not allowed in partition key expressions
+ERROR: aggregate functions are not allowed in partition key expression
CREATE TABLE fail_window_fun_in_key (
a int,
b int
) PARTITION BY RANGE ((avg(a) OVER (PARTITION BY b)));
-ERROR: window functions are not allowed in partition key expressions
+ERROR: window functions are not allowed in partition key expression
+-- check relkind
+CREATE TABLE check_relkind (
+ a int
+) PARTITION BY RANGE (a);
+SELECT relkind FROM pg_class WHERE relname = 'check_relkind';
+ relkind
+---------
+ P
+(1 row)
+
+-- cannot drop column that is part of the partition key
+CREATE TABLE no_drop_alter_partcol (
+ a int
+) PARTITION BY RANGE (a);
+ALTER TABLE no_drop_alter_partcol DROP COLUMN a;
+ERROR: cannot drop column named in partition key
+ALTER TABLE no_drop_alter_partcol ALTER COLUMN a TYPE char(5);
+ERROR: cannot alter column named in partition key
+CREATE TABLE no_drop_alter_partexpr (
+ a text
+) PARTITION BY RANGE ((substring(a from 1 for 1)));
+ALTER TABLE no_drop_alter_partexpr DROP COLUMN a;
+ERROR: cannot drop column referenced in partition key expression
+ALTER TABLE no_drop_alter_partcol ALTER COLUMN a TYPE char(5);
+ERROR: cannot alter column named in partition key
+DROP TABLE check_relkind, no_drop_alter_partcol, no_drop_alter_partexpr;
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index 1c087a3..b1e473d 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -118,6 +118,7 @@ pg_namespace|t
pg_opclass|t
pg_operator|t
pg_opfamily|t
+pg_partitioned|t
pg_pltemplate|t
pg_policy|t
pg_proc|t
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index df47656..022f38b 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -337,3 +337,24 @@ CREATE TABLE fail_window_fun_in_key (
a int,
b int
) PARTITION BY RANGE ((avg(a) OVER (PARTITION BY b)));
+
+-- check relkind
+CREATE TABLE check_relkind (
+ a int
+) PARTITION BY RANGE (a);
+SELECT relkind FROM pg_class WHERE relname = 'check_relkind';
+
+-- cannot drop column that is part of the partition key
+CREATE TABLE no_drop_alter_partcol (
+ a int
+) PARTITION BY RANGE (a);
+ALTER TABLE no_drop_alter_partcol DROP COLUMN a;
+ALTER TABLE no_drop_alter_partcol ALTER COLUMN a TYPE char(5);
+
+CREATE TABLE no_drop_alter_partexpr (
+ a text
+) PARTITION BY RANGE ((substring(a from 1 for 1)));
+ALTER TABLE no_drop_alter_partexpr DROP COLUMN a;
+ALTER TABLE no_drop_alter_partcol ALTER COLUMN a TYPE char(5);
+
+DROP TABLE check_relkind, no_drop_alter_partcol, no_drop_alter_partexpr;
--
1.7.1
[text/x-diff] 0004-Add-syntax-to-create-partitions-v5.patch (77.8K, ../../5742D5F5.3020101@lab.ntt.co.jp/5-0004-Add-syntax-to-create-partitions-v5.patch)
download | inline diff:
From b200e2ff50718090cee87d4b9bdeb7c39d5f4d69 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Thu, 3 Mar 2016 18:29:15 +0900
Subject: [PATCH 4/7] Add syntax to create partitions.
As follows:
CREATE TABLE name PARTITION OF parent
[ ( { column_name WITH OPTION column_constraint | table_constraint } [, ...] ) ]
partition_bound_spec [ PARTITION BY partition_key_spec ]
ALTER TABLE parent ATTACH PARTITION name
partition_bound_spec [ VALIDATE | NO VALIDATE ]
ALTER TABLE parent DETACH PARTITION name
partition_bound_spec refers to either list partition bound of form:
FOR VALUES IN (list-of-expressions)
or range partition bound of form:
FOR VALUES range_spec
where range_spec is:
START lower-bound [ INCLUSIVE | EXCLUSIVE ] END upper-bound [ INCLUSIVE | EXCLUSIVE ]
lower-bound and upper-bound is:
{ ( expression [,...] ) | UNBOUNDED }
In the above, each of lower-bound and upper-bound is either a single
expression for a single column key or list of expressions for a multi-
column key or empty. Note that in the absence of explicit specification,
lower-bound is inclusive and upper-bound is exclusive.
A partition is created as inheritance child of the parent with certain
differences from regular inheritance behavior. For example, when a new
partition is created, it cannot contain its own column definitions or
constraints (unlike CREATE TABLE child(...) INHERITS(parent)). When
"attaching" a table as partition of parent, it cannot contain columns
that are not contained in the parent (unlike ALTER TABLE child INHERIT
parent). No dependency is created between a partition and the parent.
One cannot create regular inheritance children of a partitioned table.
Whereas this commit enables inheritance, it does not yet store partition
bound info anywhere, IOW, the fact that a given inheritance child is a
"partition" is not persisted.
---
doc/src/sgml/ref/alter_table.sgml | 57 +++++
doc/src/sgml/ref/create_table.sgml | 77 ++++++-
src/backend/commands/createas.c | 1 +
src/backend/commands/sequence.c | 1 +
src/backend/commands/tablecmds.c | 490 +++++++++++++++++++++++++++---------
src/backend/commands/view.c | 1 +
src/backend/nodes/copyfuncs.c | 48 ++++
src/backend/nodes/equalfuncs.c | 42 +++
src/backend/nodes/outfuncs.c | 27 ++
src/backend/parser/gram.y | 167 ++++++++++++-
src/backend/parser/parse_agg.c | 7 +-
src/backend/parser/parse_expr.c | 23 ++
src/backend/parser/parse_utilcmd.c | 318 +++++++++++++++++++++++
src/include/catalog/partition.h | 1 +
src/include/nodes/nodes.h | 3 +
src/include/nodes/parsenodes.h | 43 +++-
src/include/parser/kwlist.h | 3 +
src/include/parser/parse_node.h | 3 +-
18 files changed, 1182 insertions(+), 130 deletions(-)
diff --git a/doc/src/sgml/ref/alter_table.sgml b/doc/src/sgml/ref/alter_table.sgml
index 5ca211e..97744c9 100644
--- a/doc/src/sgml/ref/alter_table.sgml
+++ b/doc/src/sgml/ref/alter_table.sgml
@@ -77,12 +77,15 @@ ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable>
NOT OF
OWNER TO { <replaceable class="PARAMETER">new_owner</replaceable> | CURRENT_USER | SESSION_USER }
REPLICA IDENTITY { DEFAULT | USING INDEX <replaceable class="PARAMETER">index_name</replaceable> | FULL | NOTHING }
+ ATTACH PARTITION <replaceable class="PARAMETER">name</replaceable> <replaceable class="PARAMETER">partition_bound_spec</replaceable> [ VALIDATE | NO VALIDATE ]
+ DETACH PARTITION <replaceable class="PARAMETER">name</replaceable>
<phrase>and <replaceable class="PARAMETER">table_constraint_using_index</replaceable> is:</phrase>
[ CONSTRAINT <replaceable class="PARAMETER">constraint_name</replaceable> ]
{ UNIQUE | PRIMARY KEY } USING INDEX <replaceable class="PARAMETER">index_name</replaceable>
[ DEFERRABLE | NOT DEFERRABLE ] [ INITIALLY DEFERRED | INITIALLY IMMEDIATE ]
+
</synopsis>
</refsynopsisdiv>
@@ -704,6 +707,51 @@ ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable>
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>ATTACH PARTITION</literal></term>
+ <listitem>
+ <para>
+ This form attaches an existing table as partition of the target table,
+ provided the target table is partitioned. The partition bound
+ specification must correspond with partition method and key of the
+ partitioned table. The table being attached must have all the columns
+ as the target table and no more. Also, it must have all the matching
+ constraints as the target table. That includes <literal>NOT NULL</>
+ and <literal>CHECK</> constraints. <literal>UNIQUE</literal>,
+ <literal>PRIMARY KEY</literal>, and <literal>FOREIGN KEY</literal>
+ constraints are not considered, but this might change in the future.
+ </para>
+
+ <para>
+ If <literal>NO VALIDATE</literal> clause is specified, existing rows in
+ source table are are not checked to see if they violate the partition
+ bound specification. This allows to quickly load a large amount of data
+ into the partitioned table. The default behavior is to perform the
+ check, as if <literal>VALIDATE</literal> clause were specified.
+ </para>
+
+ <para>
+ Note that once the table becomes a partition, it will be dropped or
+ truncated when the parent table is dropped or truncated. Dropping
+ it directly using <literal>DROP TABLE</literal> will fail; it must
+ first be detached from the parent. However, truncating a partition
+ directly works.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><literal>DETACH PARTITION</literal></term>
+ <listitem>
+ <para>
+ This form detaches specified partition of the target table, provided the
+ target table is partitioned and the specified partition exists. The
+ resulting table continues to exist as a standalone table with no ties
+ remaining with the parent partitioned table.
+ </para>
+ </listitem>
+ </varlistentry>
+
</variablelist>
</para>
@@ -936,6 +984,15 @@ ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable>
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><replaceable class="PARAMETER">partition_bound_spec</replaceable></term>
+ <listitem>
+ <para>
+ The partition bound specification for a new partition.
+ </para>
+ </listitem>
+ </varlistentry>
+
</variablelist>
</refsect1>
diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml
index 70a7579..79fb521 100644
--- a/doc/src/sgml/ref/create_table.sgml
+++ b/doc/src/sgml/ref/create_table.sgml
@@ -44,6 +44,17 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
[ ON COMMIT { PRESERVE ROWS | DELETE ROWS | DROP } ]
[ TABLESPACE <replaceable class="PARAMETER">tablespace_name</replaceable> ]
+CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name</replaceable>
+ PARTITION OF <replaceable class="PARAMETER">parent_table</replaceable> [ (
+ { <replaceable class="PARAMETER">column_name</replaceable> WITH OPTIONS [ <replaceable class="PARAMETER">column_constraint</replaceable> [ ... ] ]
+ | <replaceable>table_constraint</replaceable> }
+ [, ... ]
+) ] <replaceable class="PARAMETER">partition_bound_spec</replaceable>
+[ PARTITION BY {RANGE | LIST} ( { <replaceable class="parameter">column_name</replaceable> | ( <replaceable class="parameter">expression</replaceable> ) } [ <replaceable class="parameter">opclass</replaceable> ] [, ...] )
+[ WITH ( <replaceable class="PARAMETER">storage_parameter</replaceable> [= <replaceable class="PARAMETER">value</replaceable>] [, ... ] ) | WITH OIDS | WITHOUT OIDS ]
+[ ON COMMIT { PRESERVE ROWS | DELETE ROWS | DROP } ]
+[ TABLESPACE <replaceable class="PARAMETER">tablespace_name</replaceable> ]
+
<phrase>where <replaceable class="PARAMETER">column_constraint</replaceable> is:</phrase>
[ CONSTRAINT <replaceable class="PARAMETER">constraint_name</replaceable> ]
@@ -72,6 +83,10 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
{ INCLUDING | EXCLUDING } { DEFAULTS | CONSTRAINTS | INDEXES | STORAGE | COMMENTS | ALL }
+<phrase>and <replaceable class="PARAMETER">partition_bound_spec</replaceable> is:</phrase>
+
+FOR VALUES { <replaceable class="PARAMETER">list_spec</replaceable> | <replaceable class="PARAMETER">range_spec</replaceable> }
+
<phrase><replaceable class="PARAMETER">index_parameters</replaceable> in <literal>UNIQUE</literal>, <literal>PRIMARY KEY</literal>, and <literal>EXCLUDE</literal> constraints are:</phrase>
[ WITH ( <replaceable class="PARAMETER">storage_parameter</replaceable> [= <replaceable class="PARAMETER">value</replaceable>] [, ... ] ) ]
@@ -80,8 +95,20 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
<phrase><replaceable class="PARAMETER">exclude_element</replaceable> in an <literal>EXCLUDE</literal> constraint is:</phrase>
{ <replaceable class="parameter">column_name</replaceable> | ( <replaceable class="parameter">expression</replaceable> ) } [ <replaceable class="parameter">opclass</replaceable> ] [ ASC | DESC ] [ NULLS { FIRST | LAST } ]
-</synopsis>
+<phrase><replaceable class="PARAMETER">list_spec</replaceable> in <literal>FOR VALUES</literal> is:</phrase>
+
+IN ( <replaceable class="PARAMETER">expression</replaceable> [, ...] )
+
+<phrase><replaceable class="PARAMETER">range_spec</replaceable> in <literal>FOR VALUES</literal> is:</phrase>
+
+START <replaceable class="PARAMETER">lower-bound</replaceable> [ INCLUSIVE | EXCLUSIVE ] END <replaceable class="PARAMETER">upper-bound</replaceable> [ INCLUSIVE | EXCLUSIVE ]
+
+<phrase>where <replaceable class="PARAMETER">lower-bound</replaceable> and <replaceable class="PARAMETER">upper-bound</replaceable> are:</phrase>
+
+{ ( <replaceable class="PARAMETER">expression</replaceable> [, ...] ) | UNBOUNDED }
+
+</synopsis>
</refsynopsisdiv>
<refsect1 id="SQL-CREATETABLE-description">
@@ -232,6 +259,49 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
</varlistentry>
<varlistentry>
+ <term><literal>PARTITION OF <replaceable class="PARAMETER">parent_table</replaceable></literal></term>
+ <listitem>
+ <para>
+ Creates the table as <firstterm>partition</firstterm> of the specified
+ parent table (name optionally schema-qualified).
+ </para>
+
+ <para>
+ A partition bound specification must be present and must correspond with
+ partition method and key of the parent table. It is checked using the
+ specification that the new partition does not overlap with any existing
+ partitions of the parent.
+ </para>
+
+ <para>
+ A partition cannot have columns other than those inherited from the
+ parent. That includes the <structfield>oid</> column, which can be
+ specified using the <literal>WITH (OIDS)</literal> clause. On the other
+ hand, if parent has the <structfield>oid</> column, the partition
+ inherits the same, overriding the <literal>WITH (OIDS=FALSE)</literal>
+ clause, if any. Defaults and constraints can optionally be specified
+ for each of the inherited columns, which override those in the parent.
+ One can also specify table constraints, in addition to those inherited
+ from the parent. Note that all subsequent schema modifications to the
+ parent propagate to partition.
+ </para>
+
+ <para>
+ Any data row subsequently inserted into the parent table is mapped to
+ and stored in the partition, provided partition key of the row falls
+ within the partition bounds.
+ </para>
+
+ <para>
+ A partition is dropped or truncated when the parent table is dropped or
+ truncated. Dropping it directly using <literal>DROP TABLE</literal>
+ will fail; it must first be <firstterm>detached</> from the parent.
+ However, truncating a partition directly works.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><replaceable class="PARAMETER">column_name</replaceable></term>
<listitem>
<para>
@@ -340,6 +410,11 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
</para>
<para>
+ When specifying for a table being created as partition, one needs to
+ use column names from the parent table as part of the key.
+ </para>
+
+ <para>
Currently, there are following limitations on definition of partitioned
tables: one cannot specify any UNIQUE, PRIMARY KEY, EXCLUDE and/or
FOREIGN KEY constraints.
diff --git a/src/backend/commands/createas.c b/src/backend/commands/createas.c
index cb7a145..427470c 100644
--- a/src/backend/commands/createas.c
+++ b/src/backend/commands/createas.c
@@ -342,6 +342,7 @@ intorel_startup(DestReceiver *self, int operation, TupleDesc typeinfo)
col->is_local = true;
col->is_not_null = false;
col->is_from_type = false;
+ col->is_for_partition = false;
col->storage = 0;
col->raw_default = NULL;
col->cooked_default = NULL;
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index 5dcd4cf..21d9f96 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -159,6 +159,7 @@ DefineSequence(CreateSeqStmt *seq)
coldef->is_local = true;
coldef->is_not_null = true;
coldef->is_from_type = false;
+ coldef->is_for_partition = false;
coldef->storage = 0;
coldef->raw_default = NULL;
coldef->cooked_default = NULL;
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 87bf466..192cae9 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -279,7 +279,8 @@ typedef struct
static void truncate_check_rel(Relation rel);
static List *MergeAttributes(List *schema, List *supers, char relpersistence,
- List **supOids, List **supconstr, int *supOidCount);
+ List **supOids, List **supconstr, int *supOidCount,
+ bool is_partition);
static bool MergeCheckConstraint(List *constraints, char *name, Node *expr);
static void MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel);
static void MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel);
@@ -448,6 +449,11 @@ static void ComputePartitionAttrs(Oid relid, List *partParams,
static bool find_attr_reference_walker(Node *node,
find_attr_reference_context *context);
static bool is_partition_attr(Relation rel, AttrNumber attnum, bool *is_expr);
+static void CreateInheritance(Relation child_rel, Relation parent_rel);
+static void RemoveInheritance(Relation child_rel, Relation parent_rel);
+static ObjectAddress ATExecAttachPartition(List **wqueue, Relation rel,
+ PartitionCmd *cmd);
+static ObjectAddress ATExecDetachPartition(Relation rel, RangeVar *name);
/* ----------------------------------------------------------------
@@ -590,10 +596,16 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
/*
* Look up inheritance ancestors and generate relation schema, including
* inherited attributes.
+ *
+ * The last parameter implicitly specifies that the table is being created
+ * as partition and schema consists of columns definitions corresponding
+ * to non-dropped columns of the parent constructed such that each
+ * attribute of the table is created as inherited and non-local.
*/
schema = MergeAttributes(schema, stmt->inhRelations,
stmt->relation->relpersistence,
- &inheritOids, &old_constraints, &parentOidCount);
+ &inheritOids, &old_constraints, &parentOidCount,
+ stmt->partbound != NULL);
/*
* Create a tuple descriptor from the relation schema. Note that this
@@ -1452,7 +1464,8 @@ storage_name(char c)
*/
static List *
MergeAttributes(List *schema, List *supers, char relpersistence,
- List **supOids, List **supconstr, int *supOidCount)
+ List **supOids, List **supconstr, int *supOidCount,
+ bool is_partition)
{
ListCell *entry;
List *inhSchema = NIL;
@@ -1497,8 +1510,8 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
/*
* Typed table column option that does not belong to a column from
- * the type. This works because the columns from the type come
- * first in the list.
+ * the type or the partition parent. This works because the columns
+ * from the type or the partition parent come first in the list.
*/
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_COLUMN),
@@ -1525,6 +1538,18 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
coldef->is_from_type = false;
list_delete_cell(schema, rest, prev);
}
+ else if (coldef->is_for_partition)
+ {
+ /*
+ * merge the column options into the column from the parent
+ */
+ coldef->is_not_null = restdef->is_not_null;
+ coldef->raw_default = restdef->raw_default;
+ coldef->cooked_default = restdef->cooked_default;
+ coldef->constraints = restdef->constraints;
+ coldef->is_for_partition = false; /* job is done */
+ list_delete_cell(schema, rest, prev);
+ }
else
ereport(ERROR,
(errcode(ERRCODE_DUPLICATE_COLUMN),
@@ -1563,8 +1588,12 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
*/
relation = heap_openrv(parent, ShareUpdateExclusiveLock);
- /* Cannot inherit from partitioned tables */
- if (relation->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+ /*
+ * Cannot inherit from a partitioned table unless being created as
+ * partition.
+ */
+ if (relation->rd_rel->relkind == RELKIND_PARTITIONED_REL &&
+ !is_partition)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot inherit from partitioned table \"%s\"",
@@ -1710,6 +1739,7 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
def->is_local = false;
def->is_not_null = attribute->attnotnull;
def->is_from_type = false;
+ def->is_for_partition = false;
def->storage = attribute->attstorage;
def->raw_default = NULL;
def->cooked_default = NULL;
@@ -1838,6 +1868,9 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
* If we had no inherited attributes, the result schema is just the
* explicitly declared columns. Otherwise, we need to merge the declared
* columns into the inherited schema list.
+ *
+ * Note: In case of a partition, there are only inherited attributes that
+ * we copied from the parent as so, in transformPartitionOf().
*/
if (inhSchema != NIL)
{
@@ -1867,16 +1900,20 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
/*
* Yes, try to merge the two column definitions. They must
- * have the same type, typmod, and collation.
+ * have the same type, typmod, and collation. Don't output
+ * the notices, if partition, per note above.
*/
- if (exist_attno == schema_attno)
- ereport(NOTICE,
- (errmsg("merging column \"%s\" with inherited definition",
- attributeName)));
- else
- ereport(NOTICE,
- (errmsg("moving and merging column \"%s\" with inherited definition", attributeName),
- errdetail("User-specified column moved to the position of the inherited column.")));
+ if (!is_partition)
+ {
+ if (exist_attno == schema_attno)
+ ereport(NOTICE,
+ (errmsg("merging column \"%s\" with inherited definition",
+ attributeName)));
+ else
+ ereport(NOTICE,
+ (errmsg("moving and merging column \"%s\" with inherited definition", attributeName),
+ errdetail("User-specified column moved to the position of the inherited column.")));
+ }
def = (ColumnDef *) list_nth(inhSchema, exist_attno - 1);
typenameTypeIdAndMod(NULL, def->typeName, &defTypeId, &deftypmod);
typenameTypeIdAndMod(NULL, newdef->typeName, &newTypeId, &newtypmod);
@@ -1913,8 +1950,9 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
storage_name(def->storage),
storage_name(newdef->storage))));
- /* Mark the column as locally defined */
- def->is_local = true;
+ /* Mark the column as locally defined, unless partition */
+ if (!is_partition)
+ def->is_local = true;
/* Merge of NOT NULL constraints = OR 'em together */
def->is_not_null |= newdef->is_not_null;
/* If new def has a default, override previous default */
@@ -2093,16 +2131,20 @@ StoreCatalogInheritance1(Oid relationId, Oid parentOid,
heap_freetuple(tuple);
/*
- * Store a dependency too
+ * Store a dependency too, iff a regular inheritance relationship, which
+ * it's not, if parent is a partitioned table.
*/
- parentobject.classId = RelationRelationId;
- parentobject.objectId = parentOid;
- parentobject.objectSubId = 0;
- childobject.classId = RelationRelationId;
- childobject.objectId = relationId;
- childobject.objectSubId = 0;
+ if (!relid_is_partitioned(parentOid))
+ {
+ parentobject.classId = RelationRelationId;
+ parentobject.objectId = parentOid;
+ parentobject.objectSubId = 0;
+ childobject.classId = RelationRelationId;
+ childobject.objectId = relationId;
+ childobject.objectSubId = 0;
- recordDependencyOn(&childobject, &parentobject, DEPENDENCY_NORMAL);
+ recordDependencyOn(&childobject, &parentobject, DEPENDENCY_NORMAL);
+ }
/*
* Post creation hook of this inheritance. Since object_access_hook
@@ -3108,6 +3150,11 @@ AlterTableGetLockLevel(List *cmds)
cmd_lockmode = AlterTableGetRelOptionsLockLevel((List *) cmd->def);
break;
+ case AT_AttachPartition:
+ case AT_DetachPartition:
+ cmd_lockmode = AccessExclusiveLock;
+ break;
+
default: /* oops */
elog(ERROR, "unrecognized alter table type: %d",
(int) cmd->subtype);
@@ -3425,6 +3472,12 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
/* No command-specific prep needed */
pass = AT_PASS_MISC;
break;
+ case AT_AttachPartition:
+ case AT_DetachPartition:
+ ATSimplePermissions(rel, ATT_TABLE);
+ /* No command-specific prep needed */
+ pass = AT_PASS_MISC;
+ break;
default: /* oops */
elog(ERROR, "unrecognized alter table type: %d",
(int) cmd->subtype);
@@ -3744,6 +3797,12 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
case AT_GenericOptions:
ATExecGenericOptions(rel, (List *) cmd->def);
break;
+ case AT_AttachPartition:
+ ATExecAttachPartition(wqueue, rel, (PartitionCmd *) cmd->def);
+ break;
+ case AT_DetachPartition:
+ ATExecDetachPartition(rel, ((PartitionCmd *) cmd->def)->name);
+ break;
default: /* oops */
elog(ERROR, "unrecognized alter table type: %d",
(int) cmd->subtype);
@@ -10188,12 +10247,7 @@ ATPrepAddInherit(Relation child_rel)
static ObjectAddress
ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode)
{
- Relation parent_rel,
- catalogRelation;
- SysScanDesc scan;
- ScanKeyData key;
- HeapTuple inheritsTuple;
- int32 inhseqno;
+ Relation parent_rel;
List *children;
ObjectAddress address;
@@ -10238,38 +10292,6 @@ ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode)
errmsg("cannot inherit from partitioned table \"%s\"", parent->relname)));
/*
- * Check for duplicates in the list of parents, and determine the highest
- * inhseqno already present; we'll use the next one for the new parent.
- * (Note: get RowExclusiveLock because we will write pg_inherits below.)
- *
- * Note: we do not reject the case where the child already inherits from
- * the parent indirectly; CREATE TABLE doesn't reject comparable cases.
- */
- catalogRelation = heap_open(InheritsRelationId, RowExclusiveLock);
- ScanKeyInit(&key,
- Anum_pg_inherits_inhrelid,
- BTEqualStrategyNumber, F_OIDEQ,
- ObjectIdGetDatum(RelationGetRelid(child_rel)));
- scan = systable_beginscan(catalogRelation, InheritsRelidSeqnoIndexId,
- true, NULL, 1, &key);
-
- /* inhseqno sequences start at 1 */
- inhseqno = 0;
- while (HeapTupleIsValid(inheritsTuple = systable_getnext(scan)))
- {
- Form_pg_inherits inh = (Form_pg_inherits) GETSTRUCT(inheritsTuple);
-
- if (inh->inhparent == RelationGetRelid(parent_rel))
- ereport(ERROR,
- (errcode(ERRCODE_DUPLICATE_TABLE),
- errmsg("relation \"%s\" would be inherited from more than once",
- RelationGetRelationName(parent_rel))));
- if (inh->inhseqno > inhseqno)
- inhseqno = inh->inhseqno;
- }
- systable_endscan(scan);
-
- /*
* Prevent circularity by seeing if proposed parent inherits from child.
* (In particular, this disallows making a rel inherit from itself.)
*
@@ -10302,6 +10324,69 @@ ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode)
RelationGetRelationName(child_rel),
RelationGetRelationName(parent_rel))));
+ /* OK to create inheritance */
+ CreateInheritance(child_rel, parent_rel);
+
+ ObjectAddressSet(address, RelationRelationId,
+ RelationGetRelid(parent_rel));
+
+ /* keep our lock on the parent relation until commit */
+ heap_close(parent_rel, NoLock);
+
+ return address;
+}
+
+/*
+ * CreateInheritance
+ * Catalog manipulation portion of creating inheritance between a child
+ * table and a parent table.
+ *
+ * Common to ATExecAddInherit() and ATExecAttachPartition().
+ */
+static void
+CreateInheritance(Relation child_rel, Relation parent_rel)
+{
+ Relation catalogRelation;
+ SysScanDesc scan;
+ ScanKeyData key;
+ HeapTuple inheritsTuple;
+ int32 inhseqno;
+
+ /* Note: get RowExclusiveLock because we will write pg_inherits below. */
+ catalogRelation = heap_open(InheritsRelationId, RowExclusiveLock);
+
+ /*
+ * Check for duplicates in the list of parents, and determine the highest
+ * inhseqno already present; we'll use the next one for the new parent.
+ * Also, if proposed child is a partition, it cannot already be inheriting.
+ *
+ * Note: we do not reject the case where the child already inherits from
+ * the parent indirectly; CREATE TABLE doesn't reject comparable cases.
+ */
+ ScanKeyInit(&key,
+ Anum_pg_inherits_inhrelid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(RelationGetRelid(child_rel)));
+ scan = systable_beginscan(catalogRelation, InheritsRelidSeqnoIndexId,
+ true, NULL, 1, &key);
+
+ /* inhseqno sequences start at 1 */
+ inhseqno = 0;
+ while (HeapTupleIsValid(inheritsTuple = systable_getnext(scan)))
+ {
+ Form_pg_inherits inh = (Form_pg_inherits) GETSTRUCT(inheritsTuple);
+
+ if (inh->inhparent == RelationGetRelid(parent_rel))
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_TABLE),
+ errmsg("relation \"%s\" would be inherited from more than once",
+ RelationGetRelationName(parent_rel))));
+
+ if (inh->inhseqno > inhseqno)
+ inhseqno = inh->inhseqno;
+ }
+ systable_endscan(scan);
+
/* Match up the columns and bump attinhcount as needed */
MergeAttributesIntoExisting(child_rel, parent_rel);
@@ -10316,16 +10401,8 @@ ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode)
inhseqno + 1,
catalogRelation);
- ObjectAddressSet(address, RelationRelationId,
- RelationGetRelid(parent_rel));
-
/* Now we're done with pg_inherits */
heap_close(catalogRelation, RowExclusiveLock);
-
- /* keep our lock on the parent relation until commit */
- heap_close(parent_rel, NoLock);
-
- return address;
}
/*
@@ -10376,7 +10453,7 @@ constraints_equivalent(HeapTuple a, HeapTuple b, TupleDesc tupleDesc)
* Check columns in child table match up with columns in parent, and increment
* their attinhcount.
*
- * Called by ATExecAddInherit
+ * Called by CreateInheritance
*
* Currently all parent columns must be found in child. Missing columns are an
* error. One day we might consider creating new columns like CREATE TABLE
@@ -10394,11 +10471,13 @@ MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel)
int parent_natts;
TupleDesc tupleDesc;
HeapTuple tuple;
+ bool is_attach_partition;
attrrel = heap_open(AttributeRelationId, RowExclusiveLock);
tupleDesc = RelationGetDescr(parent_rel);
parent_natts = tupleDesc->natts;
+ is_attach_partition = relid_is_partitioned(RelationGetRelid(parent_rel));
for (parent_attno = 1; parent_attno <= parent_natts; parent_attno++)
{
@@ -10421,14 +10500,16 @@ MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel)
attribute->atttypmod != childatt->atttypmod)
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
- errmsg("child table \"%s\" has different type for column \"%s\"",
+ errmsg("%s table \"%s\" has different type for column \"%s\"",
+ is_attach_partition ? "source" : "child",
RelationGetRelationName(child_rel),
attributeName)));
if (attribute->attcollation != childatt->attcollation)
ereport(ERROR,
(errcode(ERRCODE_COLLATION_MISMATCH),
- errmsg("child table \"%s\" has different collation for column \"%s\"",
+ errmsg("%s table \"%s\" has different collation for column \"%s\"",
+ is_attach_partition ? "source" : "child",
RelationGetRelationName(child_rel),
attributeName)));
@@ -10439,8 +10520,9 @@ MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel)
if (attribute->attnotnull && !childatt->attnotnull)
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
- errmsg("column \"%s\" in child table must be marked NOT NULL",
- attributeName)));
+ errmsg("column \"%s\" in %s table must be marked NOT NULL",
+ attributeName,
+ is_attach_partition ? "source" : "child")));
/*
* OK, bump the child column's inheritance count. (If we fail
@@ -10455,7 +10537,8 @@ MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel)
{
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
- errmsg("child table is missing column \"%s\"",
+ errmsg("%s table is missing column \"%s\"",
+ is_attach_partition ? "source" : "child",
attributeName)));
}
}
@@ -10469,7 +10552,7 @@ MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel)
*
* Constraints that are marked ONLY in the parent are ignored.
*
- * Called by ATExecAddInherit
+ * Called by CreateInheritance
*
* Currently all constraints in parent must be present in the child. One day we
* may consider adding new constraints like CREATE TABLE does.
@@ -10488,9 +10571,11 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
SysScanDesc parent_scan;
ScanKeyData parent_key;
HeapTuple parent_tuple;
+ bool is_attach_partition;
catalog_relation = heap_open(ConstraintRelationId, RowExclusiveLock);
tuple_desc = RelationGetDescr(catalog_relation);
+ is_attach_partition = relid_is_partitioned(RelationGetRelid(parent_rel));
/* Outer loop scans through the parent's constraint definitions */
ScanKeyInit(&parent_key,
@@ -10538,7 +10623,8 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
if (!constraints_equivalent(parent_tuple, child_tuple, tuple_desc))
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
- errmsg("child table \"%s\" has different definition for check constraint \"%s\"",
+ errmsg("%s table \"%s\" has different definition for check constraint \"%s\"",
+ is_attach_partition ? "source" : "child",
RelationGetRelationName(child_rel),
NameStr(parent_con->conname))));
@@ -10546,8 +10632,9 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
if (child_con->connoinherit)
ereport(ERROR,
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
- errmsg("constraint \"%s\" conflicts with non-inherited constraint on child table \"%s\"",
+ errmsg("constraint \"%s\" conflicts with non-inherited constraint on %s table \"%s\"",
NameStr(child_con->conname),
+ is_attach_partition ? "source" : "child",
RelationGetRelationName(child_rel))));
/*
@@ -10570,7 +10657,8 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
if (!found)
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
- errmsg("child table is missing constraint \"%s\"",
+ errmsg("%s table is missing constraint \"%s\"",
+ is_attach_partition ? "source" : "child",
NameStr(parent_con->conname))));
}
@@ -10581,6 +10669,41 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
/*
* ALTER TABLE NO INHERIT
*
+ * Return value is the address of the relation that is no longer parent.
+ */
+static ObjectAddress
+ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
+{
+ ObjectAddress address;
+ Relation parent_rel;
+
+ /*
+ * AccessShareLock on the parent is probably enough, seeing that DROP
+ * TABLE doesn't lock parent tables at all. We need some lock since we'll
+ * be inspecting the parent's schema.
+ */
+ parent_rel = heap_openrv(parent, AccessShareLock);
+
+ /*
+ * We don't bother to check ownership of the parent table --- ownership of
+ * the child is presumed enough rights.
+ */
+
+ /* Off to RemoveInheritance() where most of the work happens */
+ RemoveInheritance(rel, parent_rel);
+
+ /* keep our lock on the parent relation until commit */
+ heap_close(parent_rel, NoLock);
+
+ ObjectAddressSet(address, RelationRelationId,
+ RelationGetRelid(parent_rel));
+
+ return address;
+}
+
+/*
+ * RemoveInheritance
+ *
* Drop a parent from the child's parents. This just adjusts the attinhcount
* and attislocal of the columns and removes the pg_inherit and pg_depend
* entries.
@@ -10594,13 +10717,11 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
* coninhcount and conislocal for inherited constraints are adjusted in
* exactly the same way.
*
- * Return value is the address of the relation that is no longer parent.
+ * Common to ATExecDropInherit() and ATExecDetachPartition().
*/
-static ObjectAddress
-ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
+static void
+RemoveInheritance(Relation child_rel, Relation parent_rel)
{
- Relation parent_rel;
- Oid parent_oid;
Relation catalogRelation;
SysScanDesc scan;
ScanKeyData key[3];
@@ -10609,19 +10730,9 @@ ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
constraintTuple;
List *connames;
bool found = false;
- ObjectAddress address;
+ bool is_detach_partition;
- /*
- * AccessShareLock on the parent is probably enough, seeing that DROP
- * TABLE doesn't lock parent tables at all. We need some lock since we'll
- * be inspecting the parent's schema.
- */
- parent_rel = heap_openrv(parent, AccessShareLock);
-
- /*
- * We don't bother to check ownership of the parent table --- ownership of
- * the child is presumed enough rights.
- */
+ is_detach_partition = relid_is_partitioned(RelationGetRelid(parent_rel));
/*
* Find and destroy the pg_inherits entry linking the two, or error out if
@@ -10631,7 +10742,7 @@ ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
ScanKeyInit(&key[0],
Anum_pg_inherits_inhrelid,
BTEqualStrategyNumber, F_OIDEQ,
- ObjectIdGetDatum(RelationGetRelid(rel)));
+ ObjectIdGetDatum(RelationGetRelid(child_rel)));
scan = systable_beginscan(catalogRelation, InheritsRelidSeqnoIndexId,
true, NULL, 1, key);
@@ -10652,11 +10763,20 @@ ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
heap_close(catalogRelation, RowExclusiveLock);
if (!found)
- ereport(ERROR,
- (errcode(ERRCODE_UNDEFINED_TABLE),
- errmsg("relation \"%s\" is not a parent of relation \"%s\"",
- RelationGetRelationName(parent_rel),
- RelationGetRelationName(rel))));
+ {
+ if (!is_detach_partition)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_TABLE),
+ errmsg("relation \"%s\" is not a parent of relation \"%s\"",
+ RelationGetRelationName(parent_rel),
+ RelationGetRelationName(child_rel))));
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_TABLE),
+ errmsg("relation \"%s\" is not a partition of relation \"%s\"",
+ RelationGetRelationName(child_rel),
+ RelationGetRelationName(parent_rel))));
+ }
/*
* Search through child columns looking for ones matching parent rel
@@ -10665,7 +10785,7 @@ ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
ScanKeyInit(&key[0],
Anum_pg_attribute_attrelid,
BTEqualStrategyNumber, F_OIDEQ,
- ObjectIdGetDatum(RelationGetRelid(rel)));
+ ObjectIdGetDatum(RelationGetRelid(child_rel)));
scan = systable_beginscan(catalogRelation, AttributeRelidNumIndexId,
true, NULL, 1, key);
while (HeapTupleIsValid(attributeTuple = systable_getnext(scan)))
@@ -10727,7 +10847,7 @@ ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
ScanKeyInit(&key[0],
Anum_pg_constraint_conrelid,
BTEqualStrategyNumber, F_OIDEQ,
- ObjectIdGetDatum(RelationGetRelid(rel)));
+ ObjectIdGetDatum(RelationGetRelid(child_rel)));
scan = systable_beginscan(catalogRelation, ConstraintRelidIndexId,
true, NULL, 1, key);
@@ -10758,7 +10878,7 @@ ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
if (copy_con->coninhcount <= 0) /* shouldn't happen */
elog(ERROR, "relation %u has non-inherited constraint \"%s\"",
- RelationGetRelid(rel), NameStr(copy_con->conname));
+ RelationGetRelid(child_rel), NameStr(copy_con->conname));
copy_con->coninhcount--;
if (copy_con->coninhcount == 0)
@@ -10770,30 +10890,22 @@ ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
}
}
- parent_oid = RelationGetRelid(parent_rel);
-
systable_endscan(scan);
heap_close(catalogRelation, RowExclusiveLock);
- drop_parent_dependency(RelationGetRelid(rel),
- RelationRelationId,
- RelationGetRelid(parent_rel));
-
+ /* Remember that dependency exists iff regular inheritance. */
+ if (!is_detach_partition)
+ drop_parent_dependency(RelationGetRelid(child_rel),
+ RelationRelationId,
+ RelationGetRelid(parent_rel));
/*
* Post alter hook of this inherits. Since object_access_hook doesn't take
* multiple object identifiers, we relay oid of parent relation using
* auxiliary_id argument.
*/
InvokeObjectPostAlterHookArg(InheritsRelationId,
- RelationGetRelid(rel), 0,
+ RelationGetRelid(child_rel), 0,
RelationGetRelid(parent_rel), false);
-
- /* keep our lock on the parent relation until commit */
- heap_close(parent_rel, NoLock);
-
- ObjectAddressSet(address, RelationRelationId, parent_oid);
-
- return address;
}
/*
@@ -12426,3 +12538,137 @@ ComputePartitionAttrs(Oid relid, List *partParams, AttrNumber *partattrs,
partopclass[attn++] = opclassOid;
}
}
+
+
+/*
+ * ALTER TABLE <name> ATTACH PARTITION <partition-name> FOR VALUES
+ *
+ * Return the address of the newly attached partition.
+ */
+static ObjectAddress
+ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
+{
+ Relation attachRel,
+ inheritsRelation;
+ SysScanDesc scan;
+ ScanKeyData key;
+ HeapTuple tuple;
+ AttrNumber attno;
+ int natts;
+ TupleDesc tupleDesc;
+ ObjectAddress address;
+
+ attachRel = heap_openrv(cmd->name, AccessShareLock);
+
+ /*
+ * Must be owner of both parent and source table -- parent was checked by
+ * ATSimplePermissions call in ATPrepCmd
+ */
+ ATSimplePermissions(attachRel, ATT_TABLE);
+
+ if (attachRel->rd_rel->reloftype)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot attach a typed table as partition")));
+
+ if (attachRel->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot attach a partitioned table as partition")));
+
+ /* attachRel should not already be a inheritance child of some relation */
+ inheritsRelation = heap_open(InheritsRelationId, AccessShareLock);
+ ScanKeyInit(&key,
+ Anum_pg_inherits_inhrelid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(RelationGetRelid(attachRel)));
+ scan = systable_beginscan(inheritsRelation, InheritsRelidSeqnoIndexId,
+ true, NULL, 1, &key);
+ if (HeapTupleIsValid(systable_getnext(scan)))
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot attach table that inherits as partition")));
+ systable_endscan(scan);
+ heap_close(inheritsRelation, AccessShareLock);
+
+ /* If attachRel is temp, it must belong to this session */
+ if (attachRel->rd_rel->relpersistence == RELPERSISTENCE_TEMP &&
+ !attachRel->rd_islocaltemp)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot attach a temporary relation of another session as partition ")));
+
+ /* If parent has OIDs, attachRel must have OIDs */
+ if (rel->rd_rel->relhasoids && !attachRel->rd_rel->relhasoids)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot attach table \"%s\" without OIDs as partition of"
+ " table \"%s\" with OIDs", RelationGetRelationName(attachRel),
+ RelationGetRelationName(rel))));
+
+ /* OTOH, if parent doesn't have them, do not allow in attachRel either */
+ if (attachRel->rd_rel->relhasoids && !rel->rd_rel->relhasoids)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot attach table \"%s\" with OIDs as partition of table"
+ " \"%s\" without OIDs", RelationGetRelationName(attachRel),
+ RelationGetRelationName(rel))));
+
+ /* Check if there are any columns in attachRel that aren't in the parent */
+ tupleDesc = RelationGetDescr(attachRel);
+ natts = tupleDesc->natts;
+ for (attno = 1; attno <= natts; attno++)
+ {
+ Form_pg_attribute attribute = tupleDesc->attrs[attno - 1];
+ char *attributeName = NameStr(attribute->attname);
+
+ /* Ignore dropped */
+ if (attribute->attisdropped)
+ continue;
+
+ /* Find same column in parent (matching on column name). */
+ tuple = SearchSysCacheCopyAttName(RelationGetRelid(rel), attributeName);
+ if (!HeapTupleIsValid(tuple))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("table \"%s\" contains column \"%s\" not found in parent \"%s\"",
+ RelationGetRelationName(attachRel), attributeName,
+ RelationGetRelationName(rel)),
+ errdetail("Table being attached should contain only the columns"
+ " present in parent.")));
+ }
+
+ /* OK to create inheritance. Rest of the checks performed there */
+ CreateInheritance(attachRel, rel);
+
+ ObjectAddressSet(address, RelationRelationId, RelationGetRelid(attachRel));
+
+ /* keep our lock until commit */
+ heap_close(attachRel, NoLock);
+
+ return address;
+}
+
+/*
+ * ALTER TABLE DETACH PARTITION
+ *
+ * Return the address of the relation that is no longer a partition of rel.
+ */
+static ObjectAddress
+ATExecDetachPartition(Relation rel, RangeVar *name)
+{
+ Relation partRel;
+ ObjectAddress address;
+
+ partRel = heap_openrv(name, AccessShareLock);
+
+ /* All inheritance related checks are performed within the function */
+ RemoveInheritance(partRel, rel);
+
+ ObjectAddressSet(address, RelationRelationId, RelationGetRelid(partRel));
+
+ /* keep our lock until commit */
+ heap_close(partRel, NoLock);
+
+ return address;
+}
diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c
index e9d9ba2..c9c72d9 100644
--- a/src/backend/commands/view.c
+++ b/src/backend/commands/view.c
@@ -95,6 +95,7 @@ DefineVirtualRelation(RangeVar *relation, List *tlist, bool replace,
def->is_local = true;
def->is_not_null = false;
def->is_from_type = false;
+ def->is_for_partition = false;
def->storage = 0;
def->raw_default = NULL;
def->cooked_default = NULL;
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 968a3f4..111fcf5 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2606,6 +2606,7 @@ _copyColumnDef(const ColumnDef *from)
COPY_SCALAR_FIELD(is_local);
COPY_SCALAR_FIELD(is_not_null);
COPY_SCALAR_FIELD(is_from_type);
+ COPY_SCALAR_FIELD(is_for_partition);
COPY_SCALAR_FIELD(storage);
COPY_NODE_FIELD(raw_default);
COPY_NODE_FIELD(cooked_default);
@@ -3001,6 +3002,7 @@ CopyCreateStmtFields(const CreateStmt *from, CreateStmt *newnode)
COPY_NODE_FIELD(relation);
COPY_NODE_FIELD(tableElts);
COPY_NODE_FIELD(inhRelations);
+ COPY_NODE_FIELD(partbound);
COPY_NODE_FIELD(partby);
COPY_NODE_FIELD(ofTypename);
COPY_NODE_FIELD(constraints);
@@ -4183,6 +4185,43 @@ _copyPartitionElem(const PartitionElem *from)
return newnode;
}
+static PartitionListSpec *
+_copyPartitionListSpec(const PartitionListSpec *from)
+{
+ PartitionListSpec *newnode = makeNode(PartitionListSpec);
+
+ COPY_NODE_FIELD(values);
+ COPY_LOCATION_FIELD(location);
+
+ return newnode;
+}
+
+static PartitionRangeSpec *
+_copyPartitionRangeSpec(const PartitionRangeSpec *from)
+{
+ PartitionRangeSpec *newnode = makeNode(PartitionRangeSpec);
+
+ COPY_SCALAR_FIELD(lowerinc);
+ COPY_NODE_FIELD(lower);
+ COPY_SCALAR_FIELD(upperinc);
+ COPY_NODE_FIELD(upper);
+ COPY_LOCATION_FIELD(location);
+
+ return newnode;
+}
+
+static PartitionCmd *
+_copyPartitionCmd(const PartitionCmd *from)
+{
+ PartitionCmd *newnode = makeNode(PartitionCmd);
+
+ COPY_NODE_FIELD(name);
+ COPY_NODE_FIELD(bound);
+ COPY_SCALAR_FIELD(skip_validate);
+
+ return newnode;
+}
+
/* ****************************************************************
* pg_list.h copy functions
* ****************************************************************
@@ -5082,6 +5121,15 @@ copyObject(const void *from)
case T_PartitionElem:
retval = _copyPartitionElem(from);
break;
+ case T_PartitionListSpec:
+ retval = _copyPartitionListSpec(from);
+ break;
+ case T_PartitionRangeSpec:
+ retval = _copyPartitionRangeSpec(from);
+ break;
+ case T_PartitionCmd:
+ retval = _copyPartitionCmd(from);
+ break;
default:
elog(ERROR, "unrecognized node type: %d", (int) nodeTag(from));
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 56325fe..1a25acf 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1156,6 +1156,7 @@ _equalCreateStmt(const CreateStmt *a, const CreateStmt *b)
COMPARE_NODE_FIELD(relation);
COMPARE_NODE_FIELD(tableElts);
COMPARE_NODE_FIELD(inhRelations);
+ COMPARE_NODE_FIELD(partbound);
COMPARE_NODE_FIELD(partby);
COMPARE_NODE_FIELD(ofTypename);
COMPARE_NODE_FIELD(constraints);
@@ -2363,6 +2364,7 @@ _equalColumnDef(const ColumnDef *a, const ColumnDef *b)
COMPARE_SCALAR_FIELD(is_local);
COMPARE_SCALAR_FIELD(is_not_null);
COMPARE_SCALAR_FIELD(is_from_type);
+ COMPARE_SCALAR_FIELD(is_for_partition);
COMPARE_SCALAR_FIELD(storage);
COMPARE_NODE_FIELD(raw_default);
COMPARE_NODE_FIELD(cooked_default);
@@ -2641,6 +2643,37 @@ _equalPartitionElem(const PartitionElem *a, const PartitionElem *b)
return true;
}
+static bool
+_equalPartitionListSpec(const PartitionListSpec *a, const PartitionListSpec *b)
+{
+ COMPARE_NODE_FIELD(values);
+ COMPARE_LOCATION_FIELD(location);
+
+ return true;
+}
+
+static bool
+_equalPartitionRangeSpec(const PartitionRangeSpec *a, const PartitionRangeSpec *b)
+{
+ COMPARE_SCALAR_FIELD(lowerinc);
+ COMPARE_NODE_FIELD(lower);
+ COMPARE_SCALAR_FIELD(upperinc);
+ COMPARE_NODE_FIELD(upper);
+ COMPARE_LOCATION_FIELD(location);
+
+ return true;
+}
+
+static bool
+_equalPartitionCmd(const PartitionCmd *a, const PartitionCmd *b)
+{
+ COMPARE_NODE_FIELD(name);
+ COMPARE_NODE_FIELD(bound);
+ COMPARE_SCALAR_FIELD(skip_validate);
+
+ return true;
+}
+
/*
* Stuff from pg_list.h
*/
@@ -3397,6 +3430,15 @@ equal(const void *a, const void *b)
case T_PartitionElem:
retval = _equalPartitionElem(a, b);
break;
+ case T_PartitionListSpec:
+ retval = _equalPartitionListSpec(a, b);
+ break;
+ case T_PartitionRangeSpec:
+ retval = _equalPartitionRangeSpec(a, b);
+ break;
+ case T_PartitionCmd:
+ retval = _equalPartitionCmd(a, b);
+ break;
default:
elog(ERROR, "unrecognized node type: %d",
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 5623471..7537473 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2369,6 +2369,7 @@ _outCreateStmtInfo(StringInfo str, const CreateStmt *node)
WRITE_NODE_FIELD(relation);
WRITE_NODE_FIELD(tableElts);
WRITE_NODE_FIELD(inhRelations);
+ WRITE_NODE_FIELD(partbound);
WRITE_NODE_FIELD(partby);
WRITE_NODE_FIELD(ofTypename);
WRITE_NODE_FIELD(constraints);
@@ -2549,6 +2550,7 @@ _outColumnDef(StringInfo str, const ColumnDef *node)
WRITE_BOOL_FIELD(is_local);
WRITE_BOOL_FIELD(is_not_null);
WRITE_BOOL_FIELD(is_from_type);
+ WRITE_BOOL_FIELD(is_for_partition);
WRITE_CHAR_FIELD(storage);
WRITE_NODE_FIELD(raw_default);
WRITE_NODE_FIELD(cooked_default);
@@ -3239,6 +3241,25 @@ _outPartitionElem(StringInfo str, const PartitionElem *node)
WRITE_LOCATION_FIELD(location);
}
+static void
+_outPartitionListSpec(StringInfo str, const PartitionListSpec *node)
+{
+ WRITE_NODE_TYPE("PARTITIONLISTVALUES");
+
+ WRITE_NODE_FIELD(values);
+}
+
+static void
+_outPartitionRangeSpec(StringInfo str, const PartitionRangeSpec *node)
+{
+ WRITE_NODE_TYPE("PARTITIONRANGE");
+
+ WRITE_BOOL_FIELD(lowerinc);
+ WRITE_NODE_FIELD(lower);
+ WRITE_BOOL_FIELD(upperinc);
+ WRITE_NODE_FIELD(upper);
+}
+
/*
* outNode -
* converts a Node into ascii string and append it to 'str'
@@ -3823,6 +3844,12 @@ outNode(StringInfo str, const void *obj)
case T_PartitionElem:
_outPartitionElem(str, obj);
break;
+ case T_PartitionListSpec:
+ _outPartitionListSpec(str, obj);
+ break;
+ case T_PartitionRangeSpec:
+ _outPartitionRangeSpec(str, obj);
+ break;
default:
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 0607439..79fbb32 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -229,6 +229,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
VariableSetStmt *vsetstmt;
PartitionElem *partelem;
PartitionBy *partby;
+ PartitionRangeSpec *partrange;
}
%type <node> stmt schema_stmt
@@ -544,6 +545,14 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <partby> PartitionBy OptPartitionBy
%type <partelem> part_elem
%type <list> part_params
+%type <list> OptPartitionElementList PartitionElementList
+%type <node> PartitionElement
+%type <node> ForValues
+%type <node> partvalue
+%type <list> partvalue_list
+%type <boolean> lb_inc ub_inc
+%type <list> RangeBound
+%type <boolean> opt_validate_spec
/*
* Non-keyword token types. These are hard-wired into the "flex" lexer.
@@ -569,7 +578,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
/* ordinary key words in alphabetical order */
%token <keyword> ABORT_P ABSOLUTE_P ACCESS ACTION ADD_P ADMIN AFTER
AGGREGATE ALL ALSO ALTER ALWAYS ANALYSE ANALYZE AND ANY ARRAY AS ASC
- ASSERTION ASSIGNMENT ASYMMETRIC AT ATTRIBUTE AUTHORIZATION
+ ASSERTION ASSIGNMENT ASYMMETRIC AT ATTACH ATTRIBUTE AUTHORIZATION
BACKWARD BEFORE BEGIN_P BETWEEN BIGINT BINARY BIT
BOOLEAN_P BOTH BY
@@ -585,7 +594,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
DATA_P DATABASE DAY_P DEALLOCATE DEC DECIMAL_P DECLARE DEFAULT DEFAULTS
DEFERRABLE DEFERRED DEFINER DELETE_P DELIMITER DELIMITERS DEPENDS DESC
- DICTIONARY DISABLE_P DISCARD DISTINCT DO DOCUMENT_P DOMAIN_P DOUBLE_P DROP
+ DETACH DICTIONARY DISABLE_P DISCARD DISTINCT DO DOCUMENT_P DOMAIN_P
+ DOUBLE_P DROP
EACH ELSE ENABLE_P ENCODING ENCRYPTED END_P ENUM_P ESCAPE EVENT EXCEPT
EXCLUDE EXCLUDING EXCLUSIVE EXECUTE EXISTS EXPLAIN
@@ -599,7 +609,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
HANDLER HAVING HEADER_P HOLD HOUR_P
IDENTITY_P IF_P ILIKE IMMEDIATE IMMUTABLE IMPLICIT_P IMPORT_P IN_P
- INCLUDING INCREMENT INDEX INDEXES INHERIT INHERITS INITIALLY INLINE_P
+ INCLUDING INCLUSIVE INCREMENT INDEX INDEXES INHERIT INHERITS INITIALLY INLINE_P
INNER_P INOUT INPUT_P INSENSITIVE INSERT INSTEAD INT_P INTEGER
INTERSECT INTERVAL INTO INVOKER IS ISNULL ISOLATION
@@ -2373,6 +2383,38 @@ alter_table_cmd:
n->def = (Node *)$1;
$$ = (Node *) n;
}
+ /* ALTER TABLE <name> ATTACH PARTITION <table_name> FOR VALUES */
+ | ATTACH PARTITION qualified_name ForValues opt_validate_spec
+ {
+ AlterTableCmd *n = makeNode(AlterTableCmd);
+ PartitionCmd *cmd = makeNode(PartitionCmd);
+
+ n->subtype = AT_AttachPartition;
+ cmd->name = $3;
+ cmd->bound = (Node *) $4;
+ cmd->skip_validate = $5;
+ n->def = (Node *) cmd;
+
+ $$ = (Node *) n;
+ }
+ /* ALTER TABLE <name> DETACH PARTITION <partition_name> */
+ | DETACH PARTITION qualified_name
+ {
+ AlterTableCmd *n = makeNode(AlterTableCmd);
+ PartitionCmd *cmd = makeNode(PartitionCmd);
+
+ n->subtype = AT_DetachPartition;
+ cmd->name = $3;
+ n->def = (Node *) cmd;
+
+ $$ = (Node *) n;
+ }
+ ;
+
+opt_validate_spec:
+ NO VALIDATE { $$ = true; }
+ | VALIDATE { $$ = false; }
+ | /* EMPTY */ { $$ = false; }
;
alter_column_default:
@@ -2468,6 +2510,60 @@ reloption_elem:
}
;
+ForValues:
+ /* a LIST partition */
+ FOR VALUES IN_P '(' partvalue_list ')'
+ {
+ PartitionListSpec *n = makeNode(PartitionListSpec);
+
+ n->values = $5;
+ n->location = @1;
+
+ $$ = (Node *) n;
+ }
+
+ /* a RANGE partition */
+ | FOR VALUES START RangeBound lb_inc END_P RangeBound ub_inc
+ {
+ PartitionRangeSpec *n = makeNode(PartitionRangeSpec);
+
+ n->lowerinc = $5;
+ n->lower = $4;
+ n->upperinc = $8;
+ n->upper = $7;
+ n->location = @1;
+
+ $$ = (Node *) n;
+ }
+ ;
+
+RangeBound:
+ UNBOUNDED { $$ = NIL; }
+ | '(' partvalue_list ')' { $$ = $2; }
+ ;
+
+lb_inc:
+ EXCLUSIVE { $$ = false;}
+ | INCLUSIVE { $$ = true; }
+ | /* EMPTY */ { $$ = true; }
+ ;
+
+ub_inc:
+ INCLUSIVE { $$ = true; }
+ | EXCLUSIVE { $$ = false; }
+ | /* EMPTY */ { $$ = false; }
+ ;
+
+partvalue:
+ Sconst { $$ = makeStringConst($1, @1); }
+ | NumericOnly { $$ = makeAConst($1, @1); }
+ | NULL_P { $$ = makeNullAConst(@1); }
+ ;
+
+partvalue_list:
+ partvalue { $$ = list_make1($1); }
+ | partvalue_list ',' partvalue { $$ = lappend($1, $3); }
+ ;
/*****************************************************************************
*
@@ -2885,6 +2981,44 @@ CreateStmt: CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
n->if_not_exists = true;
$$ = (Node *)n;
}
+ | CREATE OptTemp TABLE qualified_name PARTITION OF qualified_name
+ OptPartitionElementList ForValues OptPartitionBy OptWith
+ OnCommitOption OptTableSpace
+ {
+ CreateStmt *n = makeNode(CreateStmt);
+ $4->relpersistence = $2;
+ n->relation = $4;
+ n->tableElts = $8;
+ n->inhRelations = list_make1($7);
+ n->partbound = (Node *) $9;
+ n->partby = $10;
+ n->ofTypename = NULL;
+ n->constraints = NIL;
+ n->options = $11;
+ n->oncommit = $12;
+ n->tablespacename = $13;
+ n->if_not_exists = false;
+ $$ = (Node *)n;
+ }
+ | CREATE OptTemp TABLE IF_P NOT EXISTS qualified_name PARTITION OF
+ qualified_name OptPartitionElementList ForValues OptPartitionBy
+ OptWith OnCommitOption OptTableSpace
+ {
+ CreateStmt *n = makeNode(CreateStmt);
+ $7->relpersistence = $2;
+ n->relation = $7;
+ n->tableElts = $11;
+ n->inhRelations = list_make1($10);
+ n->partbound = (Node *) $12;
+ n->partby = $13;
+ n->ofTypename = NULL;
+ n->constraints = NIL;
+ n->options = $14;
+ n->oncommit = $15;
+ n->tablespacename = $16;
+ n->if_not_exists = true;
+ $$ = (Node *)n;
+ }
;
/*
@@ -2930,6 +3064,11 @@ OptTypedTableElementList:
| /*EMPTY*/ { $$ = NIL; }
;
+OptPartitionElementList:
+ '(' PartitionElementList ')' { $$ = $2; }
+ | /*EMPTY*/ { $$ = NIL; }
+ ;
+
TableElementList:
TableElement
{
@@ -2952,6 +3091,17 @@ TypedTableElementList:
}
;
+PartitionElementList:
+ PartitionElement
+ {
+ $$ = list_make1($1);
+ }
+ | PartitionElementList ',' PartitionElement
+ {
+ $$ = lappend($1, $3);
+ }
+ ;
+
TableElement:
columnDef { $$ = $1; }
| TableLikeClause { $$ = $1; }
@@ -2963,6 +3113,11 @@ TypedTableElement:
| TableConstraint { $$ = $1; }
;
+PartitionElement:
+ columnOptions { $$ = $1; }
+ | TableConstraint { $$ = $1; }
+ ;
+
columnDef: ColId Typename create_generic_options ColQualList
{
ColumnDef *n = makeNode(ColumnDef);
@@ -2972,6 +3127,7 @@ columnDef: ColId Typename create_generic_options ColQualList
n->is_local = true;
n->is_not_null = false;
n->is_from_type = false;
+ n->is_for_partition = false;
n->storage = 0;
n->raw_default = NULL;
n->cooked_default = NULL;
@@ -2993,6 +3149,7 @@ columnOptions: ColId WITH OPTIONS ColQualList
n->is_local = true;
n->is_not_null = false;
n->is_from_type = false;
+ n->is_for_partition = false;
n->storage = 0;
n->raw_default = NULL;
n->cooked_default = NULL;
@@ -11122,6 +11279,7 @@ TableFuncElement: ColId Typename opt_collate_clause
n->is_local = true;
n->is_not_null = false;
n->is_from_type = false;
+ n->is_for_partition = false;
n->storage = 0;
n->raw_default = NULL;
n->cooked_default = NULL;
@@ -13805,6 +13963,7 @@ unreserved_keyword:
| ASSERTION
| ASSIGNMENT
| AT
+ | ATTACH
| ATTRIBUTE
| BACKWARD
| BEFORE
@@ -13851,6 +14010,7 @@ unreserved_keyword:
| DELIMITER
| DELIMITERS
| DEPENDS
+ | DETACH
| DICTIONARY
| DISABLE_P
| DISCARD
@@ -13893,6 +14053,7 @@ unreserved_keyword:
| IMPLICIT_P
| IMPORT_P
| INCLUDING
+ | INCLUSIVE
| INCREMENT
| INDEX
| INDEXES
diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c
index 77ffd84..d71fdfe 100644
--- a/src/backend/parser/parse_agg.c
+++ b/src/backend/parser/parse_agg.c
@@ -493,7 +493,9 @@ check_agglevels_and_constraints(ParseState *pstate, Node *expr)
err = _("grouping operations are not allowed in partition key expression");
break;
-
+ case EXPR_KIND_PARTITION_FOR_VALUES:
+ errkind = true;
+ break;
/*
* There is intentionally no default: case here, so that the
@@ -854,6 +856,9 @@ transformWindowFuncCall(ParseState *pstate, WindowFunc *wfunc,
case EXPR_KIND_PARTITION_KEY:
err = _("window functions are not allowed in partition key expression");
break;
+ case EXPR_KIND_PARTITION_FOR_VALUES:
+ errkind = true;
+ break;
/*
* There is intentionally no default: case here, so that the
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 147c6da..f14d776 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -352,6 +352,26 @@ transformExprRecurse(ParseState *pstate, Node *expr)
result = (Node *) expr;
break;
}
+ case T_PartitionListSpec:
+ {
+ PartitionListSpec *list = (PartitionListSpec *) expr;
+
+ list->values = transformExpressionList(pstate, list->values,
+ pstate->p_expr_kind);
+ result = expr;
+ break;
+ }
+ case T_PartitionRangeSpec:
+ {
+ PartitionRangeSpec *range = (PartitionRangeSpec *) expr;
+
+ range->lower = transformExpressionList(pstate, range->lower,
+ pstate->p_expr_kind);
+ range->upper = transformExpressionList(pstate, range->upper,
+ pstate->p_expr_kind);
+ result = expr;
+ break;
+ }
default:
/* should not reach here */
@@ -1698,6 +1718,7 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
case EXPR_KIND_OFFSET:
case EXPR_KIND_RETURNING:
case EXPR_KIND_VALUES:
+ case EXPR_KIND_PARTITION_FOR_VALUES:
/* okay */
break;
case EXPR_KIND_CHECK_CONSTRAINT:
@@ -3254,6 +3275,8 @@ ParseExprKindName(ParseExprKind exprKind)
return "WHEN";
case EXPR_KIND_PARTITION_KEY:
return "partition key expression";
+ case EXPR_KIND_PARTITION_FOR_VALUES:
+ return "FOR VALUES";
/*
* There is intentionally no default: case here, so that the
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 465597d..e69cbfc 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -48,8 +48,10 @@
#include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "nodes/nodeFuncs.h"
+#include "optimizer/planner.h"
#include "parser/analyze.h"
#include "parser/parse_clause.h"
+#include "parser/parse_coerce.h"
#include "parser/parse_collate.h"
#include "parser/parse_expr.h"
#include "parser/parse_relation.h"
@@ -89,6 +91,7 @@ typedef struct
* the table */
IndexStmt *pkey; /* PRIMARY KEY index, if any */
bool ispartitioned; /* true if table is partitioned */
+ Node *partbound; /* transformed FOR VALUES */
} CreateStmtContext;
/* State shared by transformCreateSchemaStmt and its subroutines */
@@ -131,6 +134,10 @@ static void transformConstraintAttrs(CreateStmtContext *cxt,
List *constraintList);
static void transformColumnType(CreateStmtContext *cxt, ColumnDef *column);
static void setSchemaName(char *context_schema, char **stmt_schema_name);
+static void transformPartitionOf(CreateStmtContext *cxt, Node *bound);
+static void transformAttachPartition(CreateStmtContext *cxt, PartitionCmd *cmd);
+static Node *transformPartitionBound(CreateStmtContext *cxt, Relation parent,
+ Node *bound);
/*
@@ -232,6 +239,7 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
cxt.alist = NIL;
cxt.pkey = NULL;
cxt.ispartitioned = stmt->partby != NULL;
+ cxt.partbound = NULL;
/*
* Notice that we allow OIDs here only for plain tables, even though
@@ -250,6 +258,9 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
if (stmt->ofTypename)
transformOfType(&cxt, stmt->ofTypename);
+ if (stmt->partbound)
+ transformPartitionOf(&cxt, stmt->partbound);
+
if (stmt->partby)
{
int partnatts = list_length(stmt->partby->partParams);
@@ -355,6 +366,7 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
*/
stmt->tableElts = cxt.columns;
stmt->constraints = cxt.ckconstraints;
+ stmt->partbound = cxt.partbound;
result = lappend(cxt.blist, stmt);
result = list_concat(result, cxt.alist);
@@ -894,6 +906,7 @@ transformTableLikeClause(CreateStmtContext *cxt, TableLikeClause *table_like_cla
def->is_local = true;
def->is_not_null = attribute->attnotnull;
def->is_from_type = false;
+ def->is_for_partition = false;
def->storage = 0;
def->raw_default = NULL;
def->cooked_default = NULL;
@@ -1113,6 +1126,7 @@ transformOfType(CreateStmtContext *cxt, TypeName *ofTypename)
n->is_local = true;
n->is_not_null = false;
n->is_from_type = true;
+ n->is_for_partition = false;
n->storage = 0;
n->raw_default = NULL;
n->cooked_default = NULL;
@@ -2580,6 +2594,7 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
cxt.alist = NIL;
cxt.pkey = NULL;
cxt.ispartitioned = relid_is_partitioned(relid);
+ cxt.partbound = NULL;
/*
* The only subtypes that currently require parse transformation handling
@@ -2668,6 +2683,22 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
break;
}
+ case AT_AttachPartition:
+ {
+ PartitionCmd *partcmd = (PartitionCmd *) cmd->def;
+
+ transformAttachPartition(&cxt, partcmd);
+
+ /* assign transformed values */
+ partcmd->bound = cxt.partbound;
+ }
+
+ newcmds = lappend(newcmds, cmd);
+ break;
+ case AT_DetachPartition:
+ newcmds = lappend(newcmds, cmd);
+ break;
+
default:
newcmds = lappend(newcmds, cmd);
break;
@@ -3032,3 +3063,290 @@ setSchemaName(char *context_schema, char **stmt_schema_name)
"different from the one being created (%s)",
*stmt_schema_name, context_schema)));
}
+
+/*
+ * transformPartitionOf
+ * Analyze PARTITION OF ... FOR VALUES ...
+ */
+static void
+transformPartitionOf(CreateStmtContext *cxt, Node *bound)
+{
+ TupleDesc tupdesc;
+ int i;
+ Relation parentRel;
+ RangeVar *partof = linitial(cxt->inhRelations);
+
+ parentRel = heap_openrv(partof, AccessShareLock);
+
+ /* Check if the target table is partitioned at all */
+ if (parentRel->rd_rel->relkind != RELKIND_PARTITIONED_REL)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("\"%s\" is not partitioned",
+ RelationGetRelationName(parentRel))));
+ Assert(RelationGetPartitionKey(parentRel) != NULL);
+
+ /* If parent rel is temp, it must belong to this session */
+ if (parentRel->rd_rel->relpersistence == RELPERSISTENCE_TEMP &&
+ !parentRel->rd_islocaltemp)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot create as partition of temporary relation of another session")));
+
+ /*
+ * Do not allow OIDs in a partition, if not present in the parent. But
+ * force them in partition, if they are present in the parent.
+ */
+ if (cxt->hasoids && !parentRel->rd_rel->relhasoids)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot create table with OIDs as partition of table without OIDs")));
+
+ if (parentRel->rd_rel->relhasoids)
+ cxt->hasoids = true;
+
+ tupdesc = RelationGetDescr(parentRel);
+ for (i = 0; i < tupdesc->natts; i++)
+ {
+ Form_pg_attribute attribute = tupdesc->attrs[i];
+ ColumnDef *def;
+
+ if (attribute->attisdropped)
+ continue;
+
+ def = makeNode(ColumnDef);
+ def->colname = pstrdup(NameStr(attribute->attname));
+ def->typeName = makeTypeNameFromOid(attribute->atttypid,
+ attribute->atttypmod);
+ def->inhcount = 1;
+ def->is_local = false;
+ def->is_not_null = attribute->attnotnull;
+ def->is_from_type = false;
+ def->is_for_partition = true;
+ def->storage = attribute->attstorage;
+ def->raw_default = NULL;
+ def->cooked_default = NULL;
+ def->collClause = NULL;
+ def->collOid = attribute->attcollation;
+ def->constraints = NIL;
+ def->location = -1;
+
+ cxt->columns = lappend(cxt->columns, def);
+ }
+
+ /* tranform the values */
+ cxt->partbound = transformPartitionBound(cxt, parentRel, bound);
+
+ heap_close(parentRel, AccessShareLock);
+}
+
+/*
+ * transformAttachPartition
+ * Analyze ATTACH PARTITION ... FOR VALUES ...
+ */
+static void
+transformAttachPartition(CreateStmtContext *cxt, PartitionCmd *cmd)
+{
+ Relation parentRel = cxt->rel;
+
+ /* Check if the target table is partitioned at all */
+ if (parentRel->rd_rel->relkind != RELKIND_PARTITIONED_REL)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("\"%s\" is not partitioned", cxt->relation->relname)));
+ Assert(RelationGetPartitionKey(parentRel) != NULL);
+
+ /* tranform the values */
+ cxt->partbound = transformPartitionBound(cxt, parentRel, cmd->bound);
+}
+
+/*
+ * transformPartitionBound
+ *
+ * Transform partition bound per the partition key
+ */
+static Node *
+transformPartitionBound(CreateStmtContext *cxt, Relation parent, Node *bound)
+{
+ int i;
+ ListCell *cell;
+ Oid parentId = RelationGetRelid(parent);
+ PartitionKey key = RelationGetPartitionKey(parent);
+ PartitionListSpec *list, *result_list;
+ PartitionRangeSpec *range, *result_range;
+
+ switch (key->strategy)
+ {
+ case PARTITION_STRAT_LIST:
+ if (!IsA(bound, PartitionListSpec))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("invalid bound specification for a list partition"),
+ parser_errposition(cxt->pstate, exprLocation(bound))));
+
+ list = (PartitionListSpec *) transformExpr(cxt->pstate, bound,
+ EXPR_KIND_PARTITION_FOR_VALUES);
+
+ result_list = makeNode(PartitionListSpec);
+
+ foreach(cell, list->values)
+ {
+ Node *value = (Node *) lfirst(cell);
+ Node *orig_value = value;
+ Oid valuetype;
+
+ valuetype = exprType(value);
+ value = coerce_to_target_type(cxt->pstate,
+ value, valuetype,
+ key->tcinfo->typid[0],
+ key->tcinfo->typmod[0],
+ COERCION_ASSIGNMENT,
+ COERCE_IMPLICIT_CAST,
+ -1);
+ if (value == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("specified value cannot be cast to type \"%s\""
+ " of key column \"%s\"",
+ format_type_be(key->tcinfo->typid[0]),
+ get_relid_attribute_name(parentId, key->partattrs[0])),
+ parser_errposition(cxt->pstate, exprLocation(orig_value))));
+
+ /* Simplify the expression */
+ value = (Node *) expression_planner((Expr *) value);
+
+ result_list->values = lappend(result_list->values, value);
+ }
+ return (Node *) result_list;
+
+ case PARTITION_STRAT_RANGE:
+ if (!IsA(bound, PartitionRangeSpec))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("invalid bound specification for a range partition"),
+ parser_errposition(cxt->pstate, exprLocation(bound))));
+
+ range = (PartitionRangeSpec *) transformExpr(cxt->pstate, bound,
+ EXPR_KIND_PARTITION_FOR_VALUES);
+
+ if (!range->lower && !range->upper)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("both START and END cannot be UNBOUNDED"),
+ parser_errposition(cxt->pstate, range->location)));
+
+ result_range = makeNode(PartitionRangeSpec);
+ result_range->lowerinc = range->lowerinc;
+ result_range->upperinc = range->upperinc;
+
+ if (range->lower)
+ {
+ if (list_length(range->lower) > key->partnatts)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("START has more values specified than number"
+ " of columns in the partition key"),
+ parser_errposition(cxt->pstate,
+ exprLocation(list_nth(range->lower,
+ list_length(range->lower) - 1)))));
+ else if (list_length(range->lower) < key->partnatts)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("START has fewer values specified than number"
+ " of columns in the partition key"),
+ parser_errposition(cxt->pstate,
+ exprLocation(list_nth(range->lower,
+ list_length(range->lower) - 1)))));
+
+ i = 0;
+ foreach (cell, range->lower)
+ {
+ Node *value = (Node *) lfirst(cell);
+ Node *orig_value = value;
+ Oid valuetype;
+
+ valuetype = exprType(value);
+ value = coerce_to_target_type(cxt->pstate,
+ value, valuetype,
+ key->tcinfo->typid[i],
+ key->tcinfo->typmod[i],
+ COERCION_ASSIGNMENT,
+ COERCE_IMPLICIT_CAST,
+ -1);
+ if (value == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("specified value cannot be cast to type"
+ " \"%s\" of key column \"%s\"",
+ format_type_be(key->tcinfo->typid[i]),
+ get_relid_attribute_name(parentId, key->partattrs[i])),
+ parser_errposition(cxt->pstate, exprLocation(orig_value))));
+
+ /* Simplify the expression */
+ value = (Node *) expression_planner((Expr *) value);
+
+ result_range->lower = lappend(result_range->lower, value);
+ ++i;
+ }
+ }
+ else
+ result_range->lowerinc = false;
+
+ if (range->upper)
+ {
+ if (list_length(range->upper) > key->partnatts)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("END has more values specified than number"
+ " of columns in the partition key"),
+ parser_errposition(cxt->pstate,
+ exprLocation(list_nth(range->upper,
+ list_length(range->upper) - 1)))));
+ else if (list_length(range->upper) < key->partnatts)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("END has fewer values specified than number"
+ " of columns in the partition key"),
+ parser_errposition(cxt->pstate,
+ exprLocation(list_nth(range->upper,
+ list_length(range->upper) - 1)))));
+
+ i = 0;
+ foreach (cell, range->upper)
+ {
+ Node *value = (Node *) lfirst(cell);
+ Node *orig_value = value;
+ Oid valuetype;
+
+ valuetype = exprType(value);
+ value = coerce_to_target_type(cxt->pstate,
+ value, valuetype,
+ key->tcinfo->typid[i],
+ key->tcinfo->typmod[i],
+ COERCION_ASSIGNMENT,
+ COERCE_IMPLICIT_CAST,
+ -1);
+ if (value == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("specified value cannot be cast to type"
+ " \"%s\" of key column \"%s\"",
+ format_type_be(key->tcinfo->typid[i]),
+ get_relid_attribute_name(parentId, key->partattrs[i])),
+ parser_errposition(cxt->pstate, exprLocation(orig_value))));
+
+ /* Simplify the expression */
+ value = (Node *) expression_planner((Expr *) value);
+
+ result_range->upper = lappend(result_range->upper, value);
+ ++i;
+ }
+ }
+ else
+ result_range->upperinc = false;
+
+ return (Node *) result_range;
+ }
+
+ return NULL;
+}
diff --git a/src/include/catalog/partition.h b/src/include/catalog/partition.h
index 2c876c4..31dbe17 100644
--- a/src/include/catalog/partition.h
+++ b/src/include/catalog/partition.h
@@ -14,6 +14,7 @@
#define PARTITION_H
#include "fmgr.h"
+#include "parser/parse_node.h"
#include "utils/relcache.h"
/* Type and collation information for partition key columns */
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 9e582a5..56bbe43 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -405,6 +405,7 @@ typedef enum NodeTag
T_AlterPolicyStmt,
T_CreateTransformStmt,
T_CreateAmStmt,
+ T_PartitionCmd,
/*
* TAGS FOR PARSE TREE NODES (parsenodes.h)
@@ -454,6 +455,8 @@ typedef enum NodeTag
T_RoleSpec,
T_PartitionElem,
T_PartitionBy,
+ T_PartitionListSpec,
+ T_PartitionRangeSpec,
/*
* TAGS FOR REPLICATION GRAMMAR PARSE NODES (replnodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 4f697b8..d9dff96 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -591,6 +591,7 @@ typedef struct ColumnDef
bool is_local; /* column has local (non-inherited) def'n */
bool is_not_null; /* NOT NULL constraint specified? */
bool is_from_type; /* column definition came from table type */
+ bool is_for_partition; /* column definition is for a partition */
char storage; /* attstorage setting, or 0 for default */
Node *raw_default; /* default value (untransformed parse tree) */
Node *cooked_default; /* default value (transformed expr tree) */
@@ -731,6 +732,40 @@ typedef struct PartitionBy
int location; /* token location, or -1 if unknown */
} PartitionBy;
+/*
+ * PartitionRangeSpec - range type syntax for range partition bound
+ */
+typedef struct PartitionListSpec
+{
+ NodeTag type;
+ List *values;
+ int location;
+} PartitionListSpec;
+
+/*
+ * PartitionRangeSpec - range type syntax for range partition bound
+ */
+typedef struct PartitionRangeSpec
+{
+ NodeTag type;
+ bool lowerinc;
+ List *lower;
+ bool upperinc;
+ List *upper;
+ int location; /* token location, or -1 if unknown */
+} PartitionRangeSpec;
+
+/*
+ * PartitionCmd - ALTER TABLE partition commands
+ */
+typedef struct PartitionCmd
+{
+ NodeTag type;
+ RangeVar *name;
+ Node *bound;
+ bool skip_validate;
+} PartitionCmd;
+
/****************************************************************************
* Nodes for a Query tree
****************************************************************************/
@@ -1558,7 +1593,9 @@ typedef enum AlterTableType
AT_DisableRowSecurity, /* DISABLE ROW SECURITY */
AT_ForceRowSecurity, /* FORCE ROW SECURITY */
AT_NoForceRowSecurity, /* NO FORCE ROW SECURITY */
- AT_GenericOptions /* OPTIONS (...) */
+ AT_GenericOptions, /* OPTIONS (...) */
+ AT_AttachPartition, /* ATTACH PARTITION */
+ AT_DetachPartition /* DETACH PARTITION */
} AlterTableType;
typedef struct ReplicaIdentityStmt
@@ -1783,7 +1820,9 @@ typedef struct CreateStmt
RangeVar *relation; /* relation to create */
List *tableElts; /* column definitions (list of ColumnDef) */
List *inhRelations; /* relations to inherit from (list of
- * inhRelation) */
+ * inhRelation); (ab)used also as partition
+ * parent */
+ Node *partbound; /* FOR VALUES clause */
PartitionBy *partby; /* PARTITION BY clause */
TypeName *ofTypename; /* OF typename */
List *constraints; /* constraints (list of Constraint nodes) */
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 40da67a..70c264c 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -49,6 +49,7 @@ PG_KEYWORD("assertion", ASSERTION, UNRESERVED_KEYWORD)
PG_KEYWORD("assignment", ASSIGNMENT, UNRESERVED_KEYWORD)
PG_KEYWORD("asymmetric", ASYMMETRIC, RESERVED_KEYWORD)
PG_KEYWORD("at", AT, UNRESERVED_KEYWORD)
+PG_KEYWORD("attach", ATTACH, UNRESERVED_KEYWORD)
PG_KEYWORD("attribute", ATTRIBUTE, UNRESERVED_KEYWORD)
PG_KEYWORD("authorization", AUTHORIZATION, TYPE_FUNC_NAME_KEYWORD)
PG_KEYWORD("backward", BACKWARD, UNRESERVED_KEYWORD)
@@ -127,6 +128,7 @@ PG_KEYWORD("delimiter", DELIMITER, UNRESERVED_KEYWORD)
PG_KEYWORD("delimiters", DELIMITERS, UNRESERVED_KEYWORD)
PG_KEYWORD("depends", DEPENDS, UNRESERVED_KEYWORD)
PG_KEYWORD("desc", DESC, RESERVED_KEYWORD)
+PG_KEYWORD("detach", DETACH, UNRESERVED_KEYWORD)
PG_KEYWORD("dictionary", DICTIONARY, UNRESERVED_KEYWORD)
PG_KEYWORD("disable", DISABLE_P, UNRESERVED_KEYWORD)
PG_KEYWORD("discard", DISCARD, UNRESERVED_KEYWORD)
@@ -191,6 +193,7 @@ PG_KEYWORD("implicit", IMPLICIT_P, UNRESERVED_KEYWORD)
PG_KEYWORD("import", IMPORT_P, UNRESERVED_KEYWORD)
PG_KEYWORD("in", IN_P, RESERVED_KEYWORD)
PG_KEYWORD("including", INCLUDING, UNRESERVED_KEYWORD)
+PG_KEYWORD("inclusive", INCLUSIVE, UNRESERVED_KEYWORD)
PG_KEYWORD("increment", INCREMENT, UNRESERVED_KEYWORD)
PG_KEYWORD("index", INDEX, UNRESERVED_KEYWORD)
PG_KEYWORD("indexes", INDEXES, UNRESERVED_KEYWORD)
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index a13c6fb..3d45663 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -65,7 +65,8 @@ typedef enum ParseExprKind
EXPR_KIND_EXECUTE_PARAMETER, /* parameter value in EXECUTE */
EXPR_KIND_TRIGGER_WHEN, /* WHEN condition in CREATE TRIGGER */
EXPR_KIND_POLICY, /* USING or WITH CHECK expr in policy */
- EXPR_KIND_PARTITION_KEY /* partition key expression */
+ EXPR_KIND_PARTITION_KEY, /* partition key expression */
+ EXPR_KIND_PARTITION_FOR_VALUES /* partition bound */
} ParseExprKind;
--
1.7.1
[text/x-diff] 0005-Infrastructure-for-partition-metadata-storage-and-ma-v5.patch (101.7K, ../../5742D5F5.3020101@lab.ntt.co.jp/6-0005-Infrastructure-for-partition-metadata-storage-and-ma-v5.patch)
download | inline diff:
From 35add25224c3f05f41eb9953a11612aba59a9972 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Fri, 18 Mar 2016 15:54:17 +0900
Subject: [PATCH 5/7] Infrastructure for partition metadata storage and manipulation.
Catalog pg_partition with:
partrelid oid
partbound pg_node_tree
Now since partition metadata is stored in a dedicated catalog, it is possible
to use that knowledge to enforce special behaviors for inheritance children
(or relations in general) that are partitions. Some such behaviors include:
* Cannot drop using DROP TABLE without first "detaching" from its parent
* Cannot add, drop new columns
* Cannot rename, change type of existing columns
It also becomes now possible to check, from the FOR VALUES specification, that
the new partition (in CREATE TABLE PARTITION OF parent or ALTER TABLE parent
ATTACH PARTITION) does not overlap with any existing partition of parent.
A partition's check expression (that is, an expression tree that can be used
a check constraint for the rows in it) can be obtained with
RelationGetPartitionCheckExpr(). ExecConstraints() now checks using that
that expression, whether a row being inserted or new row value in update case
violate the partition boundary specification. Also, constraint exclusion
based partition-pruning is done using them.
When attaching a partition, rows in the table are checked whether any violates
the partition bound specification (AND'd with the parent's check expression,
if parent itself is a partition), unless NO VALIDATE clause is specified with
the command.
This commit also invents an internal representation of partition bounds in
the form of structs PartitionListBound and PartitionRangeBound, which consist
of datums and information like number of values in a list, booleans for
whether a range bound is infinite, inclusive (vs. exclusive), lower bound
(vs. upper bound), etc. "Partition descriptor" of a partitioned table
(struct PartitionDesc) consists of an array of oids of partitions, array
of pointers to PartitionListBound structs (NULL if not list partitioned,
and 2 arrays of pointers to PartitionRangeBound structs, (each NULL,
if not range partitioned).
One more data structure that this commit invents for future use is struct
PartitionDescNode. It represents a node in partition tree where each node
of the tree corresponds to a partitioned table in the tree. The tree
structure is such that a parent node links to the first child node (which
corresponds with the first partition of the parent that is partitioned
itself), which in turn, links to its right sibling (the next partition
that is partitioned) and so on. Each node consists of some book-keeping
information including a pointer to PartitionDesc of the corresponding table
and recursive pointers just mentioned viz. downlink and next.
---
doc/src/sgml/catalogs.sgml | 56 ++
src/backend/catalog/Makefile | 2 +-
src/backend/catalog/heap.c | 6 +
src/backend/catalog/partition.c | 1287 ++++++++++++++++++++++++++++
src/backend/commands/copy.c | 2 +-
src/backend/commands/lockcmds.c | 4 +-
src/backend/commands/tablecmds.c | 210 +++++-
src/backend/executor/execMain.c | 75 ++-
src/backend/executor/nodeModifyTable.c | 4 +-
src/backend/nodes/readfuncs.c | 33 +
src/backend/optimizer/util/plancat.c | 13 +
src/backend/parser/analyze.c | 1 +
src/backend/parser/parse_clause.c | 4 +-
src/backend/parser/parse_relation.c | 3 +-
src/backend/parser/parse_utilcmd.c | 6 +
src/backend/utils/adt/ruleutils.c | 78 ++
src/backend/utils/cache/relcache.c | 284 ++++++
src/backend/utils/cache/syscache.c | 12 +
src/bin/pg_dump/common.c | 86 ++
src/bin/pg_dump/pg_dump.c | 98 ++-
src/bin/pg_dump/pg_dump.h | 12 +
src/bin/psql/describe.c | 46 +-
src/include/catalog/indexing.h | 3 +
src/include/catalog/partition.h | 85 ++
src/include/catalog/pg_partition.h | 54 ++
src/include/nodes/execnodes.h | 4 +
src/include/utils/rel.h | 10 +
src/include/utils/syscache.h | 1 +
src/test/regress/expected/sanity_check.out | 1 +
29 files changed, 2444 insertions(+), 36 deletions(-)
create mode 100644 src/include/catalog/pg_partition.h
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 652f779..9e7c1d9 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -224,6 +224,11 @@
</row>
<row>
+ <entry><link linkend="catalog-pg-partition"><structname>pg_partition</structname></link></entry>
+ <entry>information about partitions</entry>
+ </row>
+
+ <row>
<entry><link linkend="catalog-pg-partitioned"><structname>pg_partitioned</structname></link></entry>
<entry>information about partitioned tables, including the partition key</entry>
</row>
@@ -4677,6 +4682,57 @@
</sect1>
+ <sect1 id="catalog-pg-partition">
+ <title><structname>pg_partition</structname></title>
+
+ <indexterm zone="catalog-pg-partition">
+ <primary>pg_partition</primary>
+ </indexterm>
+
+ <para>
+ The catalog <structname>pg_partition</structname> stores partition bounds
+ of tables that are partitions.
+ </para>
+
+ <table>
+ <title><structname>pg_partition</> Columns</title>
+
+ <tgroup cols="4">
+ <thead>
+ <row>
+ <entry>Name</entry>
+ <entry>Type</entry>
+ <entry>References</entry>
+ <entry>Description</entry>
+ </row>
+ </thead>
+
+ <tbody>
+
+ <row>
+ <entry><structfield>partrelid</structfield></entry>
+ <entry><type>oid</type></entry>
+ <entry><literal><link linkend="catalog-pg-class"><structname>pg_class</structname></link>.oid</literal></entry>
+ <entry>The OID of the <structname>pg_class</> entry for this partition</entry>
+ </row>
+
+ <row>
+ <entry><structfield>partbound</structfield></entry>
+ <entry><type>pg_node_tree</type></entry>
+ <entry></entry>
+ <entry>
+ Expression tree (in <function>nodeToString()</function> representation)
+ for partition boundary value. For list partitions, it is simply a list
+ of values. For range partitions, it consists of lower and upper bound
+ values along with their inclusivity.
+ </entry>
+ </row>
+
+ </tbody>
+ </tgroup>
+ </table>
+ </sect1>
+
<sect1 id="catalog-pg-partitioned">
<title><structname>pg_partitioned</structname></title>
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index 4dc6300..ee1be31 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -41,7 +41,7 @@ POSTGRES_BKI_SRCS = $(addprefix $(top_srcdir)/src/include/catalog/,\
pg_foreign_data_wrapper.h pg_foreign_server.h pg_user_mapping.h \
pg_foreign_table.h pg_policy.h pg_replication_origin.h \
pg_default_acl.h pg_init_privs.h pg_seclabel.h pg_shseclabel.h \
- pg_collation.h pg_range.h pg_transform.h pg_partitioned.h\
+ pg_collation.h pg_range.h pg_transform.h pg_partitioned.h pg_partition.h\
toasting.h indexing.h \
)
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 932a48b..2e99661 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -1808,7 +1808,13 @@ heap_drop_with_catalog(Oid relid)
* If a partitioned table, delete the pg_partitioned tuples.
*/
if (rel->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+ {
RemovePartitionKeyByRelId(relid);
+ RelationDropPartitions(relid);
+ }
+
+ if (relid_is_partition(relid))
+ RemovePartitionEntryByRelId(relid);
/*
* Schedule unlinking of the relation's physical files at commit.
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index c6c8dad..eb27937 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -18,19 +18,26 @@
#include "access/heapam.h"
#include "access/htup_details.h"
#include "access/nbtree.h"
+#include "access/sysattr.h"
#include "catalog/dependency.h"
#include "catalog/indexing.h"
#include "catalog/objectaddress.h"
#include "catalog/partition.h"
#include "catalog/pg_collation.h"
+#include "catalog/pg_inherits.h"
+#include "catalog/pg_inherits_fn.h"
#include "catalog/pg_opclass.h"
#include "catalog/pg_partitioned.h"
+#include "catalog/pg_partition.h"
#include "catalog/pg_type.h"
#include "executor/executor.h"
#include "miscadmin.h"
+#include "nodes/makefuncs.h"
#include "nodes/nodeFuncs.h"
+#include "nodes/parsenodes.h"
#include "optimizer/clauses.h"
#include "optimizer/planmain.h"
+#include "optimizer/var.h"
#include "storage/lmgr.h"
#include "utils/array.h"
#include "utils/builtins.h"
@@ -39,12 +46,55 @@
#include "utils/fmgroids.h"
#include "utils/inval.h"
#include "utils/lsyscache.h"
+#include "utils/rel.h"
#include "utils/syscache.h"
+typedef struct translate_var_attno_mutator_context
+{
+ AttrNumber old_attno;
+ AttrNumber new_attno;
+} translate_var_attno_mutator_context;
+
static PartitionKey CopyPartitionKey(PartitionKey fromkey);
static KeyTypeCollInfo *copy_key_type_coll_info(int nkeycols,
KeyTypeCollInfo *tcinfo);
static void free_key_type_coll_info(KeyTypeCollInfo *tcinfo);
+static bool list_overlaps_existing_partition(PartitionKey key,
+ PartitionListSpec *list_spec,
+ PartitionDesc pdesc,
+ Oid *with);
+static bool partition_range_empty(PartitionKey key,
+ PartitionRangeSpec *range_spec);
+static int32 compare_range_keys(PartitionKey key, Datum *val1, Datum *val2);
+static bool range_overlaps_existing_partition(PartitionKey key,
+ PartitionRangeSpec *range_spec,
+ PartitionDesc pdesc,
+ Oid *with);
+static PartitionListBound *make_list_from_spec(PartitionKey key, List *values);
+static PartitionRangeBound *make_range_from_spec(PartitionKey key, List *val,
+ bool inclusive, bool lower);
+static bool partition_range_overlaps(PartitionKey key,
+ PartitionRangeBound *part1_lower,
+ PartitionRangeBound *part1_upper,
+ PartitionRangeBound *part2_lower,
+ PartitionRangeBound *part2_upper);
+
+static List *get_partitions(Oid relid, int lockmode);
+
+static Expr *get_check_expr_for_list(PartitionKey key, List *values);
+static Expr *get_check_expr_for_range(PartitionKey key,
+ List *lowervalues,
+ bool lowerinc,
+ List *uppervalues,
+ bool upperinc);
+static Node *translate_var_attno(Node *expr, AttrNumber attno,
+ AttrNumber new_attno);
+static Node *translate_var_attno_mutator(Node *node,
+ translate_var_attno_mutator_context *cxt);
+static Node *generate_partition_check_expr(Relation rel);
+
+static PartitionDescNode GetPartitionDescNodeRecurse(Relation rel, int offset);
+static int get_leaf_partition_count(PartitionDescNode pdnode);
/*
* StorePartitionKey
@@ -448,3 +498,1240 @@ FreePartitionKey(PartitionKey key)
free_key_type_coll_info(key->tcinfo);
pfree(key);
}
+
+/*
+ * StorePartitionBound
+ *
+ * Store partition boundary info in pg_partition after checking for its
+ * validity.
+ */
+void
+StorePartitionBound(Oid relid, Oid parentId, Node *bound)
+{
+ Relation pg_partition;
+ Relation parent;
+ PartitionKey key;
+ PartitionDesc pdesc;
+ Oid with;
+ HeapTuple tuple;
+ char *boundString;
+ Datum values[Natts_pg_partition];
+ bool nulls[Natts_pg_partition];
+ ParseState *pstate = make_parsestate(NULL);
+
+ parent = heap_open(parentId, AccessShareLock);
+ key = RelationGetPartitionKey(parent);
+ pdesc = RelationGetPartitionDesc(parent);
+
+ /*
+ * Check if 'bound' happens to overlap with any of parent's existing
+ * partitions.
+ */
+ switch (key->strategy)
+ {
+ case PARTITION_STRAT_LIST:
+ {
+ PartitionListSpec *list;
+
+ Assert(IsA(bound, PartitionListSpec));
+ list = (PartitionListSpec *) bound;
+ if (list_overlaps_existing_partition(key, list, pdesc, &with))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("new partition's list of values overlaps with that"
+ " of partition \"%s\" of \"%s\"",
+ get_rel_name(with), get_rel_name(parentId)),
+ errhint("Please specify a list of values that does not"
+ " overlap with any existing list partition's list."),
+ parser_errposition(pstate, list->location)));
+ break;
+ }
+
+ case PARTITION_STRAT_RANGE:
+ {
+ PartitionRangeSpec *range;
+
+ Assert(IsA(bound, PartitionRangeSpec));
+ range = (PartitionRangeSpec *) bound;
+ if (partition_range_empty(key, range))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("new partition's range contains no element"),
+ errhint("Please specify a non-empty range."),
+ parser_errposition(pstate, range->location)));
+
+ if (range_overlaps_existing_partition(key, range, pdesc, &with))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("new partition's range overlaps with that of"
+ " partition \"%s\" of \"%s\"",
+ get_rel_name(with), get_rel_name(parentId)),
+ errhint("Please specify a range that does not overlap"
+ " with any existing partition's range."),
+ parser_errposition(pstate, range->location)));
+ break;
+ }
+ }
+
+ pg_partition = heap_open(PartitionRelationId, RowExclusiveLock);
+
+ boundString = nodeToString(bound);
+
+ /* Build the tuple - bound can never be null */
+ MemSet(nulls, false, sizeof(nulls));
+
+ values[Anum_pg_partition_partrelid - 1] = ObjectIdGetDatum(relid);
+ values[Anum_pg_partition_partbound - 1] = CStringGetTextDatum(boundString);
+
+ tuple = heap_form_tuple(RelationGetDescr(pg_partition), values, nulls);
+
+ simple_heap_insert(pg_partition, tuple);
+
+ /* Update the indexes on pg_partition */
+ CatalogUpdateIndexes(pg_partition, tuple);
+
+ /*
+ * Invalid the parent's relcache so that the partition is now included in
+ * its partition descriptor.
+ */
+ CacheInvalidateRelcache(parent);
+
+ heap_close(parent, AccessShareLock);
+ heap_close(pg_partition, RowExclusiveLock);
+}
+
+/*
+ * Does new list partition 'bound' overlap any of existing list partitions?
+ */
+static bool
+list_overlaps_existing_partition(PartitionKey key,
+ PartitionListSpec *list_spec,
+ PartitionDesc pdesc,
+ Oid *with)
+{
+ PartitionListBound *new_list;
+ int i;
+
+ if (pdesc->nparts == 0)
+ return false;
+
+ new_list = make_list_from_spec(key, list_spec->values);
+
+ for (i = 0; i < pdesc->nparts; i++)
+ {
+ int j;
+
+ if (pdesc->lists[i]->contains_null && new_list->contains_null)
+ return true;
+
+ for (j = 0; j < pdesc->lists[i]->nvalues; j++)
+ {
+ int k;
+
+ for (k = 0; k < new_list->nvalues; k++)
+ {
+ int32 result;
+
+ result = DatumGetInt32(FunctionCall2Coll(&key->partsupfunc[0],
+ key->tcinfo->typcoll[0],
+ pdesc->lists[i]->values[j],
+ new_list->values[k]));
+ if (!result)
+ {
+ *with = pdesc->oids[i];
+ return true;
+ }
+ }
+ }
+ }
+
+ return false;
+}
+
+/*
+ * Is new partition's range empty?
+ */
+static bool
+partition_range_empty(PartitionKey key, PartitionRangeSpec *range_spec)
+{
+ PartitionRangeBound *lower,
+ *upper;
+
+ lower = make_range_from_spec(key, range_spec->lower, range_spec->lowerinc,
+ true);
+ upper = make_range_from_spec(key, range_spec->upper, range_spec->upperinc,
+ false);
+
+ /*
+ * Only either lower or upper is allowed to be unbounded, which if
+ * one of them is, the range is not empty.
+ */
+ if (lower->infinite || upper->infinite)
+ return false;
+
+ /*
+ * If upper > lower, ouright empty or if lower = upper and either of
+ * them is exclusive.
+ */
+ if (compare_range_keys(key, upper->val, lower->val) < 0 ||
+ (compare_range_keys(key, lower->val, upper->val) == 0 &&
+ (!lower->inclusive || !upper->inclusive)))
+ return true;
+
+ return false;
+}
+
+/* Compare range composite keys using logic similar to RowCompareExpr */
+static int32
+compare_range_keys(PartitionKey key, Datum *val1, Datum *val2)
+{
+ int32 result;
+ int i;
+
+ for (i = 0; i < key->partnatts; i++)
+ {
+ result = DatumGetInt32(FunctionCall2Coll(&key->partsupfunc[i],
+ key->tcinfo->typcoll[i],
+ val1[i], val2[i]));
+ if (!result)
+ continue;
+ break;
+ }
+
+ return result;
+}
+
+/*
+ * Does new range partition's range overlap any of existing range partitions?
+ */
+static bool
+range_overlaps_existing_partition(PartitionKey key,
+ PartitionRangeSpec *range_spec,
+ PartitionDesc pdesc,
+ Oid *with)
+{
+ int i;
+ PartitionRangeBound *new_lower,
+ *new_upper;
+
+ if (pdesc->nparts == 0)
+ return false;
+
+ /*
+ * Create internal representation of rangelower and rangeupper from
+ * range_spec.
+ */
+ new_lower = make_range_from_spec(key, range_spec->lower, range_spec->lowerinc,
+ true);
+ new_upper = make_range_from_spec(key, range_spec->upper, range_spec->upperinc,
+ false);
+
+ /* Check with existing partitions for overlap */
+ for (i = 0; i < pdesc->nparts; i++)
+ {
+ if (partition_range_overlaps(key, new_lower, new_upper,
+ pdesc->rangelowers[i],
+ pdesc->rangeuppers[i]))
+ {
+ *with = pdesc->oids[i];
+ return true;
+ }
+ }
+
+ return false;
+}
+
+/*
+ * Make a PartitionListBound from list of values using the information in key
+ */
+static PartitionListBound *
+make_list_from_spec(PartitionKey key, List *values)
+{
+ PartitionListBound *list;
+ ListCell *cell;
+ int i;
+
+ list = (PartitionListBound *) palloc0(sizeof(PartitionListBound));
+ list->nvalues = list_length(values);
+ list->values = (Datum *) palloc(list->nvalues * sizeof(Datum));
+
+ i = 0;
+ foreach (cell, values)
+ {
+ Const *val = lfirst(cell);
+
+ if (val->constisnull)
+ list->contains_null = true;
+ else
+ list->values[i] = datumCopy(val->constvalue,
+ key->tcinfo->typbyval[0],
+ key->tcinfo->typlen[0]);
+ i++;
+ }
+
+ return list;
+}
+
+/*
+ * Make a PartitionRangeBound from arguments using the information in key
+ */
+static PartitionRangeBound *
+make_range_from_spec(PartitionKey key, List *val, bool inclusive, bool lower)
+{
+ PartitionRangeBound *range;
+ ListCell *cell;
+
+ range = (PartitionRangeBound *) palloc0(sizeof(PartitionRangeBound));
+ range->infinite = (val == NIL);
+ range->inclusive = inclusive;
+ range->lower = lower;
+
+ if (val)
+ {
+ int i;
+
+ range->val = (Datum *) palloc0(key->partnatts * sizeof(Datum));
+
+ i = 0;
+ foreach (cell, val)
+ {
+ Const *val = lfirst(cell);
+
+ if (val->constisnull)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("cannot specify NULL in range bound")));
+ else
+ range->val[i] = datumCopy(val->constvalue,
+ key->tcinfo->typbyval[i],
+ key->tcinfo->typlen[i]);
+ i++;
+ }
+ }
+
+ return range;
+}
+
+/* Check two range partitions for overlap */
+static bool
+partition_range_overlaps(PartitionKey key,
+ PartitionRangeBound *part1_lower,
+ PartitionRangeBound *part1_upper,
+ PartitionRangeBound *part2_lower,
+ PartitionRangeBound *part2_upper)
+{
+ if (partition_range_cmp_bounds(key, part1_lower, part2_lower) >= 0 &&
+ partition_range_cmp_bounds(key, part1_lower, part2_upper) <= 0)
+ return true;
+
+ if (partition_range_cmp_bounds(key, part2_lower, part1_lower) >= 0 &&
+ partition_range_cmp_bounds(key, part2_lower, part1_upper) <= 0)
+ return true;
+
+ return false;
+}
+
+/* Returns for two range partition bounds whether, b1 <=, =, >= b2 */
+int32
+partition_range_cmp_bounds(PartitionKey key,
+ PartitionRangeBound *b1,
+ PartitionRangeBound *b2)
+{
+ int32 result;
+
+ /*
+ * First, handle cases involving infinity, which don't require invoking
+ * the comparison proc.
+ */
+ if (b1->infinite && b2->infinite)
+ {
+ /*
+ * Both are infinity, so they are equal unless one is lower and the
+ * other not.
+ */
+ if (b1->lower == b2->lower)
+ return 0;
+ else
+ return b1->lower ? -1 : 1;
+ }
+ else if (b1->infinite)
+ return b1->lower ? -1 : 1;
+ else if (b2->infinite)
+ return b2->lower ? 1 : -1;
+
+ /*
+ * Both boundaries are finite, so compare the held values.
+ */
+ result = compare_range_keys(key, b1->val, b2->val);
+
+ /*
+ * If the comparison is anything other than equal, we're done. If they
+ * compare equal though, we still have to consider whether the boundaries
+ * are inclusive or exclusive.
+ */
+ if (result == 0)
+ {
+ if (!b1->inclusive && !b2->inclusive)
+ {
+ /* both are exclusive */
+ if (b1->lower == b2->lower)
+ return 0;
+ else
+ return b1->lower ? 1 : -1;
+ }
+ else if (!b1->inclusive)
+ return b1->lower ? 1 : -1;
+ else if (!b2->inclusive)
+ return b2->lower ? -1 : 1;
+ else
+ {
+ /*
+ * Both are inclusive and the values held are equal, so they are
+ * equal regardless of whether they are upper or lower boundaries,
+ * or a mix.
+ */
+ return 0;
+ }
+ }
+
+ return result;
+}
+
+/*
+ * RemovePartitionByRelId
+ * Remove a pg_partition entry for a partition.
+ */
+void
+RemovePartitionEntryByRelId(Oid relid)
+{
+ Relation rel;
+ HeapTuple tuple;
+
+ rel = heap_open(PartitionRelationId, RowExclusiveLock);
+ tuple = SearchSysCache1(PARTRELID, ObjectIdGetDatum(relid));
+
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "cache lookup failed for partition entry of relation %u",
+ relid);
+
+ simple_heap_delete(rel, &tuple->t_self);
+
+ /* Update the indexes on pg_partitioned_rel */
+ CatalogUpdateIndexes(rel, tuple);
+
+ ReleaseSysCache(tuple);
+ heap_close(rel, RowExclusiveLock);
+}
+
+/*
+ * does relid have pg_partition entry?
+ */
+bool
+relid_is_partition(Oid relid)
+{
+ return SearchSysCacheExists1(PARTRELID, ObjectIdGetDatum(relid));
+}
+
+/*
+ * Returns inheritance parent of relid by scanning pg_inherits
+ */
+Oid
+get_partition_parent(Oid relid)
+{
+ Form_pg_inherits form;
+ Relation catalogRelation;
+ SysScanDesc scan;
+ ScanKeyData key[2];
+ HeapTuple tuple;
+
+ catalogRelation = heap_open(InheritsRelationId, AccessShareLock);
+
+ ScanKeyInit(&key[0],
+ Anum_pg_inherits_inhrelid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(relid));
+ ScanKeyInit(&key[1],
+ Anum_pg_inherits_inhseqno,
+ BTEqualStrategyNumber, F_INT4EQ,
+ Int32GetDatum(1));
+
+ scan = systable_beginscan(catalogRelation, InheritsRelidSeqnoIndexId, true,
+ NULL, 2, key);
+
+ tuple = systable_getnext(scan);
+ Assert(HeapTupleIsValid(tuple));
+
+ form = (Form_pg_inherits) GETSTRUCT(tuple);
+
+ systable_endscan(scan);
+ heap_close(catalogRelation, AccessShareLock);
+
+ return form->inhparent;
+}
+
+/*
+ * RelationDropPartitions
+ * Find and drop all partitions of relation with OID 'relid'
+ */
+void
+RelationDropPartitions(Oid relid)
+{
+ List *partitionOids;
+ ListCell *cell;
+
+ partitionOids = get_partitions(relid, AccessExclusiveLock);
+
+ foreach (cell, partitionOids)
+ {
+ Oid partitionOid = lfirst_oid(cell);
+ ObjectAddress obj;
+
+ obj.classId = RelationRelationId;
+ obj.objectId = partitionOid;
+ obj.objectSubId = 0;
+
+ performDeletion(&obj, DROP_CASCADE, 0);
+ }
+}
+
+/*
+ * get_partition_info
+ * Return information of partitions of rel
+ *
+ * Returns number of partitions in *nparts.
+ */
+PartitionInfo **
+get_partition_info(Relation rel, int *nparts)
+{
+ int i;
+ Relation partrel;
+ List *partoids;
+ ListCell *cell;
+ PartitionInfo **parts;
+ PartitionKey key = RelationGetPartitionKey(rel);
+
+ partoids = get_partitions(RelationGetRelid(rel), NoLock);
+
+ /* no partitions found */
+ if ((*nparts = list_length(partoids)) == 0)
+ return NULL;
+
+ partrel = heap_open(PartitionRelationId, AccessShareLock);
+
+ parts = (PartitionInfo **) palloc0(*nparts * sizeof(PartitionInfo *));
+
+ i = 0;
+ foreach(cell, partoids)
+ {
+ Oid partrelid = lfirst_oid(cell);
+ HeapTuple tuple;
+ Datum datum;
+ bool isnull;
+ Node *bound;
+
+ tuple = SearchSysCache1(PARTRELID, partrelid);
+ Assert(HeapTupleIsValid(tuple));
+
+ parts[i] = (PartitionInfo *) palloc0(sizeof(PartitionInfo));
+ parts[i]->oid = partrelid;
+
+ datum = fastgetattr(tuple,
+ Anum_pg_partition_partbound,
+ partrel->rd_att,
+ &isnull);
+
+ bound = stringToNode(TextDatumGetCString(datum));
+
+ switch (key->strategy)
+ {
+ case PARTITION_STRAT_LIST:
+ {
+ PartitionListSpec *list_spec;
+
+ Assert(IsA(bound, PartitionListSpec));
+ list_spec = (PartitionListSpec *) bound;
+ parts[i]->list = make_list_from_spec(key, list_spec->values);
+ }
+ break;
+
+ case PARTITION_STRAT_RANGE:
+ {
+ PartitionRangeSpec *range_spec;
+
+ Assert(IsA(bound, PartitionRangeSpec));
+ range_spec = (PartitionRangeSpec *) bound;
+
+ parts[i]->rangelower = make_range_from_spec(key,
+ range_spec->lower,
+ range_spec->lowerinc,
+ true);
+ parts[i]->rangeupper = make_range_from_spec(key,
+ range_spec->upper,
+ range_spec->upperinc,
+ false);
+ }
+ break;
+ }
+
+ ReleaseSysCache(tuple);
+ i++;
+ }
+
+ heap_close(partrel, AccessShareLock);
+
+ return parts;
+}
+
+/*
+ * Wrapper around find_inheritance_children for use by partitioning
+ * related code
+ */
+static List *
+get_partitions(Oid relid, int lockmode)
+{
+ return find_inheritance_children(relid, lockmode);
+}
+
+/*
+ * Helper routine to deep-free a PartitionInfo array
+ */
+void
+free_partition_info(PartitionInfo **p, int num)
+{
+ int i;
+
+ for (i = 0; i < num; i++)
+ {
+ if (p[i]->list)
+ {
+ pfree(p[i]->list->values);
+ pfree(p[i]->list);
+ }
+
+ if (p[i]->rangelower)
+ {
+ if (p[i]->rangelower->val)
+ pfree(p[i]->rangelower->val);
+ pfree(p[i]->rangelower);
+ }
+
+ if (p[i]->rangeupper)
+ {
+ if (p[i]->rangeupper->val)
+ pfree(p[i]->rangeupper->val);
+ pfree(p[i]->rangeupper);
+ }
+
+ pfree(p[i]);
+ }
+
+ pfree(p);
+}
+
+/*
+ * get_check_expr_from_partbound - for direct use by tablecmds.c
+ */
+Expr *
+get_check_expr_from_partbound(Relation rel, Relation parent, Node *bound)
+{
+ PartitionKey key = RelationGetPartitionKey(parent);
+ Expr *my_expr;
+ int i;
+ ListCell *partexprs_item;
+
+ if (IsA(bound, PartitionListSpec))
+ {
+ PartitionListSpec *list_spec = (PartitionListSpec *) bound;
+
+ my_expr = get_check_expr_for_list(key, list_spec->values);
+ }
+ else if (IsA(bound, PartitionRangeSpec))
+ {
+ PartitionRangeSpec *range_spec = (PartitionRangeSpec *) bound;
+
+ my_expr = get_check_expr_for_range(key,
+ range_spec->lower,
+ range_spec->lowerinc,
+ range_spec->upper,
+ range_spec->upperinc);
+ }
+
+ /*
+ * Translate vars in the generated expression with the correct attnos.
+ * Note that the vars in my_expr bear attnos dictated by key which carries
+ * attnos of the parent.
+ */
+ partexprs_item = list_head(key->partexprs);
+ for (i = 0; i < key->partnatts; i++)
+ {
+ AttrNumber attno = key->partattrs[i],
+ new_attno;
+ char *attname;
+
+ if (attno != 0)
+ {
+ attname = get_attname(RelationGetRelid(parent), attno);
+ new_attno = get_attnum(RelationGetRelid(rel), attname);
+
+ my_expr = (Expr *) translate_var_attno((Node *) my_expr,
+ attno,
+ new_attno);
+ }
+ else
+ {
+ Node *expr = (Node *) lfirst(partexprs_item);
+ Bitmapset *expr_attrs = NULL;
+ int index;
+
+ pull_varattnos(expr, 1, &expr_attrs);
+ partexprs_item = lnext(partexprs_item);
+
+ index = -1;
+ while ((index = bms_next_member(expr_attrs, index)) > 0)
+ {
+ AttrNumber attno = index + FirstLowInvalidHeapAttributeNumber;
+ attname = get_attname(RelationGetRelid(parent), attno);
+ new_attno = get_attnum(RelationGetRelid(rel), attname);
+ my_expr = (Expr *) translate_var_attno((Node *) my_expr,
+ attno,
+ new_attno);
+ }
+ }
+ }
+
+ return my_expr;
+}
+
+/*
+ * get_check_expr_for_list
+ */
+static Expr *
+get_check_expr_for_list(PartitionKey key, List *values)
+{
+ ArrayExpr *values_arr = makeNode(ArrayExpr);
+ ScalarArrayOpExpr *arrayop_expr;
+ Node *key_col;
+ Oid operoid;
+ StrategyNumber strategy = BTEqualStrategyNumber;
+
+ /* Btree equality operator of specified opfamily */
+ operoid = get_opfamily_member(key->partopfamily[0],
+ key->tcinfo->typid[0],
+ key->tcinfo->typid[0],
+ strategy);
+
+ /* lexpr */
+ if (key->partattrs[0] != 0)
+ key_col = (Node *) makeVar(1, key->partattrs[0],
+ key->tcinfo->typid[0],
+ key->tcinfo->typmod[0],
+ key->tcinfo->typcoll[0],
+ 0);
+ else
+ key_col = (Node *) linitial(key->partexprs);
+
+ /* rexpr */
+ values_arr->array_typeid = get_array_type(key->tcinfo->typid[0]);
+ values_arr->array_collid = key->tcinfo->typcoll[0];
+ values_arr->element_typeid = key->tcinfo->typid[0];
+ values_arr->elements = values;
+ values_arr->multidims = false;
+ values_arr->location = -1;
+
+ /* build the actual predicate expression node */
+ arrayop_expr = makeNode(ScalarArrayOpExpr);
+ arrayop_expr->opno = operoid;
+ arrayop_expr->opfuncid = get_opcode(operoid);
+ arrayop_expr->useOr = true;
+ arrayop_expr->inputcollid = key->tcinfo->typcoll[0];
+ arrayop_expr->args = list_make2(key_col, values_arr);
+ arrayop_expr->location = -1;
+
+ return (Expr *) arrayop_expr;
+}
+
+/*
+ * get_check_expr_for_range
+ */
+static Expr *
+get_check_expr_for_range(PartitionKey key,
+ List *lowervalues,
+ bool lowerinc,
+ List *uppervalues,
+ bool upperinc)
+{
+ List *and_args = NIL;
+ ListCell *cell1,
+ *cell2,
+ *partexprs_item;
+ int i;
+ uint16 strategy;
+
+ /*
+ * Handle the case with only one bound.
+ *
+ * In this case, consider only the first column of the key and generate
+ * appropriate opclause since only the comparison with the first column
+ * would have determined whether a rows went into such a partition.
+ * It's always true that -INF < someval and someval < +INF.
+ */
+ if (lowervalues == NIL || uppervalues == NIL)
+ {
+ List *values;
+ Const *key_val;
+ Node *key_col;
+ Oid operoid;
+ bool islower,
+ inclusive;
+
+ if (lowervalues != NIL)
+ {
+ values = lowervalues;
+ islower = true;
+ inclusive = lowerinc;
+ }
+ else
+ {
+ values = uppervalues;
+ islower = false;
+ inclusive = upperinc;
+ }
+
+ /* lexpr */
+ if (key->partattrs[0] != 0)
+ key_col = (Node *) makeVar(1, key->partattrs[0],
+ key->tcinfo->typid[0],
+ key->tcinfo->typmod[0],
+ key->tcinfo->typcoll[0],
+ 0);
+ else
+ key_col = (Node *) linitial(key->partexprs);
+
+ /* rexpr */
+ key_val = linitial(values);
+
+ if (islower)
+ strategy = inclusive ? BTGreaterEqualStrategyNumber : BTGreaterStrategyNumber;
+ else
+ strategy = inclusive ? BTLessEqualStrategyNumber : BTLessStrategyNumber;
+
+ operoid = get_opfamily_member(key->partopfamily[0],
+ key->tcinfo->typid[0],
+ key->tcinfo->typid[0],
+ strategy);
+
+ and_args = lappend(and_args,
+ make_opclause(operoid, BOOLOID,
+ false,
+ (Expr *) key_col,
+ (Expr *) key_val,
+ InvalidOid,
+ key->tcinfo->typcoll[0]));
+
+ return makeBoolExpr(AND_EXPR, and_args, -1);
+ }
+
+ /* Both bounds constrain values going into the partition. */
+ i = 0;
+ partexprs_item = list_head(key->partexprs);
+ forboth (cell1, lowervalues, cell2, uppervalues)
+ {
+ Node *key_col;
+ Const *lower_val = lfirst(cell1);
+ Const *upper_val = lfirst(cell2);
+ Expr *lower_clause,
+ *upper_clause;
+ Oid eqop,
+ lowerop,
+ upperop;
+ EState *estate;
+ MemoryContext oldcxt;
+ Expr *test_expr;
+ ExprState *test_exprstate;
+ Datum test_result;
+ bool isNull;
+
+ /* lexpr */
+ if (key->partattrs[i] != 0)
+ {
+ key_col = (Node *) makeVar(1, key->partattrs[i],
+ key->tcinfo->typid[i],
+ key->tcinfo->typmod[i],
+ key->tcinfo->typcoll[i],
+ 0);
+ }
+ else
+ {
+ key_col = (Node *) lfirst(partexprs_item);
+ partexprs_item = lnext(partexprs_item);
+ }
+
+ /*
+ * If lower_val = upper_val (= val, say), create a column = val
+ * constraint.
+ */
+ eqop = get_opfamily_member(key->partopfamily[i],
+ key->tcinfo->typid[i],
+ key->tcinfo->typid[i],
+ BTEqualStrategyNumber);
+
+ estate = CreateExecutorState();
+ oldcxt = MemoryContextSwitchTo(estate->es_query_cxt);
+ test_expr = make_opclause(eqop,
+ BOOLOID,
+ false,
+ (Expr *) lower_val,
+ (Expr *) upper_val,
+ InvalidOid,
+ key->tcinfo->typcoll[i]);
+ fix_opfuncids((Node *) test_expr);
+ test_exprstate = ExecInitExpr(test_expr, NULL);
+ test_result = ExecEvalExprSwitchContext(test_exprstate,
+ GetPerTupleExprContext(estate),
+ &isNull, NULL);
+ MemoryContextSwitchTo(oldcxt);
+ FreeExecutorState(estate);
+
+ if (DatumGetBool(test_result))
+ {
+ and_args = lappend(and_args,
+ make_opclause(eqop,
+ BOOLOID,
+ false,
+ (Expr *) key_col,
+ /* could've been upper_val */
+ (Expr *) lower_val,
+ InvalidOid,
+ key->tcinfo->typcoll[i]));
+
+ /* Go to the next column. */
+ }
+ else
+ {
+ /*
+ * For all columns but last, use inclusive strategy (<= or >=).
+ *
+ * For the last column, the bound's inclusivity determines.
+ */
+ strategy = i < key->partnatts - 1 || lowerinc
+ ? BTGreaterEqualStrategyNumber
+ : BTGreaterStrategyNumber;
+ lowerop = get_opfamily_member(key->partopfamily[i],
+ key->tcinfo->typid[i],
+ key->tcinfo->typid[i],
+ strategy);
+ strategy = i < key->partnatts - 1 || upperinc
+ ? BTLessEqualStrategyNumber
+ : BTLessStrategyNumber;
+ upperop = get_opfamily_member(key->partopfamily[i],
+ key->tcinfo->typid[i],
+ key->tcinfo->typid[i],
+ strategy);
+
+ lower_clause = make_opclause(lowerop,
+ BOOLOID,
+ false,
+ (Expr *) key_col,
+ (Expr *) lower_val,
+ InvalidOid,
+ key->tcinfo->typcoll[i]);
+ upper_clause = make_opclause(upperop,
+ BOOLOID,
+ false,
+ (Expr *) key_col,
+ (Expr *) upper_val,
+ InvalidOid,
+ key->tcinfo->typcoll[i]);
+
+ and_args = lappend(and_args,
+ makeBoolExpr(AND_EXPR,
+ list_make2(lower_clause, upper_clause), -1));
+
+ /* No need to constraint further columns. */
+ break;
+ }
+
+ i++;
+ }
+
+ return makeBoolExpr(AND_EXPR, and_args, -1);
+}
+
+/*
+ * translate_var_attno
+ * Changes Vars with a given attno in the provided expression tree to
+ * Vars with new_attno
+ */
+static Node *
+translate_var_attno(Node *expr, AttrNumber attno, AttrNumber new_attno)
+{
+ translate_var_attno_mutator_context cxt;
+
+ cxt.old_attno = attno;
+ cxt.new_attno = new_attno;
+
+ return expression_tree_mutator(expr, translate_var_attno_mutator, &cxt);
+}
+
+/*
+ * translate_var_attno_mutator
+ */
+static Node *
+translate_var_attno_mutator(Node *node,
+ translate_var_attno_mutator_context *cxt)
+{
+ if (node == NULL)
+ return NULL;
+
+ if (IsA(node, Var) && ((Var *) node)->varattno == cxt->old_attno)
+ {
+ Var *newvar = copyObject(node);
+
+ newvar->varattno = cxt->new_attno;
+
+ return (Node *) newvar;
+ }
+
+ return expression_tree_mutator(node, translate_var_attno_mutator,
+ (void *) cxt);
+}
+
+/*
+ * RelationGetPartitionConstraint
+ * Returns the equivelent CHECK constraint expression for a partition
+ */
+Node *
+RelationGetPartitionCheckExpr(Relation rel)
+{
+ /* Quick exit */
+ if (!relid_is_partition(RelationGetRelid(rel)))
+ return NULL;
+
+ /* Quick copy */
+ if (rel->rd_partcheck)
+ return copyObject(rel->rd_partcheck);
+
+ /* None of those, generate */
+ return generate_partition_check_expr(rel);
+}
+
+/*
+ * generate_partition_constraints
+ * Generate partition check constraint expression rel's pg_partition
+ * entry
+ *
+ * Result expression tree is stored CacheMemoryContext to ensure it survives
+ * as long as the relcache entry. But we should be running in a less long-lived
+ * working context. To avoid leaking cache memory if this routine fails partway
+ * routine fails partway through, we build in working memory and then copy
+ * the completed structure into cache memory.
+ */
+static Node *
+generate_partition_check_expr(Relation rel)
+{
+ HeapTuple tuple;
+ MemoryContext oldcxt;
+ Datum boundDatum;
+ bool isnull;
+ Node *bound;
+ Node *my_expr = NULL,
+ *result = NULL;
+ Oid relid = RelationGetRelid(rel);
+ Relation parent = heap_open(get_partition_parent(relid), NoLock);
+
+ /* quick copy */
+ if (rel->rd_partcheck)
+ {
+ heap_close(parent, NoLock);
+ return copyObject(rel->rd_partcheck);
+ }
+
+ tuple = SearchSysCache1(PARTRELID, relid);
+ Assert(HeapTupleIsValid(tuple));
+ boundDatum = SysCacheGetAttr(PARTRELID, tuple,
+ Anum_pg_partition_partbound,
+ &isnull);
+ Assert(!isnull);
+ bound = stringToNode(TextDatumGetCString(boundDatum));
+
+ my_expr = (Node *) get_check_expr_from_partbound(rel, parent, bound);
+
+ /*
+ * If parent is also a partition, create a AND_EXPR BoolExpr with just
+ * created expression and parent's check expression, as its args.
+ */
+ if (relid_is_partition(RelationGetRelid(parent)))
+ {
+ Node *parent_expr;
+
+ parent_expr = generate_partition_check_expr(parent);
+ result = (Node *) makeBoolExpr(AND_EXPR,
+ list_make2(parent_expr, my_expr), -1);
+ }
+ else
+ result = my_expr;
+
+ oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
+ rel->rd_partcheck = copyObject(result);
+ MemoryContextSwitchTo(oldcxt);
+
+ ReleaseSysCache(tuple);
+ heap_close(parent, NoLock);
+
+ return result;
+}
+
+/*
+ * RelationGetPartitionDescNode
+ * Recursively form partition node tree rooted at rel's node
+ */
+PartitionDescNode
+RelationGetPartitionDescNode(Relation rel)
+{
+ PartitionDescNode root;
+
+ /*
+ * Recurse to build the tree of partition descriptor nodes with
+ * rel's descriptor as the root of the tree.
+ */
+ root = GetPartitionDescNodeRecurse(rel, 0);
+ root->index = 0;
+ root->num_leaf_partitions = get_leaf_partition_count(root);
+
+ return root;
+}
+
+/*
+ * GetPartitionDescNodeRecurse
+ * Workhorse of RelationGetPartitionDescNode
+ *
+ * offset is the index (0-based) of the first leaf node in the tree, as
+ * as determined and passed by the parent or the left sibling.
+ */
+static PartitionDescNode
+GetPartitionDescNodeRecurse(Relation rel, int offset)
+{
+ PartitionDescNode root,
+ prev;
+ PartitionDesc pdesc = RelationGetPartitionDesc(rel);
+ int i;
+
+ /* First build our own node */
+ root = (PartitionDescNode) palloc0(sizeof(PartitionDescNodeData));
+ root->pdesc = pdesc;
+ root->relid = RelationGetRelid(rel);
+ root->offset = offset;
+ root->downlink = NULL;
+ root->next = NULL;
+
+ /*
+ * Recursively add nodes for partitions that are themselves partitioned,
+ * linking ourselves to the first such node via 'downlink'. Each node
+ * thus created (being on the same level) is linked to the right sibling
+ * via 'next'. For each node, determine the offset to pass and once
+ * created, update its index and num_leaf_partitions.
+ */
+ prev = NULL;
+ for (i = 0; i < pdesc->nparts; i++)
+ {
+ Oid relid = pdesc->oids[i];
+ int offset;
+ Relation rel;
+ PartitionDescNode pdnode;
+
+ if (!relid_is_partitioned(relid))
+ continue;
+
+ rel = heap_open(relid, AccessShareLock);
+
+ /* Offset for the new node */
+ if (prev)
+ /*
+ * Count leaves on the left - prev node has the account of those
+ * on its own left and then there may have been some siblings
+ * in-between that were leaves themselves.
+ */
+ offset = prev->offset + prev->num_leaf_partitions +
+ (i - prev->index - 1);
+ else
+ /*
+ * We're the first child - account of leaf nodes comes from the
+ * parent. Left siblings were all leaves themselves.
+ */
+ offset = root->offset + i;
+
+ pdnode = GetPartitionDescNodeRecurse(rel, offset);
+ pdnode->index = i;
+ pdnode->num_leaf_partitions = get_leaf_partition_count(pdnode);
+
+ heap_close(rel, AccessShareLock);
+
+ /* Found our first child; link to it. */
+ if (root->downlink == NULL)
+ root->downlink = pdnode;
+
+ /* Link new node to the left sibling, if any */
+ if (prev)
+ prev->next = pdnode;
+
+ prev = pdnode;
+ }
+
+ return root;
+}
+
+/*
+ * get_leaf_partition_count
+ * Recursively count the number of leaf partitions in the partition
+ * tree rooted at pdnode
+ */
+static int
+get_leaf_partition_count(PartitionDescNode pdnode)
+{
+ int i;
+ int result = 0;
+ PartitionDescNode node = pdnode->downlink;
+
+ for (i = 0; i < pdnode->pdesc->nparts; i++)
+ {
+ /* Indexes 0..(node->index - 1) are of leaf partitions */
+ if (node && i == node->index)
+ {
+ result += get_leaf_partition_count(node);
+ node = node->next;
+ }
+ else
+ result += 1;
+ }
+
+ return result;
+}
+
+/*
+ * get_leaf_partition_count
+ * Recursively compute the list of OIDs of leaf partitions in the
+ * partition tree rooted at pdnode
+ */
+List *
+get_leaf_partition_oids(PartitionDescNode pdnode)
+{
+ int i;
+ List *result = NIL;
+ Oid *myparts = pdnode->pdesc->oids;
+ int n_myparts = pdnode->pdesc->nparts;
+ PartitionDescNode node = pdnode->downlink;
+
+ for (i = 0; i < n_myparts; i++)
+ {
+ /* Indexes 0..(node->index - 1) are leaf partitions */
+ if (node && i == node->index)
+ {
+ result = list_concat(result, get_leaf_partition_oids(node));
+ node = node->next;
+ }
+ else
+ result = lappend_oid(result, myparts[i]);
+ }
+
+ return result;
+}
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 403738d..14e81de 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -2464,7 +2464,7 @@ CopyFrom(CopyState cstate)
if (!skip_tuple)
{
/* Check the constraints of the tuple */
- if (cstate->rel->rd_att->constr)
+ if (cstate->rel->rd_att->constr || resultRelInfo->ri_PartitionCheck)
ExecConstraints(resultRelInfo, slot, estate);
if (useHeapMultiInsert)
diff --git a/src/backend/commands/lockcmds.c b/src/backend/commands/lockcmds.c
index 62671a8..5120c93 100644
--- a/src/backend/commands/lockcmds.c
+++ b/src/backend/commands/lockcmds.c
@@ -16,6 +16,7 @@
#include "access/heapam.h"
#include "catalog/namespace.h"
+#include "catalog/partition.h"
#include "catalog/pg_inherits_fn.h"
#include "commands/lockcmds.h"
#include "miscadmin.h"
@@ -63,7 +64,8 @@ LockTableCommand(LockStmt *lockstmt)
RangeVarCallbackForLockTable,
(void *) &lockstmt->mode);
- if (recurse)
+ /* Force inheritance recursion, if partitioned table. */
+ if (recurse || relid_is_partitioned(reloid))
LockTableRecurse(reloid, lockstmt->mode, lockstmt->nowait);
}
}
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 192cae9..b5a1136 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -121,6 +121,19 @@ static List *on_commits = NIL;
/*
+ * For AT ATTACH PARTITION <table> FOR VALUES <partition_bound_spec>,
+ *
+ * Struct describing partition check expression corresponding to
+ * <partition_bound_spec> to validate for rows in <table> during
+ * Phase 3 scan.
+ */
+typedef struct PartitionCheck
+{
+ Expr *expr;
+ List *state;
+} PartitionCheck;
+
+/*
* State information for ALTER TABLE
*
* The pending-work queue for an ALTER TABLE is a List of AlteredTableInfo
@@ -163,6 +176,8 @@ typedef struct AlteredTableInfo
Oid newTableSpace; /* new tablespace; 0 means no change */
bool chgPersistence; /* T if SET LOGGED/UNLOGGED is used */
char newrelpersistence; /* if above is true */
+ PartitionCheck *partitionCheck; /* partition check expression for
+ * ATTACH PARTITION validation */
/* Objects to rebuild after completing ALTER TYPE operations */
List *changedConstraintOids; /* OIDs of constraints to rebuild */
List *changedConstraintDefs; /* string definitions of same */
@@ -712,6 +727,19 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
StoreCatalogInheritance(relationId, inheritOids);
/*
+ * Process and store partition bound. Do this before
+ * CommandCounterIncrement() below, so that we are not deemed an existing
+ * partition by StorePartitionBound() (which is basically found out by
+ * querying pg_inherits).
+ */
+ if (stmt->partbound)
+ {
+ Oid parentId = linitial_oid(inheritOids);
+
+ StorePartitionBound(relationId, parentId, stmt->partbound);
+ }
+
+ /*
* We must bump the command counter to make the newly-created relation
* tuple visible for opening.
*/
@@ -995,6 +1023,13 @@ RangeVarCallbackForDropRelation(const RangeVar *rel, Oid relOid, Oid oldRelOid,
if (!OidIsValid(relOid))
return;
+ if (relid_is_partition(relOid))
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("\"%s\" is a partition of \"%s\"", rel->relname,
+ get_rel_name(get_partition_parent(relOid))),
+ errhint("Use ALTER TABLE DETACH PARTITION to be able to drop it.")));
+
tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relOid));
if (!HeapTupleIsValid(tuple))
return; /* concurrently dropped, so nothing to do */
@@ -1086,7 +1121,8 @@ ExecuteTruncate(TruncateStmt *stmt)
rels = lappend(rels, rel);
relids = lappend_oid(relids, myrelid);
- if (recurse)
+ /* Force inheritance recursion, if partitioned table. */
+ if (recurse || relid_is_partitioned(myrelid))
{
ListCell *child;
List *children;
@@ -1810,8 +1846,12 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
Node *expr;
bool found_whole_row;
- /* ignore if the constraint is non-inheritable */
- if (check[i].ccnoinherit)
+ /*
+ * ignore if the constraint is non-inheritable and this is not
+ * the partition case (constraints of partitioned tables are
+ * always present in their partitions)
+ */
+ if (check[i].ccnoinherit && !is_partition)
continue;
/* Adjust Vars to match new table's column numbering */
@@ -2246,6 +2286,11 @@ renameatt_check(Oid myrelid, Form_pg_class classform, bool recursing)
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot rename column of typed table")));
+ if (relid_is_partition(myrelid) && !recursing)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot rename column of a partition")));
+
/*
* Renaming the columns of sequences or toast tables doesn't actually
* break anything from the system's point of view, since internal
@@ -2472,11 +2517,13 @@ renameatt(RenameStmt *stmt)
return InvalidObjectAddress;
}
+ /* Force inheritance recursion, if partitioned table. */
attnum =
renameatt_internal(relid,
stmt->subname, /* old att name */
stmt->newname, /* new att name */
- interpretInhOption(stmt->relation->inhOpt), /* recursive? */
+ interpretInhOption(stmt->relation->inhOpt) ||
+ relid_is_partitioned(relid), /* recursive? */
false, /* recursing? */
0, /* expected inhcount */
stmt->behavior);
@@ -2624,11 +2671,13 @@ RenameConstraint(RenameStmt *stmt)
}
}
+ /* Force inheritance recursion, if partitioned table. */
return
rename_constraint_internal(relid, typid,
stmt->subname,
stmt->newname,
- stmt->relation ? interpretInhOption(stmt->relation->inhOpt) : false, /* recursive? */
+ stmt->relation ? interpretInhOption(stmt->relation->inhOpt) ||
+ relid_is_partitioned(relid) : false, /* recursive? */
false, /* recursing? */
0 /* expected inhcount */ );
@@ -2870,8 +2919,11 @@ AlterTable(Oid relid, LOCKMODE lockmode, AlterTableStmt *stmt)
CheckTableNotInUse(rel, "ALTER TABLE");
+ /* Force inheritance recursion, if partitioned table */
ATController(stmt,
- rel, stmt->cmds, interpretInhOption(stmt->relation->inhOpt),
+ rel, stmt->cmds,
+ interpretInhOption(stmt->relation->inhOpt) ||
+ relid_is_partitioned(RelationGetRelid(rel)),
lockmode);
}
@@ -3988,7 +4040,7 @@ ATRewriteTables(AlterTableStmt *parsetree, List **wqueue, LOCKMODE lockmode)
* Test the current data within the table against new constraints
* generated by ALTER TABLE commands, but don't rebuild data.
*/
- if (tab->constraints != NIL || tab->new_notnull)
+ if (tab->constraints != NIL || tab->new_notnull || tab->partitionCheck)
ATRewriteTable(tab, InvalidOid, lockmode);
/*
@@ -4132,6 +4184,21 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
}
}
+ /*
+ * Build expression execution states for partition check expression
+ * as well.
+ */
+ if (tab->partitionCheck)
+ {
+ List *expr;
+
+ needscan = true;
+ Assert(tab->partitionCheck->expr);
+ expr = list_make1(tab->partitionCheck->expr);
+ tab->partitionCheck->state = (List *)
+ ExecPrepareExpr((Expr *) expr, estate);
+ }
+
foreach(l, tab->newvals)
{
NewColumnValue *ex = lfirst(l);
@@ -4321,6 +4388,12 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
}
}
+ if (tab->partitionCheck &&
+ !ExecQual(tab->partitionCheck->state, econtext, true))
+ ereport(ERROR,
+ (errcode(ERRCODE_CHECK_VIOLATION),
+ errmsg("table contains a row violating the partition bound specification")));
+
/* Write the tuple out to the new relation */
if (newrel)
heap_insert(newrel, tuple, mycid, hi_options, bistate);
@@ -4518,7 +4591,8 @@ ATSimpleRecursion(List **wqueue, Relation rel,
*/
if (recurse &&
(rel->rd_rel->relkind == RELKIND_RELATION ||
- rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE))
+ rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE ||
+ rel->rd_rel->relkind == RELKIND_PARTITIONED_REL))
{
Oid relid = RelationGetRelid(rel);
ListCell *child;
@@ -4840,6 +4914,11 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
if (recursing)
ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
+ if (relid_is_partition(RelationGetRelid(rel)) && !recursing)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot add column to a partition")));
+
attrdesc = heap_open(AttributeRelationId, RowExclusiveLock);
/*
@@ -5295,6 +5374,7 @@ ATExecDropNotNull(Relation rel, const char *colName, LOCKMODE lockmode)
List *indexoidlist;
ListCell *indexoidscan;
ObjectAddress address;
+ Oid relid;
/*
* lookup the attribute
@@ -5362,6 +5442,28 @@ ATExecDropNotNull(Relation rel, const char *colName, LOCKMODE lockmode)
list_free(indexoidlist);
/*
+ * If rel is partition, throw error if the attribute is not null in
+ * parent
+ */
+ relid = RelationGetRelid(rel);
+ if (relid_is_partition(relid))
+ {
+ Oid parentId = get_partition_parent(relid);
+ Relation parent = heap_open(parentId, AccessShareLock);
+ TupleDesc tupDesc = RelationGetDescr(parent);
+ AttrNumber parent_attnum;
+
+ parent_attnum = get_attnum(parentId, colName);
+ if (tupDesc->attrs[parent_attnum - 1]->attnotnull)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("column \"%s\" is not null in parent",
+ colName),
+ errhint("Please drop not null in the parent instead")));
+ heap_close(parent, AccessShareLock);
+ }
+
+ /*
* Okay, actually perform the catalog change ... if needed
*/
if (((Form_pg_attribute) GETSTRUCT(tuple))->attnotnull)
@@ -5891,6 +5993,11 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
if (recursing)
ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
+ if (relid_is_partition(RelationGetRelid(rel)) && !recursing)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot drop column from a partition")));
+
/*
* get the number of the attribute
*/
@@ -5936,11 +6043,13 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
if (!is_expr)
ereport(ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
- errmsg("cannot drop column named in partition key")));
+ errmsg("cannot drop column named in%s partition key",
+ recursing ? " downstream" : "")));
else
ereport(ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
- errmsg("cannot drop column referenced in partition key expression")));
+ errmsg("cannot drop column referenced in%s partition key expression",
+ recursing ? " downstream" : "")));
}
ReleaseSysCache(tuple);
@@ -6374,8 +6483,9 @@ ATAddCheckConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
/*
* If adding a NO INHERIT constraint, no need to find our children.
+ * However, that applies only to regular inheritance parents.
*/
- if (constr->is_no_inherit)
+ if (constr->is_no_inherit && !relid_is_partitioned(RelationGetRelid(rel)))
return address;
/*
@@ -7936,9 +8046,12 @@ ATExecDropConstraint(Relation rel, const char *constrName,
/*
* Propagate to children as appropriate. Unlike most other ALTER
* routines, we have to do this one level of recursion at a time; we can't
- * use find_all_inheritors to do it in one pass.
+ * use find_all_inheritors to do it in one pass. Note that if the parent
+ * is a partitioned table, we propagate to children (partitions) despite
+ * is_no_inherit_constraint.
*/
- if (!is_no_inherit_constraint)
+ if (!is_no_inherit_constraint ||
+ relid_is_partitioned(RelationGetRelid(rel)))
children = find_inheritance_children(RelationGetRelid(rel), lockmode);
else
children = NIL;
@@ -8068,6 +8181,11 @@ ATPrepAlterColumnType(List **wqueue,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot alter column type of typed table")));
+ if (relid_is_partition(RelationGetRelid(rel)) && !recursing)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot alter column type of a partition")));
+
/* lookup the attribute so we can check inheritance status */
tuple = SearchSysCacheAttName(RelationGetRelid(rel), colName);
if (!HeapTupleIsValid(tuple))
@@ -8098,11 +8216,13 @@ ATPrepAlterColumnType(List **wqueue,
if (!is_expr)
ereport(ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
- errmsg("cannot alter column named in partition key")));
+ errmsg("cannot alter column named in%s partition key",
+ recursing ? " downstream" : "")));
else
ereport(ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
- errmsg("cannot alter column referenced in partition key expression")));
+ errmsg("cannot alter column referenced in%s partition key expression",
+ recursing ? " downstream" : "")));
}
/* Look up the target type */
@@ -10235,6 +10355,11 @@ ATPrepAddInherit(Relation child_rel)
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot change inheritance of typed table")));
+ if (relid_is_partition(RelationGetRelid(child_rel)))
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot change inheritance of a partition")));
+
if (child_rel->rd_rel->relkind == RELKIND_PARTITIONED_REL)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
@@ -10291,6 +10416,11 @@ ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode)
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot inherit from partitioned table \"%s\"", parent->relname)));
+ if (relid_is_partition(RelationGetRelid(parent_rel)))
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot inherit from a partition")));
+
/*
* Prevent circularity by seeing if proposed parent inherits from child.
* (In particular, this disallows making a rel inherit from itself.)
@@ -10677,6 +10807,11 @@ ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
ObjectAddress address;
Relation parent_rel;
+ if (relid_is_partition(RelationGetRelid(rel)))
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot change inheritance of a partition")));
+
/*
* AccessShareLock on the parent is probably enough, seeing that DROP
* TABLE doesn't lock parent tables at all. We need some lock since we'll
@@ -12566,6 +12701,12 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
*/
ATSimplePermissions(attachRel, ATT_TABLE);
+ if (relid_is_partition(RelationGetRelid(attachRel)))
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("\"%s\" is already a partition",
+ RelationGetRelationName(attachRel))));
+
if (attachRel->rd_rel->reloftype)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
@@ -12599,6 +12740,12 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
errmsg("cannot attach a temporary relation of another session as partition ")));
/* If parent has OIDs, attachRel must have OIDs */
+ if (attachRel->rd_rel->reloftype)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot attach a typed table as partition")));
+
+ /* If parent has OIDs then child must have OIDs */
if (rel->rd_rel->relhasoids && !attachRel->rd_rel->relhasoids)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
@@ -12641,6 +12788,30 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
/* OK to create inheritance. Rest of the checks performed there */
CreateInheritance(attachRel, rel);
+ /*
+ * Set up to have the rows in table to be checked for violation of the
+ * partition bound spec in phase 3 scan.
+ */
+ if (!cmd->skip_validate)
+ {
+ AlteredTableInfo *tab;
+ PartitionCheck *check;
+ Expr *my_expr,
+ *parent_expr = (Expr *) RelationGetPartitionCheckExpr(rel);
+
+ tab = ATGetQueueEntry(wqueue, attachRel);
+ check = (PartitionCheck *) palloc0(sizeof(PartitionCheck));
+ my_expr = get_check_expr_from_partbound(attachRel, rel, cmd->bound);
+ check->expr = parent_expr ? makeBoolExpr(AND_EXPR,
+ list_make2(parent_expr, my_expr), -1)
+ : my_expr;
+ tab->partitionCheck = check;
+ }
+
+ /* Store bound into the catalog */
+ StorePartitionBound(RelationGetRelid(attachRel), RelationGetRelid(rel),
+ cmd->bound);
+
ObjectAddressSet(address, RelationRelationId, RelationGetRelid(attachRel));
/* keep our lock until commit */
@@ -12665,6 +12836,15 @@ ATExecDetachPartition(Relation rel, RangeVar *name)
/* All inheritance related checks are performed within the function */
RemoveInheritance(partRel, rel);
+ /* Remove the pg_partition entry */
+ RemovePartitionEntryByRelId(RelationGetRelid(partRel));
+
+ /*
+ * Invalidate the relcache so that the partition is no longer included
+ * in our partition descriptor.
+ */
+ CacheInvalidateRelcache(rel);
+
ObjectAddressSet(address, RelationRelationId, RelationGetRelid(partRel));
/* keep our lock until commit */
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 29073c0..f7a23d9 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -42,6 +42,7 @@
#include "access/transam.h"
#include "access/xact.h"
#include "catalog/namespace.h"
+#include "catalog/partition.h"
#include "commands/matview.h"
#include "commands/trigger.h"
#include "executor/execdebug.h"
@@ -1251,6 +1252,8 @@ InitResultRelInfo(ResultRelInfo *resultRelInfo,
resultRelInfo->ri_ConstraintExprs = NULL;
resultRelInfo->ri_junkFilter = NULL;
resultRelInfo->ri_projectReturning = NULL;
+ resultRelInfo->ri_PartitionCheck = (Expr *)
+ RelationGetPartitionCheckExpr(resultRelationDesc);
}
/*
@@ -1684,6 +1687,49 @@ ExecRelCheck(ResultRelInfo *resultRelInfo,
return NULL;
}
+/*
+ * ExecPartitionCheck --- check that tuple meets the partition boundary
+ * specification.
+ *
+ * Note: This is called, *iff* resultRelInfo is the main target table.
+ */
+static bool
+ExecPartitionCheck(ResultRelInfo *resultRelInfo, TupleTableSlot *slot,
+ EState *estate)
+{
+ ExprContext *econtext;
+
+ /*
+ * If first time through, build expression state tree for the partition
+ * check expression. Keep it in the per-query memory context so they'll
+ * survive throughout the query.
+ */
+ if (resultRelInfo->ri_PartitionCheckExpr == NULL)
+ {
+ List *qual = make_ands_implicit(resultRelInfo->ri_PartitionCheck);
+ resultRelInfo->ri_PartitionCheckExpr = (List *)
+ ExecPrepareExpr((Expr *) qual, estate);
+ }
+
+ /*
+ * We will use the EState's per-tuple context for evaluating constraint
+ * expressions (creating it if it's not already there).
+ */
+ econtext = GetPerTupleExprContext(estate);
+
+ /* Arrange for econtext's scan tuple to be the tuple under test */
+ econtext->ecxt_scantuple = slot;
+
+ /*
+ * NOTE: SQL specifies that a NULL result from a constraint expression
+ * is not to be treated as a failure. Therefore, tell ExecQual to
+ * return TRUE for NULL.
+ *
+ * XXX - although, it's unlikely that NULL would result.
+ */
+ return ExecQual(resultRelInfo->ri_PartitionCheckExpr, econtext, true);
+}
+
void
ExecConstraints(ResultRelInfo *resultRelInfo,
TupleTableSlot *slot, EState *estate)
@@ -1695,9 +1741,9 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
Bitmapset *insertedCols;
Bitmapset *updatedCols;
- Assert(constr);
+ Assert(constr || resultRelInfo->ri_PartitionCheck);
- if (constr->has_not_null)
+ if (constr && constr->has_not_null)
{
int natts = tupdesc->natts;
int attrChk;
@@ -1728,7 +1774,7 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
}
}
- if (constr->num_check > 0)
+ if (constr && constr->num_check > 0)
{
const char *failed;
@@ -1752,6 +1798,29 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
errtableconstraint(rel, failed)));
}
}
+
+ if (resultRelInfo->ri_PartitionCheck)
+ {
+ if (!ExecPartitionCheck(resultRelInfo, slot, estate))
+ {
+ char *val_desc;
+
+ insertedCols = GetInsertedColumns(resultRelInfo, estate);
+ updatedCols = GetUpdatedColumns(resultRelInfo, estate);
+ modifiedCols = bms_union(insertedCols, updatedCols);
+ val_desc = ExecBuildSlotValueDescription(RelationGetRelid(rel),
+ slot,
+ tupdesc,
+ modifiedCols,
+ 64);
+ ereport(ERROR,
+ (errcode(ERRCODE_CHECK_VIOLATION),
+ errmsg("new row violates the partition boundary"
+ " specification of \"%s\"",
+ RelationGetRelationName(rel)),
+ val_desc ? errdetail("Failing row contains %s.", val_desc) : 0));
+ }
+ }
}
/*
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 7ad1140..6ecb6be 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -354,7 +354,7 @@ ExecInsert(ModifyTableState *mtstate,
/*
* Check the constraints of the tuple
*/
- if (resultRelationDesc->rd_att->constr)
+ if (resultRelationDesc->rd_att->constr || resultRelInfo->ri_PartitionCheck)
ExecConstraints(resultRelInfo, slot, estate);
if (onconflict != ONCONFLICT_NONE && resultRelInfo->ri_NumIndices > 0)
@@ -907,7 +907,7 @@ lreplace:;
/*
* Check the constraints of the tuple
*/
- if (resultRelationDesc->rd_att->constr)
+ if (resultRelationDesc->rd_att->constr || resultRelInfo->ri_PartitionCheck)
ExecConstraints(resultRelInfo, slot, estate);
/*
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 6f28047..1ea1439 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -2249,6 +2249,35 @@ _readExtensibleNode(void)
}
/*
+ * _readPartitionListSpec
+ */
+static PartitionListSpec *
+_readPartitionListSpec(void)
+{
+ READ_LOCALS(PartitionListSpec);
+
+ READ_NODE_FIELD(values);
+
+ READ_DONE();
+}
+
+/*
+ * _readPartitionRangeSpec
+ */
+static PartitionRangeSpec *
+_readPartitionRangeSpec(void)
+{
+ READ_LOCALS(PartitionRangeSpec);
+
+ READ_BOOL_FIELD(lowerinc);
+ READ_NODE_FIELD(lower);
+ READ_BOOL_FIELD(upperinc);
+ READ_NODE_FIELD(upper);
+
+ READ_DONE();
+}
+
+/*
* parseNodeString
*
* Given a character string representing a node tree, parseNodeString creates
@@ -2478,6 +2507,10 @@ parseNodeString(void)
return_value = _readAlternativeSubPlan();
else if (MATCH("EXTENSIBLENODE", 14))
return_value = _readExtensibleNode();
+ else if (MATCH("PARTITIONLISTVALUES", 19))
+ return_value = _readPartitionListSpec();
+ else if (MATCH("PARTITIONRANGE", 14))
+ return_value = _readPartitionRangeSpec();
else
{
elog(ERROR, "badly formatted node string \"%.32s\"...", token);
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index b0b606e..1179643 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -27,6 +27,7 @@
#include "catalog/catalog.h"
#include "catalog/dependency.h"
#include "catalog/heap.h"
+#include "catalog/partition.h"
#include "catalog/pg_am.h"
#include "catalog/pg_constraint.h"
#include "foreign/fdwapi.h"
@@ -1112,6 +1113,7 @@ get_relation_constraints(PlannerInfo *root,
Index varno = rel->relid;
Relation relation;
TupleConstr *constr;
+ Node *pexpr;
/*
* We assume the relation has already been safely locked.
@@ -1191,6 +1193,17 @@ get_relation_constraints(PlannerInfo *root,
}
}
+ /* Append partition check constraint, if any */
+ pexpr = RelationGetPartitionCheckExpr(relation);
+ if (pexpr)
+ {
+ /* Fix Vars to have the desired varno */
+ if (varno != 1)
+ ChangeVarNodes(pexpr, 1, varno, 0);
+
+ result = list_concat(result, make_ands_implicit((Expr *) pexpr));
+ }
+
heap_close(relation, NoLock);
return result;
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 7d2fedf..f55129a 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -25,6 +25,7 @@
#include "postgres.h"
#include "access/sysattr.h"
+#include "catalog/partition.h"
#include "catalog/pg_type.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
diff --git a/src/backend/parser/parse_clause.c b/src/backend/parser/parse_clause.c
index 307e383..5124dbf 100644
--- a/src/backend/parser/parse_clause.c
+++ b/src/backend/parser/parse_clause.c
@@ -202,8 +202,8 @@ setTargetTable(ParseState *pstate, RangeVar *relation,
pstate->p_target_rangetblentry = rte;
/*
- * Override specified inheritance option, if relation is a partitioned
- * table and it's a target of INSERT (ie, alsoSource == false).
+ * For UPDATE/DELETE, force inheritance recursion, if partitioned
+ * table.
*/
if (alsoSource)
rte->inh |= relid_is_partitioned(rte->relid);
diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c
index abcc1d6..b3870f6 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -1218,8 +1218,7 @@ addRangeTableEntry(ParseState *pstate,
* The initial default on access checks is always check-for-READ-access,
* which is the right thing for all except target tables.
*
- * Note: we override the specified inheritance option, if relation is a
- * partitioned table.
+ * Note: We force inheritance recursion, if partitioned table.
*/
rte->lateral = false;
rte->inh = inh || relid_is_partitioned(RelationGetRelid(rel));
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e69cbfc..a477883 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -32,6 +32,7 @@
#include "catalog/dependency.h"
#include "catalog/heap.h"
#include "catalog/index.h"
+#include "catalog/partition.h"
#include "catalog/namespace.h"
#include "catalog/partition.h"
#include "catalog/pg_am.h"
@@ -3105,6 +3106,11 @@ transformPartitionOf(CreateStmtContext *cxt, Node *bound)
if (parentRel->rd_rel->relhasoids)
cxt->hasoids = true;
+ /*
+ * Create ColumnDefs correponding to parent's attributes as "inherited"
+ * attributes. Also, set is_for_partition whose function is similar to
+ * is_from_type for MergeAttributes()'s purposes.
+ */
tupdesc = RelationGetDescr(parentRel);
for (i = 0; i < tupdesc->natts; i++)
{
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index a9e077f..dae706e 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -8224,6 +8224,84 @@ get_rule_expr(Node *node, deparse_context *context,
}
break;
+ case T_PartitionListSpec:
+ {
+ PartitionListSpec *list_spec = (PartitionListSpec *) node;
+ ListCell *cell;
+ char *sep;
+
+ appendStringInfoString(buf, "FOR VALUES");
+
+ appendStringInfoString(buf, " IN (");
+ sep = "";
+ foreach (cell, list_spec->values)
+ {
+ Const *val = lfirst(cell);
+
+ appendStringInfoString(buf, sep);
+ get_const_expr(val, context, -1);
+ sep = ", ";
+ }
+
+ appendStringInfoString(buf, ")");
+ }
+ break;
+
+ case T_PartitionRangeSpec:
+ {
+ PartitionRangeSpec *range_spec = (PartitionRangeSpec *) node;
+ ListCell *cell;
+ char *sep;
+
+ appendStringInfoString(buf, "FOR VALUES");
+
+ appendStringInfoString(buf, " START");
+ if (!range_spec->lower)
+ appendStringInfoString(buf, " UNBOUNDED");
+ else
+ {
+ appendStringInfoString(buf, " (");
+
+ sep = "";
+ foreach (cell, range_spec->lower)
+ {
+ Const *val = lfirst(cell);
+
+ appendStringInfoString(buf, sep);
+ get_const_expr(val, context, -1);
+ sep = ", ";
+ }
+ appendStringInfoString(buf, ")");
+
+ if (!range_spec->lowerinc)
+ appendStringInfoString(buf, " EXCLUSIVE");
+ }
+
+ appendStringInfoString(buf, " END");
+
+ if (!range_spec->upper)
+ appendStringInfoString(buf, " UNBOUNDED");
+ else
+ {
+ appendStringInfoString(buf, " (");
+
+ sep = "";
+ foreach (cell, range_spec->upper)
+ {
+ Const *val = lfirst(cell);
+
+ appendStringInfoString(buf, sep);
+ get_const_expr(val, context, -1);
+ sep = ", ";
+ }
+ appendStringInfoString(buf, ")");
+
+ if (range_spec->upperinc)
+ appendStringInfoString(buf, " INCLUSIVE");
+ }
+ }
+ break;
+
case T_List:
{
char *sep;
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index e48a1f4..abd2c12 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -71,6 +71,7 @@
#include "storage/smgr.h"
#include "utils/array.h"
#include "utils/builtins.h"
+#include "utils/datum.h"
#include "utils/fmgroids.h"
#include "utils/inval.h"
#include "utils/lsyscache.h"
@@ -279,6 +280,19 @@ static OpClassCacheEnt *LookupOpclassInfo(Oid operatorClassOid,
StrategyNumber numSupport);
static void RelationCacheInitFileRemoveInDir(const char *tblspcpath);
static void unlink_initfile(const char *initfilename);
+static void RelationBuildPartitionDesc(Relation relation);
+static int32 range_partition_cmp_upper(const void *a, const void *b,
+ void *arg);
+static PartitionListBound *copyPartitionListBound(PartitionListBound *src,
+ PartitionKey key);
+static PartitionRangeBound *copyPartitionRangeBound(PartitionRangeBound *src,
+ PartitionKey key);
+static bool equalPartitionDescs(PartitionKey key, PartitionDesc pdesc1,
+ PartitionDesc pdesc2);
+static bool equalListBounds(PartitionKey key, PartitionListBound *lb1,
+ PartitionListBound *lb2);
+static bool equalRangeBounds(PartitionKey key, PartitionRangeBound *rb1,
+ PartitionRangeBound *rb2);
/*
@@ -928,6 +942,87 @@ equalRSDesc(RowSecurityDesc *rsdesc1, RowSecurityDesc *rsdesc2)
}
/*
+ * equalPartitionDescs
+ * Compare two partition descriptors for logical equality
+ */
+static bool
+equalPartitionDescs(PartitionKey key, PartitionDesc pdesc1,
+ PartitionDesc pdesc2)
+{
+ int i;
+
+ if (key == NULL)
+ return false;
+
+ if (pdesc1 != NULL)
+ {
+ if (pdesc2 == NULL)
+ return false;
+ if (pdesc1->nparts != pdesc2->nparts)
+ return false;
+
+ for (i = 0; i < pdesc1->nparts; i++)
+ {
+ if (pdesc1->oids[i] != pdesc2->oids[i])
+ return false;
+
+ if (pdesc1->lists &&
+ !equalListBounds(key, pdesc1->lists[i], pdesc2->lists[i]))
+ return false;
+
+ if (pdesc1->rangelowers &&
+ (!equalRangeBounds(key, pdesc1->rangelowers[i],
+ pdesc2->rangelowers[i]) ||
+ !equalRangeBounds(key, pdesc1->rangeuppers[i],
+ pdesc2->rangeuppers[i])))
+ return false;
+ }
+ }
+ else if (pdesc2 != NULL)
+ return false;
+
+ return true;
+}
+
+/* Are two listbounds equal (contain same values)? */
+static bool
+equalListBounds(PartitionKey key, PartitionListBound *lb1,
+ PartitionListBound *lb2)
+{
+ int i,
+ j;
+
+ if (lb1->nvalues != lb2->nvalues)
+ return false;
+
+ if (lb1->contains_null != lb2->contains_null)
+ return false;
+
+ for (i = 0; i < lb1->nvalues; i++)
+ for (j = 0; j < lb2->nvalues; j++)
+ /* Non-zero result means not equal */
+ if (DatumGetInt32(FunctionCall2Coll(&key->partsupfunc[0],
+ key->tcinfo->typcoll[0],
+ lb1->values[i],
+ lb2->values[j])))
+ return false;
+
+ return true;
+}
+
+/*
+ * Are two rangebounds equal?
+ *
+ * It's clear in this case that both are either lower bounds or upper bounds.
+ */
+static bool
+equalRangeBounds(PartitionKey key, PartitionRangeBound *rb1,
+ PartitionRangeBound *rb2)
+{
+ return partition_range_cmp_bounds(key, rb1, rb2) == 0;
+}
+
+/*
* RelationBuildDesc
*
* Build a relation descriptor. The caller must hold at least
@@ -1053,9 +1148,16 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
/* if it's a partitioned table, initialize key info */
if (relation->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+ {
RelationBuildPartitionKey(relation);
+ RelationBuildPartitionDesc(relation);
+ }
else
+ {
relation->rd_partkey = NULL;
+ relation->rd_partdesc = NULL;
+ relation->rd_pdcxt = NULL;
+ }
/*
* if it's an index, initialize index-related information
@@ -1399,6 +1501,160 @@ IndexSupportInitialize(oidvector *indclass,
}
/*
+ * RelationBuildPartitionDesc
+ * Form relation's partition descriptor from information in pg_partition
+ * and store it in the relcache
+ *
+ * Not flushed from the cache unless changed.
+ */
+static void
+RelationBuildPartitionDesc(Relation relation)
+{
+ int i,
+ nparts;
+ PartitionKey key = relation->rd_partkey;
+ PartitionDesc result;
+ PartitionInfo **parts;
+ MemoryContext oldcxt;
+
+ /* Read partitions from the catalog */
+ parts = get_partition_info(relation, &nparts);
+
+ relation->rd_pdcxt = AllocSetContextCreate(CacheMemoryContext,
+ RelationGetRelationName(relation),
+ ALLOCSET_DEFAULT_MINSIZE,
+ ALLOCSET_DEFAULT_INITSIZE,
+ ALLOCSET_DEFAULT_MAXSIZE);
+
+ oldcxt = MemoryContextSwitchTo(relation->rd_pdcxt);
+ result = (PartitionDescData *) palloc0(sizeof(PartitionDescData));
+
+ result->nparts = nparts;
+ if (parts)
+ {
+ result->oids = (Oid *) palloc0(nparts * sizeof(Oid));
+
+ switch (key->strategy)
+ {
+ case PARTITION_STRAT_LIST:
+ result->lists = (PartitionListBound **) palloc0(nparts *
+ sizeof(PartitionListBound *));
+ break;
+
+ case PARTITION_STRAT_RANGE:
+ result->rangelowers = (PartitionRangeBound **) palloc0(nparts *
+ sizeof(PartitionRangeBound *));
+ result->rangeuppers = (PartitionRangeBound **) palloc0(nparts *
+ sizeof(PartitionRangeBound *));
+
+ /* Sort on range->upper */
+ qsort_arg(parts, nparts, sizeof(PartitionInfo *),
+ range_partition_cmp_upper, key);
+ break;
+ }
+
+ for (i = 0; i < nparts; i++)
+ {
+ result->oids[i] = parts[i]->oid;
+
+ if (parts[i]->list)
+ result->lists[i] = copyPartitionListBound(parts[i]->list, key);
+ else
+ {
+ result->rangelowers[i] = copyPartitionRangeBound(parts[i]->rangelower, key);
+ result->rangeuppers[i] = copyPartitionRangeBound(parts[i]->rangeupper, key);
+ }
+ }
+
+ /* Clean up */
+ free_partition_info(parts, nparts);
+ }
+
+ MemoryContextSwitchTo(oldcxt);
+ relation->rd_partdesc = result;
+}
+
+/*
+ * range_partition_cmp_upper
+ * Compare two range partition PartitionInfos using their rangeupper
+ * bounds
+ *
+ * Used as a callback to qsort_arg for sorting range partitions.
+ */
+static int32
+range_partition_cmp_upper(const void *a, const void *b, void *arg)
+{
+ int32 result;
+ int i;
+ PartitionKey key = (PartitionKey) arg;
+ PartitionRangeBound *b1 = (*(const PartitionInfo **) a)->rangeupper,
+ *b2 = (*(const PartitionInfo **) b)->rangeupper;
+
+ if (b1->infinite || b2->infinite)
+ return b1->infinite ? 1 : -1;
+
+ for (i = 0; i < key->partnatts; i++)
+ {
+ result = DatumGetInt32(FunctionCall2Coll(&key->partsupfunc[i],
+ key->tcinfo->typcoll[i],
+ b1->val[i], b2->val[i]));
+ if (!result)
+ continue;
+
+ break;
+ }
+
+ return result;
+}
+
+/*
+ * Helper routine to copy a list partition bound struct
+ */
+static PartitionListBound *
+copyPartitionListBound(PartitionListBound *src, PartitionKey key)
+{
+ int i;
+ PartitionListBound *result;
+
+ result = (PartitionListBound *) palloc0(sizeof(PartitionListBound));
+ result->nvalues = src->nvalues;
+ result->contains_null = src->contains_null;
+ result->values = (Datum *) palloc0(src->nvalues * sizeof(Datum));
+
+ for (i = 0; i < src->nvalues; i++)
+ result->values[i] = datumCopy(src->values[i],
+ key->tcinfo->typbyval[0],
+ key->tcinfo->typlen[0]);
+ return result;
+}
+
+/*
+ * Helper routine to copy a range partition bound struct
+ */
+static PartitionRangeBound *
+copyPartitionRangeBound(PartitionRangeBound *src, PartitionKey key)
+{
+ int i;
+ PartitionRangeBound *result;
+
+ result = (PartitionRangeBound *) palloc0(sizeof(PartitionRangeBound));
+ result->infinite = src->infinite;
+ result->inclusive = src->inclusive;
+ result->lower = src->lower;
+
+ if (src->val)
+ {
+ result->val = (Datum *) palloc0(key->partnatts * sizeof(Datum));
+ for (i = 0; i < key->partnatts; i++)
+ result->val[i] = datumCopy(src->val[i],
+ key->tcinfo->typbyval[i],
+ key->tcinfo->typlen[i]);
+ }
+
+ return result;
+}
+
+/*
* LookupOpclassInfo
*
* This routine maintains a per-opclass cache of the information needed
@@ -2055,6 +2311,10 @@ RelationDestroyRelation(Relation relation, bool remember_tupdesc)
if (relation->rd_rsdesc)
MemoryContextDelete(relation->rd_rsdesc->rscxt);
FreePartitionKey(relation->rd_partkey);
+ if (relation->rd_pdcxt)
+ MemoryContextDelete(relation->rd_pdcxt);
+ if (relation->rd_partcheck)
+ pfree(relation->rd_partcheck);
if (relation->rd_fdwroutine)
pfree(relation->rd_fdwroutine);
pfree(relation);
@@ -2218,6 +2478,7 @@ RelationClearRelation(Relation relation, bool rebuild)
bool keep_tupdesc;
bool keep_rules;
bool keep_policies;
+ bool keep_partdesc;
/* Build temporary entry, but don't link it into hashtable */
newrel = RelationBuildDesc(save_relid, false);
@@ -2248,6 +2509,9 @@ RelationClearRelation(Relation relation, bool rebuild)
keep_tupdesc = equalTupleDescs(relation->rd_att, newrel->rd_att);
keep_rules = equalRuleLocks(relation->rd_rules, newrel->rd_rules);
keep_policies = equalRSDesc(relation->rd_rsdesc, newrel->rd_rsdesc);
+ keep_partdesc = equalPartitionDescs(relation->rd_partkey,
+ relation->rd_partdesc,
+ newrel->rd_partdesc);
/*
* Perform swapping of the relcache entry contents. Within this
@@ -2303,6 +2567,10 @@ RelationClearRelation(Relation relation, bool rebuild)
/* pgstat_info must be preserved */
SWAPFIELD(struct PgStat_TableStatus *, pgstat_info);
+ /* preserve old partdesc if no logical change */
+ if (keep_partdesc)
+ SWAPFIELD(PartitionDesc, rd_partdesc);
+
#undef SWAPFIELD
/* And now we can throw away the temporary entry */
@@ -3529,6 +3797,20 @@ RelationCacheInitializePhase3(void)
restart = true;
}
+ /*
+ * Reload partition key and descriptor for a partitioned table.
+ */
+ if (relation->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+ {
+ RelationBuildPartitionKey(relation);
+ Assert(relation->rd_partkey != NULL);
+
+ RelationBuildPartitionDesc(relation);
+ Assert(relation->rd_partdesc != NULL);
+
+ restart = true;
+ }
+
/* Release hold on the relation */
RelationDecrementReferenceCount(relation);
@@ -4984,6 +5266,8 @@ load_relcache_init_file(bool shared)
rel->trigdesc = NULL;
rel->rd_rsdesc = NULL;
rel->rd_partkey = NULL;
+ rel->rd_partdesc = NULL;
+ rel->rd_partcheck = NULL;
rel->rd_indexprs = NIL;
rel->rd_indpred = NIL;
rel->rd_exclops = NULL;
diff --git a/src/backend/utils/cache/syscache.c b/src/backend/utils/cache/syscache.c
index a6563fe..4a5783a 100644
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -49,6 +49,7 @@
#include "catalog/pg_operator.h"
#include "catalog/pg_opfamily.h"
#include "catalog/pg_partitioned.h"
+#include "catalog/pg_partition.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_range.h"
#include "catalog/pg_rewrite.h"
@@ -569,6 +570,17 @@ static const struct cachedesc cacheinfo[] = {
},
8
},
+ {PartitionRelationId, /* PARTRELID */
+ PartitionRelidIndexId,
+ 1,
+ {
+ Anum_pg_partition_partrelid,
+ 0,
+ 0,
+ 0
+ },
+ 128
+ },
{PartitionedRelationId, /* PARTEDRELID */
PartitionedRelidIndexId,
1,
diff --git a/src/bin/pg_dump/common.c b/src/bin/pg_dump/common.c
index 1cbb987..c8e56bd 100644
--- a/src/bin/pg_dump/common.c
+++ b/src/bin/pg_dump/common.c
@@ -68,6 +68,8 @@ static int numextmembers;
static void flagInhTables(TableInfo *tbinfo, int numTables,
InhInfo *inhinfo, int numInherits);
+static void flagPartitions(TableInfo *tblinfo, int numTables,
+ PartInfo *partinfo, int numPartitions);
static void flagInhAttrs(DumpOptions *dopt, TableInfo *tblinfo, int numTables);
static DumpableObject **buildIndexArray(void *objArray, int numObjs,
Size objSize);
@@ -75,6 +77,8 @@ static int DOCatalogIdCompare(const void *p1, const void *p2);
static int ExtensionMemberIdCompare(const void *p1, const void *p2);
static void findParentsByOid(TableInfo *self,
InhInfo *inhinfo, int numInherits);
+static void findPartitionParentByOid(TableInfo *self, PartInfo *partinfo,
+ int numPartitions);
static int strInArray(const char *pattern, char **arr, int arr_size);
@@ -93,8 +97,10 @@ getSchemaData(Archive *fout, int *numTablesPtr)
NamespaceInfo *nspinfo;
ExtensionInfo *extinfo;
InhInfo *inhinfo;
+ PartInfo *partinfo;
int numAggregates;
int numInherits;
+ int numPartitions;
int numRules;
int numProcLangs;
int numCasts;
@@ -232,6 +238,10 @@ getSchemaData(Archive *fout, int *numTablesPtr)
inhinfo = getInherits(fout, &numInherits);
if (g_verbose)
+ write_msg(NULL, "reading partition information\n");
+ partinfo = getPartitions(fout, &numPartitions);
+
+ if (g_verbose)
write_msg(NULL, "reading event triggers\n");
getEventTriggers(fout, &numEventTriggers);
@@ -245,6 +255,11 @@ getSchemaData(Archive *fout, int *numTablesPtr)
write_msg(NULL, "finding inheritance relationships\n");
flagInhTables(tblinfo, numTables, inhinfo, numInherits);
+ /* Link tables to partition parents, mark parents as interesting */
+ if (g_verbose)
+ write_msg(NULL, "finding partition relationships\n");
+ flagPartitions(tblinfo, numTables, partinfo, numPartitions);
+
if (g_verbose)
write_msg(NULL, "reading column info for interesting tables\n");
getTableAttrs(fout, tblinfo, numTables);
@@ -319,6 +334,43 @@ flagInhTables(TableInfo *tblinfo, int numTables,
}
}
+/* flagPartitions -
+ * Fill in parent link fields of every target table that is partition,
+ * and mark parents of partitions as interesting
+ *
+ * modifies tblinfo
+ */
+static void
+flagPartitions(TableInfo *tblinfo, int numTables,
+ PartInfo *partinfo, int numPartitions)
+{
+ int i;
+
+ for (i = 0; i < numTables; i++)
+ {
+ /* Some kinds are never partitions */
+ if (tblinfo[i].relkind == RELKIND_SEQUENCE ||
+ tblinfo[i].relkind == RELKIND_VIEW ||
+ tblinfo[i].relkind == RELKIND_MATVIEW)
+ continue;
+
+ /* Don't bother computing anything for non-target tables, either */
+ if (!tblinfo[i].dobj.dump)
+ continue;
+
+ /* Find the parent TableInfo and save */
+ findPartitionParentByOid(&tblinfo[i], partinfo, numPartitions);
+
+ /* Mark the parent as interesting for getTableAttrs */
+ if (tblinfo[i].partitionOf)
+ {
+ tblinfo[i].partitionOf->interesting = true;
+ addObjectDependency(&tblinfo[i].dobj,
+ tblinfo[i].partitionOf->dobj.dumpId);
+ }
+ }
+}
+
/* flagInhAttrs -
* for each dumpable table in tblinfo, flag its inherited attributes
*
@@ -920,6 +972,40 @@ findParentsByOid(TableInfo *self,
}
/*
+ * findPartitionParentByOid
+ * find a partition's parent in tblinfo[]
+ */
+static void
+findPartitionParentByOid(TableInfo *self, PartInfo *partinfo,
+ int numPartitions)
+{
+ Oid oid = self->dobj.catId.oid;
+ int i;
+
+ for (i = 0; i < numPartitions; i++)
+ {
+ if (partinfo[i].partrelid == oid)
+ {
+ TableInfo *parent;
+
+ parent = findTableByOid(partinfo[i].partparent);
+ if (parent == NULL)
+ {
+ write_msg(NULL, "failed sanity check, parent OID %u of table \"%s\" (OID %u) not found\n",
+ partinfo[i].partparent,
+ self->dobj.name,
+ oid);
+ exit_nicely(1);
+ }
+ self->partitionOf = parent;
+
+ /* While we're at it, also save the partdef */
+ self->partitiondef = partinfo[i].partdef;
+ }
+ }
+}
+
+/*
* parseOidArray
* parse a string of numbers delimited by spaces into a character array
*
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index b3da2f7..97d9517 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -6085,6 +6085,63 @@ getInherits(Archive *fout, int *numInherits)
}
/*
+ * getPartitions
+ * read all the partition inheritance and partition bound information
+ * from the system catalogs return them in the PartInfo* structure
+ *
+ * numPartitions is set to the number of pairs read in
+ */
+PartInfo *
+getPartitions(Archive *fout, int *numPartitions)
+{
+ PGresult *res;
+ int ntups;
+ int i;
+ PQExpBuffer query = createPQExpBuffer();
+ PartInfo *partinfo;
+
+ int i_partrelid;
+ int i_partparent;
+ int i_partbound;
+
+ /* Make sure we are in proper schema */
+ selectSourceSchema(fout, "pg_catalog");
+
+ /* find all the inheritance information */
+
+ appendPQExpBufferStr(query,
+ "SELECT partrelid, inhparent AS partparent, "
+ " pg_get_expr(partbound, partrelid) AS partbound "
+ "FROM pg_partition, pg_inherits "
+ "WHERE partrelid = inhrelid");
+
+ res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
+
+ ntups = PQntuples(res);
+
+ *numPartitions = ntups;
+
+ partinfo = (PartInfo *) pg_malloc(ntups * sizeof(PartInfo));
+
+ i_partrelid = PQfnumber(res, "partrelid");
+ i_partparent = PQfnumber(res, "partparent");
+ i_partbound = PQfnumber(res, "partbound");
+
+ for (i = 0; i < ntups; i++)
+ {
+ partinfo[i].partrelid = atooid(PQgetvalue(res, i, i_partrelid));
+ partinfo[i].partparent = atooid(PQgetvalue(res, i, i_partparent));
+ partinfo[i].partdef = pg_strdup(PQgetvalue(res, i, i_partbound));
+ }
+
+ PQclear(res);
+
+ destroyPQExpBuffer(query);
+
+ return partinfo;
+}
+
+/*
* getIndexes
* get information about every index on a dumpable table
*
@@ -15278,6 +15335,17 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
if (tbinfo->reloftype && !dopt->binary_upgrade)
appendPQExpBuffer(q, " OF %s", tbinfo->reloftype);
+ if (tbinfo->partitionOf && !dopt->binary_upgrade)
+ {
+ TableInfo *parentRel = tbinfo->partitionOf;
+
+ appendPQExpBuffer(q, " PARTITION OF ");
+ if (parentRel->dobj.namespace != tbinfo->dobj.namespace)
+ appendPQExpBuffer(q, "%s.",
+ fmtId(parentRel->dobj.namespace->dobj.name));
+ appendPQExpBufferStr(q, fmtId(parentRel->dobj.name));
+ }
+
if (tbinfo->relkind != RELKIND_MATVIEW)
{
/* Dump the attributes */
@@ -15306,8 +15374,11 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
(!tbinfo->inhNotNull[j] ||
dopt->binary_upgrade));
- /* Skip column if fully defined by reloftype */
- if (tbinfo->reloftype &&
+ /*
+ * Skip column if fully defined by reloftype or the
+ * partition parent.
+ */
+ if ((tbinfo->reloftype || tbinfo->partitionOf) &&
!has_default && !has_notnull && !dopt->binary_upgrade)
continue;
@@ -15336,7 +15407,8 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
}
/* Attribute type */
- if (tbinfo->reloftype && !dopt->binary_upgrade)
+ if ((tbinfo->reloftype || tbinfo->partitionOf) &&
+ !dopt->binary_upgrade)
{
appendPQExpBufferStr(q, " WITH OPTIONS");
}
@@ -15401,15 +15473,22 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
if (actual_atts)
appendPQExpBufferStr(q, "\n)");
- else if (!(tbinfo->reloftype && !dopt->binary_upgrade))
+ else if (!((tbinfo->reloftype || tbinfo->partitionOf) &&
+ !dopt->binary_upgrade))
{
/*
* We must have a parenthesized attribute list, even though
- * empty, when not using the OF TYPE syntax.
+ * empty, when not using the OF TYPE or PARTITION OF syntax.
*/
appendPQExpBufferStr(q, " (\n)");
}
+ if (tbinfo->partitiondef && !dopt->binary_upgrade)
+ {
+ appendPQExpBufferStr(q, "\n");
+ appendPQExpBufferStr(q, tbinfo->partitiondef);
+ }
+
if (numParents > 0 && !dopt->binary_upgrade)
{
appendPQExpBufferStr(q, "\nINHERITS (");
@@ -15579,6 +15658,15 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
tbinfo->reloftype);
}
+ if (tbinfo->partitionOf)
+ {
+ appendPQExpBufferStr(q, "\n-- For binary upgrade, set up partitions this way.\n");
+ appendPQExpBuffer(q, "ALTER TABLE ONLY %s ATTACH PARTITION %s %s;\n",
+ fmtId(tbinfo->partitionOf->dobj.name),
+ tbinfo->dobj.name,
+ tbinfo->partitiondef);
+ }
+
appendPQExpBufferStr(q, "\n-- For binary upgrade, set heap's relfrozenxid and relminmxid\n");
appendPQExpBuffer(q, "UPDATE pg_catalog.pg_class\n"
"SET relfrozenxid = '%u', relminmxid = '%u'\n"
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 0292859..760067a 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -320,6 +320,8 @@ typedef struct _tableInfo
struct _tableDataInfo *dataObj; /* TableDataInfo, if dumping its data */
int numTriggers; /* number of triggers for table */
struct _triggerInfo *triggers; /* array of TriggerInfo structs */
+ struct _tableInfo *partitionOf; /* TableInfo for the partition parent */
+ char *partitiondef; /* partition key definition */
} TableInfo;
typedef struct _attrDefInfo
@@ -460,6 +462,15 @@ typedef struct _inhInfo
Oid inhparent; /* OID of its parent */
} InhInfo;
+/* PartInfo isn't a DumpableObject, just temporary state */
+typedef struct _partInfo
+{
+ Oid partrelid; /* OID of a partition */
+ Oid partparent; /* OID of its parent */
+ char *partdef; /* partition bound definition */
+} PartInfo;
+
+
typedef struct _prsInfo
{
DumpableObject dobj;
@@ -626,6 +637,7 @@ extern ConvInfo *getConversions(Archive *fout, int *numConversions);
extern TableInfo *getTables(Archive *fout, int *numTables);
extern void getOwnedSeqs(Archive *fout, TableInfo tblinfo[], int numTables);
extern InhInfo *getInherits(Archive *fout, int *numInherits);
+extern PartInfo *getPartitions(Archive *fout, int *numPartitions);
extern void getIndexes(Archive *fout, TableInfo tblinfo[], int numTables);
extern void getConstraints(Archive *fout, TableInfo tblinfo[], int numTables);
extern RuleInfo *getRules(Archive *fout, int *numRules);
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 78a1fdb..6bbfc9a 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -1694,6 +1694,34 @@ describeOneTableDetails(const char *schemaname,
}
/* Make footers */
+ if (pset.sversion >= 90600)
+ {
+ /* Get the partition information */
+ PGresult *result;
+ char *parent_name;
+ char *partdef;
+
+ printfPQExpBuffer(&buf,
+ "SELECT inhparent::pg_catalog.regclass, pg_get_expr(partbound, partrelid)"
+ " FROM pg_catalog.pg_partition p"
+ " JOIN pg_catalog.pg_inherits i"
+ " ON p.partrelid = i.inhrelid"
+ " WHERE p.partrelid = '%s';", oid);
+ result = PSQLexec(buf.data);
+ if (!result)
+ goto error_return;
+
+ if (PQntuples(result) > 0)
+ {
+ parent_name = PQgetvalue(result, 0, 0);
+ partdef = PQgetvalue(result, 0, 1);
+ printfPQExpBuffer(&tmpbuf, _("Partition Of: %s %s"), parent_name,
+ partdef);
+ printTableAddFooter(&cont, tmpbuf.data);
+ PQclear(result);
+ }
+ }
+
if (pset.sversion >= 90600 && tableinfo.relkind == 'P')
{
/* Get the partition key information */
@@ -2455,8 +2483,12 @@ describeOneTableDetails(const char *schemaname,
PQclear(result);
}
- /* print inherited tables */
- printfPQExpBuffer(&buf, "SELECT c.oid::pg_catalog.regclass FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i WHERE c.oid=i.inhparent AND i.inhrelid = '%s' ORDER BY inhseqno;", oid);
+ /* print inherited tables (exclude, if parent is a partitioned table) */
+ printfPQExpBuffer(&buf,
+ "SELECT c.oid::pg_catalog.regclass"
+ " FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i"
+ " WHERE c.oid=i.inhparent AND i.inhrelid = '%s'"
+ " AND c.relkind != 'P' ORDER BY inhseqno;", oid);
result = PSQLexec(buf.data);
if (!result)
@@ -2485,9 +2517,15 @@ describeOneTableDetails(const char *schemaname,
PQclear(result);
}
- /* print child tables */
+ /* print child tables (exclude, if parent is a partitioned table) */
if (pset.sversion >= 80300)
- printfPQExpBuffer(&buf, "SELECT c.oid::pg_catalog.regclass FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i WHERE c.oid=i.inhrelid AND i.inhparent = '%s' ORDER BY c.oid::pg_catalog.regclass::pg_catalog.text;", oid);
+ printfPQExpBuffer(&buf,
+ "SELECT c.oid::pg_catalog.regclass"
+ " FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i"
+ " WHERE c.oid=i.inhrelid AND"
+ " i.inhparent = '%s' AND"
+ " EXISTS (SELECT 1 FROM pg_class c WHERE c.oid = '%s' AND c.relkind != 'P')"
+ " ORDER BY c.oid::pg_catalog.regclass::pg_catalog.text;", oid, oid);
else
printfPQExpBuffer(&buf, "SELECT c.oid::pg_catalog.regclass FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i WHERE c.oid=i.inhrelid AND i.inhparent = '%s' ORDER BY c.relname;", oid);
diff --git a/src/include/catalog/indexing.h b/src/include/catalog/indexing.h
index cadb741..73786c0 100644
--- a/src/include/catalog/indexing.h
+++ b/src/include/catalog/indexing.h
@@ -322,6 +322,9 @@ DECLARE_UNIQUE_INDEX(pg_replication_origin_roname_index, 6002, on pg_replication
DECLARE_UNIQUE_INDEX(pg_partitioned_partedrelid_index, 3351, on pg_partitioned using btree(partedrelid oid_ops));
#define PartitionedRelidIndexId 3351
+DECLARE_UNIQUE_INDEX(pg_partition_partrelid_index, 3354, on pg_partition using btree(partrelid oid_ops));
+#define PartitionRelidIndexId 3354
+
/* last step of initialization script: build the indexes declared above */
BUILD_INDICES
diff --git a/src/include/catalog/partition.h b/src/include/catalog/partition.h
index 31dbe17..7d4bf2f 100644
--- a/src/include/catalog/partition.h
+++ b/src/include/catalog/partition.h
@@ -42,6 +42,76 @@ typedef struct PartitionKeyData
typedef struct PartitionKeyData *PartitionKey;
+/* Internal representation of a list partition bound */
+typedef struct PartitionListBound
+{
+ bool contains_null;
+ int nvalues; /* number of values in the following array */
+ Datum *values; /* values contained in the list */
+} PartitionListBound;
+
+/* Internal representation of a range partition bound */
+typedef struct
+{
+ Datum *val; /* composite bound value, if any */
+ bool infinite; /* bound is +/- infinity */
+ bool inclusive; /* bound is inclusive (vs exclusive) */
+ bool lower; /* this is the lower (vs upper) bound */
+} PartitionRangeBound;
+
+/*
+ * Information about a single partition
+ */
+typedef struct PartitionInfo
+{
+ Oid oid; /* partition OID */
+ PartitionListBound *list; /* list partition bound */
+ PartitionRangeBound *rangelower; /* range partition lower bound */
+ PartitionRangeBound *rangeupper; /* range partition upper bound */
+} PartitionInfo;
+
+/*
+ * Information about partitions of a partitioned table.
+ */
+typedef struct PartitionDescData
+{
+ int nparts; /* number of partitions */
+ Oid *oids; /* OIDs of the partitions */
+ PartitionListBound **lists; /* list bounds */
+ PartitionRangeBound **rangelowers; /* range lower bounds */
+ PartitionRangeBound **rangeuppers; /* range upper bounds */
+} PartitionDescData;
+
+typedef struct PartitionDescData *PartitionDesc;
+
+/*
+ * Partition tree node
+ *
+ * pkinfo PartitionKey executor state
+ * pdesc info about immediate partitions (see
+ PartitionDescData)
+ * index index within 0..(parent_pdesc->nparts - 1)
+ * num_leaf_partitions number of leaf partitions in the partition
+ * tree rooted at this node
+ * offset index (0-based) of the first leaf partition
+ * in the partition tree rooted at this node
+ * downlink link to the leftmost child node
+ * next link to the right sibling node on a given level
+ */
+typedef struct PartitionDescNodeData
+{
+ PartitionDesc pdesc;
+ Oid relid;
+ int index;
+ int offset;
+ int num_leaf_partitions;
+
+ struct PartitionDescNodeData *downlink;
+ struct PartitionDescNodeData *next;
+} PartitionDescNodeData;
+
+typedef struct PartitionDescNodeData *PartitionDescNode;
+
extern void StorePartitionKey(Relation rel,
char strategy,
int16 partnatts,
@@ -53,4 +123,19 @@ extern void RelationBuildPartitionKey(Relation relation);
extern void FreePartitionKey(PartitionKey key);
extern bool relid_is_partitioned(Oid relid);
+extern void StorePartitionBound(Oid relid, Oid parentId, Node *bound);
+extern void RemovePartitionEntryByRelId(Oid relid);
+extern bool relid_is_partition(Oid relid);
+extern Oid get_partition_parent(Oid relid);
+extern void RelationDropPartitions(Oid relid);
+extern PartitionInfo **get_partition_info(Relation rel, int *nparts);
+extern void free_partition_info(PartitionInfo **p, int num);
+extern int32 partition_range_cmp_bounds(PartitionKey key,
+ PartitionRangeBound *b1,
+ PartitionRangeBound *b2);
+extern Node *RelationGetPartitionCheckExpr(Relation rel);
+extern Expr *get_check_expr_from_partbound(Relation rel, Relation parent,
+ Node *bound);
+extern List *get_leaf_partition_oids(PartitionDescNode pdnode);
+extern PartitionDescNode RelationGetPartitionDescNode(Relation rel);
#endif /* PARTITION_H */
diff --git a/src/include/catalog/pg_partition.h b/src/include/catalog/pg_partition.h
new file mode 100644
index 0000000..d31a6b7
--- /dev/null
+++ b/src/include/catalog/pg_partition.h
@@ -0,0 +1,54 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_partition.h
+ * definition of the system "partition" relation (pg_partition)
+ * along with the relation's initial contents.
+ *
+ *
+ * Portions Copyright (c) 1996-2015, PostgreSQL Global Development Group
+ *
+ * $PostgreSQL: pgsql/src/include/catalog/pg_partition.h $
+ *
+ * NOTES
+ * the genbki.sh script reads this file and generates .bki
+ * information from the DATA() statements.
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_PARTITION_H
+#define PG_PARTITION_H
+
+#include "catalog/genbki.h"
+
+/* ----------------
+ * pg_partitioned_rel definition. cpp turns this into
+ * typedef struct FormData_pg_partitioned_rel
+ * ----------------
+ */
+#define PartitionRelationId 3353
+
+CATALOG(pg_partition,3353) BKI_WITHOUT_OIDS
+{
+ Oid partrelid; /* partition relation OID */
+
+#ifdef CATALOG_VARLEN /* variable-length fields start here */
+ pg_node_tree partbound; /* node representation of partition bound */
+#endif
+} FormData_pg_partition;
+
+/* ----------------
+ * Form_pg_partition corresponds to a pointer to a tuple with
+ * the format of pg_partition relation.
+ * ----------------
+ */
+typedef FormData_pg_partition *Form_pg_partition;
+
+/* ----------------
+ * compiler constants for pg_partition
+ * ----------------
+ */
+#define Natts_pg_partition 2
+#define Anum_pg_partition_partrelid 1
+#define Anum_pg_partition_partbound 2
+
+#endif /* PG_PARTITION_H */
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index ee4e189..74288e2 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -319,6 +319,8 @@ typedef struct JunkFilter
* projectReturning for computing a RETURNING list
* onConflictSetProj for computing ON CONFLICT DO UPDATE SET
* onConflictSetWhere list of ON CONFLICT DO UPDATE exprs (qual)
+ * PartitionCheck partition check expression
+ * PartitionCheckExpr partition check expression state
* ----------------
*/
typedef struct ResultRelInfo
@@ -343,6 +345,8 @@ typedef struct ResultRelInfo
ProjectionInfo *ri_projectReturning;
ProjectionInfo *ri_onConflictSetProj;
List *ri_onConflictSetWhere;
+ Expr *ri_PartitionCheck;
+ List *ri_PartitionCheckExpr;
} ResultRelInfo;
/* ----------------
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 2e43ebf..20c5965 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -91,6 +91,10 @@ typedef struct RelationData
struct RowSecurityDesc *rd_rsdesc; /* row security policies, or NULL */
struct PartitionKeyData *rd_partkey; /* partition key, or NULL */
+ MemoryContext rd_pdcxt; /* private context for partdesc */
+ struct PartitionDescData *rd_partdesc; /* partitions, or NULL */
+ Node *rd_partcheck; /* partition CHECK constraint expression
+ * tree */
/* data managed by RelationGetIndexList: */
List *rd_indexlist; /* list of OIDs of indexes on relation */
@@ -510,6 +514,12 @@ typedef struct ViewOptions
*/
#define RelationGetPartitionKey(relation) ((relation)->rd_partkey)
+/*
+ * RelationGetPartitionDesc
+ * Returns partition descriptor for a relation.
+ */
+#define RelationGetPartitionDesc(relation) ((relation)->rd_partdesc)
+
/* routines in utils/cache/relcache.c */
extern void RelationIncrementReferenceCount(Relation rel);
extern void RelationDecrementReferenceCount(Relation rel);
diff --git a/src/include/utils/syscache.h b/src/include/utils/syscache.h
index e727842..4e493e0 100644
--- a/src/include/utils/syscache.h
+++ b/src/include/utils/syscache.h
@@ -72,6 +72,7 @@ enum SysCacheIdentifier
OPEROID,
OPFAMILYAMNAMENSP,
OPFAMILYOID,
+ PARTRELID,
PARTEDRELID,
PROCNAMEARGSNSP,
PROCOID,
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index b1e473d..57bde9d 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -118,6 +118,7 @@ pg_namespace|t
pg_opclass|t
pg_operator|t
pg_opfamily|t
+pg_partition|t
pg_partitioned|t
pg_pltemplate|t
pg_policy|t
--
1.7.1
[text/x-diff] 0006-Introduce-tuple-routing-for-partitioned-tables-v5.patch (29.3K, ../../5742D5F5.3020101@lab.ntt.co.jp/7-0006-Introduce-tuple-routing-for-partitioned-tables-v5.patch)
download | inline diff:
From e9850f583ad1088d0c36f68b48ed9129ac3fde58 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Fri, 18 Mar 2016 19:48:49 +0900
Subject: [PATCH 6/7] Introduce tuple routing for partitioned tables.
A tuple inserted into a partitioned table using insert or copy is now
routed to the appropriate leaf partition, recursively.
State required for the same is built in advance by BeginCopy() and
ExecInitModifyTable(). It consists of N ResultRelInfos and pointers
to TupleConversionMap structs, where N is the number of leaf partitions
in the multi-level partition tree. One needs only to get a pointer to the
root PartitionDescNode node for the tree. A tuple conversion map is only
allocated if the tuple descriptor of a leaf partition differs from that
of the root table. Partition check expression is not initialized for
leaf partition result relations, as they are redundant in this case.
Whether or not to build one is now determined by new parameter
load_partition_check of InitResultRelInfo().
CopyFrom() or ExecInsert() passes the root node (set up initially, as
just mentioned) along with the tuple slot to get_partition_for_tuple()
which traverses the tree to determine the appropriate leaf partition for
the tuple. It returns the index (0..N) of the leaf partition. Caller
then switches the result relation (ResultRelInfo) appropriately and
performs the tuple conversion, if required, before following up with
the row triggers, constraints, actual heap and index insertion.
* Following requires further consideration:
Both CopyFrom() and ExecInsert() switch to the partition before invoking
BR triggers and back after AR trigger are processed. That ignores parent's
triggers completely. Note that those triggers are not required to be present
in each partition. OTOH, constraints are fine (both check and not null),
since parent's constraints are present in partitions.
---
src/backend/catalog/partition.c | 324 ++++++++++++++++++++++++++++++++
src/backend/commands/copy.c | 113 +++++++++++-
src/backend/commands/tablecmds.c | 1 +
src/backend/executor/execMain.c | 44 +++++-
src/backend/executor/nodeModifyTable.c | 96 ++++++++++
src/backend/parser/analyze.c | 8 +
src/include/catalog/partition.h | 24 +++
src/include/executor/executor.h | 6 +
src/include/nodes/execnodes.h | 10 +
9 files changed, 621 insertions(+), 5 deletions(-)
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index eb27937..f669932 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -96,6 +96,21 @@ static Node *generate_partition_check_expr(Relation rel);
static PartitionDescNode GetPartitionDescNodeRecurse(Relation rel, int offset);
static int get_leaf_partition_count(PartitionDescNode pdnode);
+static PartitionKeyExecInfo *BuildPartitionKeyExecInfo(Relation rel);
+static void FormPartitionKeyDatum(PartitionKeyExecInfo *pkinfo,
+ TupleTableSlot *slot,
+ EState *estate,
+ Datum *values,
+ bool *isnull);
+static int list_partition_for_tuple(PartitionKey key, PartitionDesc pdesc,
+ Datum value, bool isnull);
+static int range_partition_for_tuple(PartitionKey key, PartitionDesc pdesc,
+ Datum *values);
+static int range_partition_bsearch(PartitionKey key, PartitionDesc pdesc,
+ Datum *values);
+static bool rightof(PartitionKey key, Datum *values, PartitionRangeBound *bound);
+static bool leftof(PartitionKey key, Datum *values, PartitionRangeBound *bound);
+
/*
* StorePartitionKey
* Store the partition keys of rel into pg_partitioned catalog
@@ -1618,6 +1633,7 @@ GetPartitionDescNodeRecurse(Relation rel, int offset)
/* First build our own node */
root = (PartitionDescNode) palloc0(sizeof(PartitionDescNodeData));
+ root->pkinfo = BuildPartitionKeyExecInfo(rel);
root->pdesc = pdesc;
root->relid = RelationGetRelid(rel);
root->offset = offset;
@@ -1735,3 +1751,311 @@ get_leaf_partition_oids(PartitionDescNode pdnode)
return result;
}
+
+/* ----------------
+ * BuildPartitionKeyExecInfo
+ * Construct a list of PartitionKeyExecInfo records for an open
+ * relation
+ *
+ * PartitionKeyExecInfo stores the information about the partition key
+ * that's needed when inserting tuples into a partitioned table; especially,
+ * partition key expression state if there are any expression columns in
+ * the partition key. Normally we build a PartitionKeyExecInfo for a
+ * partitioned table just once per command, and then use it for (potentially)
+ * many tuples.
+ * ----------------
+ */
+static PartitionKeyExecInfo *
+BuildPartitionKeyExecInfo(Relation rel)
+{
+ PartitionKeyExecInfo *pkinfo;
+
+ pkinfo = (PartitionKeyExecInfo *) palloc0(sizeof(PartitionKeyExecInfo));
+ pkinfo->pi_Key = CopyPartitionKey(rel->rd_partkey);
+ pkinfo->pi_ExpressionState = NIL;
+
+ return pkinfo;
+}
+
+/*
+ * get_partition_for_tuple
+ * Recursively finds the "leaf" partition for tuple (slot)
+ *
+ * Returns -1 if no partition is found and sets *failed_at to the OID of
+ * the partitioned table whose partition was not found.
+ */
+int
+get_partition_for_tuple(PartitionDescNode pdnode,
+ TupleTableSlot *slot,
+ EState *estate,
+ Oid *failed_at)
+{
+ PartitionKeyExecInfo *pkinfo = pdnode->pkinfo;
+ PartitionDescNode node;
+ Datum values[PARTITION_MAX_KEYS];
+ bool isnull[PARTITION_MAX_KEYS];
+ int i;
+ int index;
+
+ /* Guard against stack overflow due to overly deep partition tree */
+ check_stack_depth();
+
+ if (pdnode->pdesc->nparts == 0)
+ {
+ *failed_at = pdnode->relid;
+ return -1;
+ }
+
+ /* Extract partition key from tuple */
+ Assert(GetPerTupleExprContext(estate)->ecxt_scantuple == slot);
+ FormPartitionKeyDatum(pkinfo, slot, estate, values, isnull);
+
+ /* Disallow nulls, if range partition key */
+ for (i = 0; i < pkinfo->pi_Key->partnatts; i++)
+ if (isnull[i] && pkinfo->pi_Key->strategy == PARTITION_STRAT_RANGE)
+ ereport(ERROR,
+ (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+ errmsg("range partition key contains null")));
+
+ switch (pkinfo->pi_Key->strategy)
+ {
+ case PARTITION_STRAT_LIST:
+ index = list_partition_for_tuple(pkinfo->pi_Key, pdnode->pdesc,
+ values[0], isnull[0]);
+ break;
+
+ case PARTITION_STRAT_RANGE:
+ index = range_partition_for_tuple(pkinfo->pi_Key, pdnode->pdesc,
+ values);
+ break;
+ }
+
+ /* Don't recurse, if no partition exists for the key at this level... */
+ if (index < 0)
+ {
+ *failed_at = pdnode->relid;
+ return index;
+ }
+
+ /* ... or, if the index'th partition is a leaf partition. */
+ if (!relid_is_partitioned(pdnode->pdesc->oids[index]))
+ {
+ PartitionDescNode prev;
+
+ /* Patiently determine own leaf partition index */
+ prev = node = pdnode->downlink;
+ if (node && node->index < index)
+ {
+ while (node)
+ {
+ if (node->index > index)
+ {
+ node = prev;
+ break;
+ }
+
+ prev = node;
+ node = node->next;
+ }
+
+ if (!node)
+ node = prev;
+
+ /*
+ * Account for siblings on left that are partition trees and those
+ * that are leaf partitions themsleves.
+ */
+ return node->offset + node->num_leaf_partitions +
+ (index - node->index - 1);
+ }
+ else
+ /* Only leaf partitions in pdnode->offset..index */
+ return pdnode->offset + index;
+
+ }
+
+ /* Recurse, by locating the index'th partition's node */
+ node = pdnode->downlink;
+ while (node->next != NULL && node->index != index)
+ node = node->next;
+ Assert (node != NULL);
+
+ return get_partition_for_tuple(node, slot, estate, failed_at);
+}
+
+/*
+ * FormPartitionKeyDatum
+ * Construct values[] and isnull[] arrays for partition key columns
+ */
+static void
+FormPartitionKeyDatum(PartitionKeyExecInfo *pkinfo,
+ TupleTableSlot *slot,
+ EState *estate,
+ Datum *values,
+ bool *isnull)
+{
+ ListCell *partexpr_item;
+ int i;
+
+ if (pkinfo->pi_Key->partexprs != NIL && pkinfo->pi_ExpressionState == NIL)
+ {
+ /* First time through, set up expression evaluation state */
+ pkinfo->pi_ExpressionState = (List *)
+ ExecPrepareExpr((Expr *) pkinfo->pi_Key->partexprs,
+ estate);
+ /* Check caller has set up context correctly */
+ Assert(GetPerTupleExprContext(estate)->ecxt_scantuple == slot);
+ }
+
+ partexpr_item = list_head(pkinfo->pi_ExpressionState);
+ for (i = 0; i < pkinfo->pi_Key->partnatts; i++)
+ {
+ AttrNumber keycol = pkinfo->pi_Key->partattrs[i];
+ Datum pkDatum;
+ bool isNull;
+
+ if (keycol != 0)
+ {
+ /* Plain column; get the value directly from the heap tuple */
+ pkDatum = slot_getattr(slot, keycol, &isNull);
+ }
+ else
+ {
+ /* Expression; need to evaluate it */
+ if (partexpr_item == NULL)
+ elog(ERROR, "wrong number of partition key expressions");
+ pkDatum = ExecEvalExprSwitchContext((ExprState *) lfirst(partexpr_item),
+ GetPerTupleExprContext(estate),
+ &isNull,
+ NULL);
+ partexpr_item = lnext(partexpr_item);
+ }
+ values[i] = pkDatum;
+ isnull[i] = isNull;
+ }
+
+ if (partexpr_item != NULL)
+ elog(ERROR, "wrong number of partition key expressions");
+}
+
+/*
+ * list_partition_for_tuple
+ * Find the list partition for a tuple
+ *
+ * Returns -1 if none found.
+ */
+static int
+list_partition_for_tuple(PartitionKey key, PartitionDesc pdesc,
+ Datum value, bool isnull)
+{
+ int i;
+
+ Assert(pdesc->nparts > 0);
+
+ for (i = 0; i < pdesc->nparts; i++)
+ {
+ int j;
+
+ if (isnull)
+ {
+ if (pdesc->lists[i]->contains_null)
+ return i;
+
+ continue;
+ }
+
+ for (j = 0; j < pdesc->lists[i]->nvalues; j++)
+ {
+ int32 cmpval;
+
+ cmpval = DatumGetInt32(FunctionCall2Coll(&key->partsupfunc[0],
+ key->tcinfo->typcoll[0],
+ pdesc->lists[i]->values[j],
+ value));
+ if (!cmpval)
+ return i;
+ }
+ }
+
+ return -1;
+}
+
+/*
+ * range_partition_for_tuple
+ * Search the range partition for a range key ('values')
+ *
+ * Returns -1 if none found.
+ */
+static int
+range_partition_for_tuple(PartitionKey key, PartitionDesc pdesc, Datum *values)
+{
+ Assert(pdesc->nparts > 0);
+
+ return range_partition_bsearch(key, pdesc, values);
+}
+
+/*
+ * range_partition_bsearch
+ * Workhorse of range_partition_for_tuple
+ */
+static int
+range_partition_bsearch(PartitionKey key, PartitionDesc pdesc,
+ Datum *values)
+{
+ int low, high;
+
+ /* Good ol' bsearch */
+ low = 0;
+ high = pdesc->nparts - 1;
+ while (low <= high)
+ {
+ int idx = (low + high) / 2;
+
+ if (pdesc->rangeuppers[idx]->infinite)
+ {
+ if (rightof(key, values, pdesc->rangelowers[idx]))
+ return idx;
+
+ break;
+ }
+ else if (leftof(key, values, pdesc->rangeuppers[idx]))
+ {
+ if (pdesc->rangelowers[idx]->infinite)
+ return idx;
+
+ if (rightof(key, values, pdesc->rangelowers[idx]))
+ return idx;
+
+ high = idx - 1;
+ continue;
+ }
+
+ low = idx + 1;
+ }
+
+ return -1;
+}
+
+/* Does range key lie to the right of partition bound */
+static bool
+rightof(PartitionKey key, Datum *values, PartitionRangeBound *bound)
+{
+ int32 cmpval = compare_range_keys(key, values, bound->val);
+
+ if (!cmpval)
+ return bound->lower ? bound->inclusive : !bound->inclusive;
+
+ return cmpval > 0;
+}
+
+/* Does range key lie to the left of partition bound */
+static bool
+leftof(PartitionKey key, Datum *values, PartitionRangeBound *bound)
+{
+ int32 cmpval = compare_range_keys(key, values, bound->val);
+
+ if (!cmpval)
+ return !bound->lower ? bound->inclusive : !bound->inclusive;
+
+ return cmpval < 0;
+}
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 14e81de..0b788b1 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -161,6 +161,10 @@ typedef struct CopyStateData
ExprState **defexprs; /* array of default att expressions */
bool volatile_defexprs; /* is any of defexprs volatile? */
List *range_table;
+ PartitionDescNode pdnode; /* partition descriptor node tree */
+ ResultRelInfo *partitions;
+ TupleConversionMap **partition_tupconv_maps;
+ int num_partitions;
/*
* These variables are used to reduce overhead in textual COPY FROM.
@@ -1364,6 +1368,52 @@ BeginCopy(bool is_from,
(errcode(ERRCODE_UNDEFINED_COLUMN),
errmsg("table \"%s\" does not have OIDs",
RelationGetRelationName(cstate->rel))));
+
+ /* Initialize state for CopyFrom tuple routing */
+ if (is_from && rel->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+ {
+ List *leaf_part_oids;
+ ListCell *cell;
+ int i;
+ int num_leaf_parts;
+ ResultRelInfo *leaf_rel_rri;
+
+ cstate->pdnode = RelationGetPartitionDescNode(rel);
+ leaf_part_oids = get_leaf_partition_oids(cstate->pdnode);
+ num_leaf_parts = list_length(leaf_part_oids);
+
+ cstate->num_partitions = num_leaf_parts;
+ cstate->partitions = (ResultRelInfo *)
+ palloc0(num_leaf_parts * sizeof(ResultRelInfo));
+ cstate->partition_tupconv_maps = (TupleConversionMap **)
+ palloc0(num_leaf_parts * sizeof(TupleConversionMap *));
+
+ leaf_rel_rri = cstate->partitions;
+ i = 0;
+ foreach(cell, leaf_part_oids)
+ {
+ Relation leaf_rel;
+
+ leaf_rel = heap_open(lfirst_oid(cell), RowExclusiveLock);
+ InitResultRelInfo(leaf_rel_rri,
+ leaf_rel,
+ 1, /* dummy */
+ false, /* no need for partition check */
+ 0);
+
+ /* Open partition indices */
+ ExecOpenIndices(leaf_rel_rri, false);
+
+ if (!equalTupleDescs(tupDesc, RelationGetDescr(leaf_rel)))
+ cstate->partition_tupconv_maps[i] =
+ convert_tuples_by_name(tupDesc,
+ RelationGetDescr(leaf_rel),
+ gettext_noop("could not convert row type"));
+
+ leaf_rel_rri++;
+ i++;
+ }
+ }
}
else
{
@@ -1659,6 +1709,8 @@ ClosePipeToProgram(CopyState cstate)
static void
EndCopy(CopyState cstate)
{
+ int i;
+
if (cstate->is_program)
{
ClosePipeToProgram(cstate);
@@ -1672,6 +1724,18 @@ EndCopy(CopyState cstate)
cstate->filename)));
}
+ /* Close all partitions and indices thereof */
+ for (i = 0; i < cstate->num_partitions; i++)
+ {
+ ResultRelInfo *resultRelInfo = cstate->partitions + i;
+
+ ExecCloseIndices(resultRelInfo);
+ heap_close(resultRelInfo->ri_RelationDesc, NoLock);
+
+ if (cstate->partition_tupconv_maps[i])
+ pfree(cstate->partition_tupconv_maps[i]);
+ }
+
MemoryContextDelete(cstate->copycontext);
pfree(cstate);
}
@@ -2216,6 +2280,7 @@ CopyFrom(CopyState cstate)
Datum *values;
bool *nulls;
ResultRelInfo *resultRelInfo;
+ ResultRelInfo *saved_resultRelInfo = NULL;
EState *estate = CreateExecutorState(); /* for ExecConstraints() */
ExprContext *econtext;
TupleTableSlot *myslot;
@@ -2236,7 +2301,8 @@ CopyFrom(CopyState cstate)
Assert(cstate->rel);
- if (cstate->rel->rd_rel->relkind != RELKIND_RELATION)
+ if (cstate->rel->rd_rel->relkind != RELKIND_RELATION &&
+ cstate->rel->rd_rel->relkind != RELKIND_PARTITIONED_REL)
{
if (cstate->rel->rd_rel->relkind == RELKIND_VIEW)
ereport(ERROR,
@@ -2344,6 +2410,7 @@ CopyFrom(CopyState cstate)
InitResultRelInfo(resultRelInfo,
cstate->rel,
1, /* dummy rangetable index */
+ true, /* do load partition check expression */
0);
ExecOpenIndices(resultRelInfo, false);
@@ -2371,6 +2438,7 @@ CopyFrom(CopyState cstate)
if ((resultRelInfo->ri_TrigDesc != NULL &&
(resultRelInfo->ri_TrigDesc->trig_insert_before_row ||
resultRelInfo->ri_TrigDesc->trig_insert_instead_row)) ||
+ cstate->pdnode != NULL ||
cstate->volatile_defexprs)
{
useHeapMultiInsert = false;
@@ -2395,7 +2463,11 @@ CopyFrom(CopyState cstate)
values = (Datum *) palloc(tupDesc->natts * sizeof(Datum));
nulls = (bool *) palloc(tupDesc->natts * sizeof(bool));
- bistate = GetBulkInsertState();
+ if (useHeapMultiInsert)
+ bistate = GetBulkInsertState();
+ else
+ bistate = NULL;
+
econtext = GetPerTupleExprContext(estate);
/* Set up callback to identify error line number */
@@ -2447,6 +2519,31 @@ CopyFrom(CopyState cstate)
slot = myslot;
ExecStoreTuple(tuple, slot, InvalidBuffer, false);
+ /* Determine the partition */
+ saved_resultRelInfo = resultRelInfo;
+ if (cstate->pdnode)
+ {
+ int i_leaf_partition;
+ TupleConversionMap *map;
+
+ econtext->ecxt_scantuple = slot;
+ i_leaf_partition = ExecFindPartition(resultRelInfo,
+ cstate->pdnode,
+ slot,
+ estate);
+ Assert(i_leaf_partition >= 0 &&
+ i_leaf_partition < cstate->num_partitions);
+
+ resultRelInfo = cstate->partitions + i_leaf_partition;
+ estate->es_result_relation_info = resultRelInfo;
+
+ map = cstate->partition_tupconv_maps[i_leaf_partition];
+ if (map)
+ tuple = do_convert_tuple(tuple, map);
+
+ tuple->t_tableOid = RelationGetRelid(resultRelInfo->ri_RelationDesc);
+ }
+
skip_tuple = false;
/* BEFORE ROW INSERT Triggers */
@@ -2497,7 +2594,8 @@ CopyFrom(CopyState cstate)
List *recheckIndexes = NIL;
/* OK, store the tuple and create index entries for it */
- heap_insert(cstate->rel, tuple, mycid, hi_options, bistate);
+ heap_insert(resultRelInfo->ri_RelationDesc,
+ tuple, mycid, hi_options, bistate);
if (resultRelInfo->ri_NumIndices > 0)
recheckIndexes = ExecInsertIndexTuples(slot, &(tuple->t_self),
@@ -2517,6 +2615,12 @@ CopyFrom(CopyState cstate)
* tuples inserted by an INSERT command.
*/
processed++;
+
+ if (saved_resultRelInfo)
+ {
+ resultRelInfo = saved_resultRelInfo;
+ estate->es_result_relation_info = resultRelInfo;
+ }
}
}
@@ -2530,7 +2634,8 @@ CopyFrom(CopyState cstate)
/* Done, clean up */
error_context_stack = errcallback.previous;
- FreeBulkInsertState(bistate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
MemoryContextSwitchTo(oldcontext);
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index b5a1136..bf76013 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -1242,6 +1242,7 @@ ExecuteTruncate(TruncateStmt *stmt)
InitResultRelInfo(resultRelInfo,
rel,
0, /* dummy rangetable index */
+ false,
0);
resultRelInfo++;
}
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index f7a23d9..0bb3fb7 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -826,6 +826,7 @@ InitPlan(QueryDesc *queryDesc, int eflags)
InitResultRelInfo(resultRelInfo,
resultRelation,
resultRelationIndex,
+ true,
estate->es_instrument);
resultRelInfo++;
}
@@ -1215,6 +1216,7 @@ void
InitResultRelInfo(ResultRelInfo *resultRelInfo,
Relation resultRelationDesc,
Index resultRelationIndex,
+ bool load_partition_check,
int instrument_options)
{
MemSet(resultRelInfo, 0, sizeof(ResultRelInfo));
@@ -1252,7 +1254,8 @@ InitResultRelInfo(ResultRelInfo *resultRelInfo,
resultRelInfo->ri_ConstraintExprs = NULL;
resultRelInfo->ri_junkFilter = NULL;
resultRelInfo->ri_projectReturning = NULL;
- resultRelInfo->ri_PartitionCheck = (Expr *)
+ if (load_partition_check)
+ resultRelInfo->ri_PartitionCheck = (Expr *)
RelationGetPartitionCheckExpr(resultRelationDesc);
}
@@ -1316,6 +1319,7 @@ ExecGetTriggerResultRel(EState *estate, Oid relid)
InitResultRelInfo(rInfo,
rel,
0, /* dummy rangetable index */
+ true,
estate->es_instrument);
estate->es_trig_target_relations =
lappend(estate->es_trig_target_relations, rInfo);
@@ -2988,3 +2992,41 @@ EvalPlanQualEnd(EPQState *epqstate)
epqstate->planstate = NULL;
epqstate->origslot = NULL;
}
+
+int
+ExecFindPartition(ResultRelInfo *resultRelInfo, PartitionDescNode pdnode,
+ TupleTableSlot *slot, EState *estate)
+{
+ int i_leaf_partition;
+ Oid failed_at;
+
+ i_leaf_partition = get_partition_for_tuple(pdnode, slot, estate,
+ &failed_at);
+
+ if (i_leaf_partition < 0)
+ {
+ Relation rel = resultRelInfo->ri_RelationDesc;
+ char *val_desc;
+ Bitmapset *insertedCols,
+ *updatedCols,
+ *modifiedCols;
+ TupleDesc tupDesc = RelationGetDescr(rel);
+
+ insertedCols = GetInsertedColumns(resultRelInfo, estate);
+ updatedCols = GetUpdatedColumns(resultRelInfo, estate);
+ modifiedCols = bms_union(insertedCols, updatedCols);
+ val_desc = ExecBuildSlotValueDescription(RelationGetRelid(rel),
+ slot,
+ tupDesc,
+ modifiedCols,
+ 64);
+ Assert(OidIsValid(failed_at));
+ ereport(ERROR,
+ (errcode(ERRCODE_CHECK_VIOLATION),
+ errmsg("no partition of relation \"%s\" found for row",
+ get_rel_name(failed_at)),
+ val_desc ? errdetail("Failing row contains %s.", val_desc) : 0));
+ }
+
+ return i_leaf_partition;
+}
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 6ecb6be..581112d 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -243,6 +243,7 @@ ExecInsert(ModifyTableState *mtstate,
{
HeapTuple tuple;
ResultRelInfo *resultRelInfo;
+ ResultRelInfo *saved_resultRelInfo = NULL;
Relation resultRelationDesc;
Oid newId;
List *recheckIndexes = NIL;
@@ -257,6 +258,31 @@ ExecInsert(ModifyTableState *mtstate,
* get information on the (current) result relation
*/
resultRelInfo = estate->es_result_relation_info;
+
+ saved_resultRelInfo = resultRelInfo;
+
+ if (mtstate->mt_partition_node)
+ {
+ int i_leaf_partition;
+ ExprContext *econtext = GetPerTupleExprContext(estate);
+ TupleConversionMap *map;
+
+ econtext->ecxt_scantuple = slot;
+ i_leaf_partition = ExecFindPartition(resultRelInfo,
+ mtstate->mt_partition_node,
+ slot,
+ estate);
+ Assert(i_leaf_partition >= 0 &&
+ i_leaf_partition < mtstate->mt_num_partitions);
+
+ resultRelInfo = mtstate->mt_partitions + i_leaf_partition;
+ estate->es_result_relation_info = resultRelInfo;
+
+ map = mtstate->mt_partition_tupconv_maps[i_leaf_partition];
+ if (map)
+ tuple = do_convert_tuple(tuple, map);
+ }
+
resultRelationDesc = resultRelInfo->ri_RelationDesc;
/*
@@ -496,6 +522,12 @@ ExecInsert(ModifyTableState *mtstate,
list_free(recheckIndexes);
+ if (saved_resultRelInfo)
+ {
+ resultRelInfo = saved_resultRelInfo;
+ estate->es_result_relation_info = resultRelInfo;
+ }
+
/*
* Check any WITH CHECK OPTION constraints from parent views. We are
* required to do this after testing all constraints and uniqueness
@@ -1550,6 +1582,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
Plan *subplan;
ListCell *l;
int i;
+ Relation rel;
/* check for unsupported flags */
Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));
@@ -1640,6 +1673,57 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
estate->es_result_relation_info = saved_resultRelInfo;
+ /* Build state for INSERT tuple routing */
+ rel = mtstate->resultRelInfo->ri_RelationDesc;
+ if (operation == CMD_INSERT && rel->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+ {
+ int i,
+ num_leaf_parts;
+ List *leaf_part_oids;
+ ListCell *cell;
+ ResultRelInfo *leaf_rel_rri;
+
+ mtstate->mt_partition_node = RelationGetPartitionDescNode(rel);
+ leaf_part_oids = get_leaf_partition_oids(mtstate->mt_partition_node);
+ num_leaf_parts = list_length(leaf_part_oids);
+
+ mtstate->mt_num_partitions = num_leaf_parts;
+ mtstate->mt_partitions = (ResultRelInfo *)
+ palloc0(num_leaf_parts * sizeof(ResultRelInfo));
+ mtstate->mt_partition_tupconv_maps = (TupleConversionMap **)
+ palloc0(num_leaf_parts * sizeof(TupleConversionMap *));
+
+ leaf_rel_rri = mtstate->mt_partitions;
+ i = 0;
+ foreach(cell, leaf_part_oids)
+ {
+ Relation leaf_rel;
+
+ leaf_rel = heap_open(lfirst_oid(cell), RowExclusiveLock);
+ InitResultRelInfo(leaf_rel_rri,
+ leaf_rel,
+ 1, /* dummy */
+ false, /* no need for partition checks */
+ eflags);
+
+ /* Open partition indices (note: ON CONFLICT unsupported)*/
+ if (leaf_rel_rri->ri_RelationDesc->rd_rel->relhasindex &&
+ operation != CMD_DELETE &&
+ leaf_rel_rri->ri_IndexRelationDescs == NULL)
+ ExecOpenIndices(leaf_rel_rri, false);
+
+ if (!equalTupleDescs(RelationGetDescr(rel),
+ RelationGetDescr(leaf_rel)))
+ mtstate->mt_partition_tupconv_maps[i] =
+ convert_tuples_by_name(RelationGetDescr(rel),
+ RelationGetDescr(leaf_rel),
+ gettext_noop("could not convert row type"));
+
+ leaf_rel_rri++;
+ i++;
+ }
+ }
+
/*
* Initialize any WITH CHECK OPTION constraints if needed.
*/
@@ -1957,6 +2041,18 @@ ExecEndModifyTable(ModifyTableState *node)
resultRelInfo);
}
+ /* Close all partitions and indices thereof */
+ for (i = 0; i < node->mt_num_partitions; i++)
+ {
+ ResultRelInfo *resultRelInfo = node->mt_partitions + i;
+
+ ExecCloseIndices(resultRelInfo);
+ heap_close(resultRelInfo->ri_RelationDesc, NoLock);
+
+ if (node->mt_partition_tupconv_maps[i])
+ pfree(node->mt_partition_tupconv_maps[i]);
+ }
+
/*
* Free the exprcontext
*/
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index f55129a..802cd68 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -755,8 +755,16 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
/* Process ON CONFLICT, if any. */
if (stmt->onConflictClause)
+ {
+ /* Bail out if target relation is partitioned table */
+ if (relid_is_partitioned(pstate->p_target_rangetblentry->relid))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("ON CONFLICT clause is not supported with partitioned tables")));
+
qry->onConflict = transformOnConflictClause(pstate,
stmt->onConflictClause);
+ }
/*
* If we have a RETURNING clause, we need to add the target relation to
diff --git a/src/include/catalog/partition.h b/src/include/catalog/partition.h
index 7d4bf2f..c987d63 100644
--- a/src/include/catalog/partition.h
+++ b/src/include/catalog/partition.h
@@ -14,6 +14,8 @@
#define PARTITION_H
#include "fmgr.h"
+#include "executor/tuptable.h"
+#include "nodes/execnodes.h"
#include "parser/parse_node.h"
#include "utils/relcache.h"
@@ -85,6 +87,22 @@ typedef struct PartitionDescData
typedef struct PartitionDescData *PartitionDesc;
/*
+ * PartitionKeyExecInfo
+ *
+ * This struct holds the information needed to extract partition
+ * column values from a heap tuple.
+ *
+ * Key copy of the rd_partkey of rel
+ * ExpressionState exec state for expressions, or NIL if none
+ */
+typedef struct PartitionKeyExecInfo
+{
+ NodeTag type;
+ PartitionKey pi_Key;
+ List *pi_ExpressionState; /* list of ExprState */
+} PartitionKeyExecInfo;
+
+/*
* Partition tree node
*
* pkinfo PartitionKey executor state
@@ -100,6 +118,7 @@ typedef struct PartitionDescData *PartitionDesc;
*/
typedef struct PartitionDescNodeData
{
+ PartitionKeyExecInfo *pkinfo;
PartitionDesc pdesc;
Oid relid;
int index;
@@ -138,4 +157,9 @@ extern Expr *get_check_expr_from_partbound(Relation rel, Relation parent,
Node *bound);
extern List *get_leaf_partition_oids(PartitionDescNode pdnode);
extern PartitionDescNode RelationGetPartitionDescNode(Relation rel);
+
+extern int get_partition_for_tuple(PartitionDescNode pdnode,
+ TupleTableSlot *slot,
+ EState *estate,
+ Oid *failed_at);
#endif /* PARTITION_H */
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index 44fac27..d4e8b35 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -14,6 +14,7 @@
#ifndef EXECUTOR_H
#define EXECUTOR_H
+#include "catalog/partition.h"
#include "executor/execdesc.h"
#include "nodes/parsenodes.h"
@@ -188,6 +189,7 @@ extern void CheckValidResultRel(Relation resultRel, CmdType operation);
extern void InitResultRelInfo(ResultRelInfo *resultRelInfo,
Relation resultRelationDesc,
Index resultRelationIndex,
+ bool load_partition_check,
int instrument_options);
extern ResultRelInfo *ExecGetTriggerResultRel(EState *estate, Oid relid);
extern bool ExecContextForcesOids(PlanState *planstate, bool *hasoids);
@@ -211,6 +213,10 @@ extern void EvalPlanQualSetPlan(EPQState *epqstate,
extern void EvalPlanQualSetTuple(EPQState *epqstate, Index rti,
HeapTuple tuple);
extern HeapTuple EvalPlanQualGetTuple(EPQState *epqstate, Index rti);
+extern int ExecFindPartition(ResultRelInfo *resultRelInfo,
+ PartitionDescNode pdnode,
+ TupleTableSlot *slot,
+ EState *estate);
#define EvalPlanQualSetSlot(epqstate, slot) ((epqstate)->origslot = (slot))
extern void EvalPlanQualFetchRowMarks(EPQState *epqstate);
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 74288e2..f2f535e 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -16,6 +16,7 @@
#include "access/genam.h"
#include "access/heapam.h"
+#include "access/tupconvert.h"
#include "executor/instrument.h"
#include "lib/pairingheap.h"
#include "nodes/params.h"
@@ -1140,6 +1141,15 @@ typedef struct ModifyTableState
* tlist */
TupleTableSlot *mt_conflproj; /* CONFLICT ... SET ... projection
* target */
+ struct PartitionDescNodeData *mt_partition_node;
+ /* Partition descriptor node tree */
+ ResultRelInfo *mt_partitions; /* Per leaf partition target
+ * relations */
+ TupleConversionMap **mt_partition_tupconv_maps;
+ /* Per leaf partition
+ * tuple conversion map */
+ int mt_num_partitions; /* Number of leaf partition target
+ * relations in the above array */
} ModifyTableState;
/* ----------------
--
1.7.1
^ permalink raw reply [nested|flat] 161+ messages in thread
* Re: Declarative partitioning
@ 2016-06-08 13:22 Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
0 siblings, 1 reply; 161+ messages in thread
From: Ashutosh Bapat @ 2016-06-08 13:22 UTC (permalink / raw)
To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; +Cc: Ildar Musin <i.musin@postgrespro.ru>; Tom Lane <tgl@sss.pgh.pa.us>; pgsql-hackers
0003-... patch does not apply cleanly. It has some conflicts in pg_dump.c.
I have tried fixing the conflict in attached patch.
On Mon, May 23, 2016 at 3:35 PM, Amit Langote <Langote_Amit_f8@lab.ntt.co.jp
> wrote:
>
> Hi Ildar,
>
> On 2016/05/21 0:29, Ildar Musin wrote:
> > On 20.05.2016 11:37, Amit Langote wrote:
> >> Moreover, instead of pruning partitions in planner prep phase, might it
> >> not be better to do that when considering paths for the (partitioned)
> rel?
> >> IOW, instead of looking at parse->jointree, we should rather be
> working
> >> with rel->baserestrictinfo. Although, that would require some revisions
> >> to how append_rel_list, simple_rel_list, etc. are constructed and
> >> manipulated in a given planner invocation. Maybe it's time for that...
> >> Again, you may have already considered these things.
> >>
> > Yes, you're right, this is how we did it in pg_pathman extension. But for
> > this patch it requires further consideration and I'll do it in future!
>
> OK, sure.
>
> >> Could you try with the attached updated set of patches? I changed
> >> partition descriptor relcache code to eliminate excessive copying in
> >> previous versions.
>
> [...]
>
> > However I've encountered a problem which is that postgres crashes
> > occasionally while creating partitions. Here is function that reproduces
> > this behaviour:
> >
> > CREATE OR REPLACE FUNCTION fail()
> > RETURNS void
> > LANGUAGE plpgsql
> > AS $$
> > BEGIN
> > DROP TABLE IF EXISTS abc CASCADE;
> > CREATE TABLE abc (id SERIAL NOT NULL, a INT, dt TIMESTAMP) PARTITION BY
> > RANGE (a);
> > CREATE INDEX ON abc (a);
> > CREATE TABLE abc_0 PARTITION OF abc FOR VALUES START (0) END (1000);
> > CREATE TABLE abc_1 PARTITION OF abc FOR VALUES START (1000) END (2000);
> > END
> > $$;
> >
> > SELECT fail();
> >
> > It happens not every time but quite often. It doesn't happen if I execute
> > this commands one by one in psql. Backtrace:
> >
> > #0 range_overlaps_existing_partition (key=0x7f1097504410,
> > range_spec=0x1d0f400, pdesc=0x1d32200, with=0x7ffe437ead00) at
> > partition.c:747
>
> [...]
>
> I made a mistake in the last version of the patch which caused a relcache
> field to be pfree'd unexpectedly. Attached updated patches.
>
> Thanks,
> Amit
>
>
> --
> Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
> To make changes to your subscription:
> http://www.postgresql.org/mailpref/pgsql-hackers
>
>
--
Best Wishes,
Ashutosh Bapat
EnterpriseDB Corporation
The Postgres Database Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Attachments:
[text/x-diff] pg_dump_adjust.patch (568B, ../../CAFjFpRe3MYVAdVu6azPUGQ0-=Zvd5uOkvdDY0WFuejA=QAH+OA@mail.gmail.com/3-pg_dump_adjust.patch)
download | inline diff:
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index ea3bfd6..22c3a36 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -5296,7 +5296,7 @@ getTables(Archive *fout, int *numTables)
"(c.oid = pip.objoid "
"AND pip.classoid = 'pg_class'::regclass "
"AND pip.objsubid = 0) "
- "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c') "
+ "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c', '%c') "
"ORDER BY c.oid",
acl_subquery->data,
racl_subquery->data,
^ permalink raw reply [nested|flat] 161+ messages in thread
* Re: Declarative partitioning
@ 2016-06-09 01:50 Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
parent: Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
0 siblings, 2 replies; 161+ messages in thread
From: Amit Langote @ 2016-06-09 01:50 UTC (permalink / raw)
To: Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; +Cc: Ildar Musin <i.musin@postgrespro.ru>; Tom Lane <tgl@sss.pgh.pa.us>; pgsql-hackers
On 2016/06/08 22:22, Ashutosh Bapat wrote:
> On Mon, May 23, 2016 at 3:35 PM, Amit Langote wrote
>>
>> [...]
>>
>> I made a mistake in the last version of the patch which caused a relcache
>> field to be pfree'd unexpectedly. Attached updated patches.
>
> 0003-... patch does not apply cleanly. It has some conflicts in pg_dump.c.
> I have tried fixing the conflict in attached patch.
Thanks. See attached rebased patches.
Regards,
Amit
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Attachments:
[text/x-diff] 0001-Add-syntax-to-specify-partition-key-v6.patch (40.1K, ../../19a3733b-ab58-b993-c858-ec8fc578ddfd@lab.ntt.co.jp/2-0001-Add-syntax-to-specify-partition-key-v6.patch)
download | inline diff:
From 248cafdf2e32d24e404dc17d7b73e710700e082d Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Mon, 22 Feb 2016 14:06:33 +0900
Subject: [PATCH 1/7] Add syntax to specify partition key.
This adds the following clause to CREATE TABLE:
PARTITION BY method (col [USING opclass] [, ...])
where 'method' is either RANGE or LIST. 'col' could be either a simple
column reference or a parenthesized expression. 'opclass' is name
of btree operator class to use with the column. If method is LIST,
only one column is allowed, whereas, for RANGE method up to 32 columns
can be specified. If this clause is specified, the following constraints
viz. PARTITION KEY, UNIQUE, FOREIGN KEY, EXCLUSION are not allowed
because they cannot be supported on partitioned tables.
This adds 2 new parser nodes viz. PartitionElem and PartitionBy. The former
is similar to IndexElem and the latter consists of details of partition
key such as the method and list of PartitionElems. The new member partby
of CreateStmt is a pointer to the PartitionBy struct created when PARTITION
BY clause is present.
---
doc/src/sgml/ref/create_table.sgml | 35 +++++
src/backend/commands/indexcmds.c | 4 +-
src/backend/commands/tablecmds.c | 212 ++++++++++++++++++++++++++++
src/backend/nodes/copyfuncs.c | 33 +++++
src/backend/nodes/equalfuncs.c | 28 ++++
src/backend/nodes/outfuncs.c | 27 ++++
src/backend/parser/gram.y | 110 ++++++++++++---
src/backend/parser/parse_agg.c | 11 ++
src/backend/parser/parse_expr.c | 5 +
src/backend/parser/parse_utilcmd.c | 63 ++++++++
src/include/commands/defrem.h | 2 +
src/include/nodes/nodes.h | 2 +
src/include/nodes/parsenodes.h | 35 +++++
src/include/parser/kwlist.h | 1 +
src/include/parser/parse_node.h | 3 +-
src/include/pg_config_manual.h | 5 +
src/test/regress/expected/create_table.out | 67 +++++++++
src/test/regress/sql/create_table.sql | 56 ++++++++
18 files changed, 677 insertions(+), 22 deletions(-)
diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml
index d1807ed..70a7579 100644
--- a/doc/src/sgml/ref/create_table.sgml
+++ b/doc/src/sgml/ref/create_table.sgml
@@ -28,6 +28,7 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
[, ... ]
] )
[ INHERITS ( <replaceable>parent_table</replaceable> [, ... ] ) ]
+[ PARTITION BY {RANGE | LIST} ( { <replaceable class="parameter">column_name</replaceable> | ( <replaceable class="parameter">expression</replaceable> ) } [ <replaceable class="parameter">opclass</replaceable> ] [, ...] )
[ WITH ( <replaceable class="PARAMETER">storage_parameter</replaceable> [= <replaceable class="PARAMETER">value</replaceable>] [, ... ] ) | WITH OIDS | WITHOUT OIDS ]
[ ON COMMIT { PRESERVE ROWS | DELETE ROWS | DROP } ]
[ TABLESPACE <replaceable class="PARAMETER">tablespace_name</replaceable> ]
@@ -38,6 +39,7 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
| <replaceable>table_constraint</replaceable> }
[, ... ]
) ]
+[ PARTITION BY {RANGE | LIST} ( { <replaceable class="parameter">column_name</replaceable> | ( <replaceable class="parameter">expression</replaceable> ) } [ <replaceable class="parameter">opclass</replaceable> ] [, ...] )
[ WITH ( <replaceable class="PARAMETER">storage_parameter</replaceable> [= <replaceable class="PARAMETER">value</replaceable>] [, ... ] ) | WITH OIDS | WITHOUT OIDS ]
[ ON COMMIT { PRESERVE ROWS | DELETE ROWS | DROP } ]
[ TABLESPACE <replaceable class="PARAMETER">tablespace_name</replaceable> ]
@@ -314,6 +316,39 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
</varlistentry>
<varlistentry>
+ <term><literal>PARTITION BY {RANGE | LIST} ( { <replaceable class="parameter">column_name</replaceable> | ( <replaceable class="parameter">expression</replaceable> ) } [ <replaceable class="parameter">opclass</replaceable> ] [, ...] ) </literal></term>
+ <listitem>
+ <para>
+ The optional <literal>PARTITION BY</> clause specifies a method of
+ partitioning the table and the corresponding partition key. Table
+ thus created is called <firstterm>partitioned</firstterm> table. Key
+ consists of an ordered list of column names and/or expressions when
+ using the <literal>RANGE</> method, whereas only a single column or
+ expression can be specified when using the <literal>LIST</> method.
+ The type of a key column or an expresion must have an associated
+ btree operator class or one must be specified along with the column
+ or the expression.
+ </para>
+
+ <para>
+ A partitioned table is divided into sub-tables (called partitions), which
+ in turn, are created using separate <literal>CREATE TABLE</> commands.
+ The table itself is empty. A data row inserted into the table is mapped
+ to and stored in one of the partitions (if one exists) based on the
+ values of columns and/or expressions in the partition key and partition
+ rules associated with the partitions.
+ </para>
+
+ <para>
+ Currently, there are following limitations on definition of partitioned
+ tables: one cannot specify any UNIQUE, PRIMARY KEY, EXCLUDE and/or
+ FOREIGN KEY constraints.
+ </para>
+
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><literal>LIKE <replaceable>source_table</replaceable> [ <replaceable>like_option</replaceable> ... ]</literal></term>
<listitem>
<para>
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index d14d540..4ef6b80 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -69,8 +69,6 @@ static void ComputeIndexAttrs(IndexInfo *indexInfo,
char *accessMethodName, Oid accessMethodId,
bool amcanorder,
bool isconstraint);
-static Oid GetIndexOpClass(List *opclass, Oid attrType,
- char *accessMethodName, Oid accessMethodId);
static char *ChooseIndexName(const char *tabname, Oid namespaceId,
List *colnames, List *exclusionOpNames,
bool primary, bool isconstraint);
@@ -1256,7 +1254,7 @@ ComputeIndexAttrs(IndexInfo *indexInfo,
/*
* Resolve possibly-defaulted operator class specification
*/
-static Oid
+Oid
GetIndexOpClass(List *opclass, Oid attrType,
char *accessMethodName, Oid accessMethodId)
{
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 86e9814..7c82f39 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -433,6 +433,11 @@ static void RangeVarCallbackForDropRelation(const RangeVar *rel, Oid relOid,
Oid oldRelOid, void *arg);
static void RangeVarCallbackForAlterRelation(const RangeVar *rv, Oid relid,
Oid oldrelid, void *arg);
+static PartitionBy *transformPartitionBy(Relation rel, PartitionBy *partitionby);
+static void ComputePartitionAttrs(Oid relid, List *partParams,
+ AttrNumber *partattrs,
+ List **partexprs,
+ Oid *partoplass);
/* ----------------------------------------------------------------
@@ -710,6 +715,18 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
AddRelationNewConstraints(rel, rawDefaults, stmt->constraints,
true, true, false);
+ /* Process partition key, if any */
+ if (stmt->partby)
+ {
+ AttrNumber partattrs[PARTITION_MAX_KEYS];
+ Oid partopclass[PARTITION_MAX_KEYS];
+ List *partexprs = NIL;
+
+ stmt->partby = transformPartitionBy(rel, stmt->partby);
+ ComputePartitionAttrs(relationId, stmt->partby->partParams,
+ partattrs, &partexprs, partopclass);
+ }
+
ObjectAddressSet(address, RelationRelationId, relationId);
/*
@@ -12059,3 +12076,198 @@ RangeVarCallbackForAlterRelation(const RangeVar *rv, Oid relid, Oid oldrelid,
ReleaseSysCache(tuple);
}
+
+/*
+ * transformPartitionBy
+ * Transform any expressions present in the partition key
+ */
+static PartitionBy *
+transformPartitionBy(Relation rel, PartitionBy *partitionby)
+{
+ PartitionBy *partby;
+ ParseState *pstate;
+ RangeTblEntry *rte;
+ ListCell *l;
+
+ partby = (PartitionBy *) makeNode(PartitionBy);
+
+ partby->strategy = partitionby->strategy;
+ partby->location = partitionby->location;
+ partby->partParams = NIL;
+
+ /*
+ * Create a dummy ParseState and insert the target relation as its sole
+ * rangetable entry. We need a ParseState for transformExpr.
+ */
+ pstate = make_parsestate(NULL);
+ rte = addRangeTableEntryForRelation(pstate, rel, NULL, false, true);
+ addRTEtoQuery(pstate, rte, true, true, true);
+
+ /* take care of any partition expressions */
+ foreach(l, partitionby->partParams)
+ {
+ ListCell *column;
+ PartitionElem *pelem = (PartitionElem *) lfirst(l);
+
+ /* Check for PARTITION BY ... ON (foo, foo) */
+ foreach(column, partby->partParams)
+ {
+ PartitionElem *pparam = (PartitionElem *) lfirst(column);
+
+ if (pelem->name && pparam->name &&
+ !strcmp(pelem->name, pparam->name))
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_COLUMN),
+ errmsg("column \"%s\" appears twice in partition key", pelem->name),
+ parser_errposition(pstate, pelem->location)));
+ }
+
+ if (pelem->expr)
+ {
+ /* Now do parse transformation of the expression */
+ pelem->expr = transformExpr(pstate, pelem->expr,
+ EXPR_KIND_PARTITION_KEY);
+
+ /* we have to fix its collations too */
+ assign_expr_collations(pstate, pelem->expr);
+
+ /*
+ * transformExpr() should have already rejected subqueries,
+ * aggregates, and window functions, based on the EXPR_KIND_ for
+ * a partition key expression.
+ *
+ * Also reject expressions returning sets; this is for consistency
+ * DefineRelation() will make more checks.
+ */
+ if (expression_returns_set(pelem->expr))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("partition key expression cannot return a set"),
+ parser_errposition(pstate, pelem->location)));
+ }
+
+ partby->partParams = lappend(partby->partParams, pelem);
+ }
+
+ return partby;
+}
+
+/*
+ * ComputePartitionAttrs
+ * Compute per-partition-column information from partParams
+ */
+static void
+ComputePartitionAttrs(Oid relid, List *partParams, AttrNumber *partattrs,
+ List **partexprs, Oid *partopclass)
+{
+ int attn;
+ ListCell *lc;
+
+ attn = 0;
+ foreach(lc, partParams)
+ {
+ PartitionElem *pelem = (PartitionElem *) lfirst(lc);
+ Oid atttype;
+ Oid opclassOid;
+
+ if (pelem->name != NULL)
+ {
+ HeapTuple atttuple;
+ Form_pg_attribute attform;
+
+ atttuple = SearchSysCacheAttName(relid, pelem->name);
+ if (!HeapTupleIsValid(atttuple))
+ {
+ /* difference in error message spellings is historical */
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_COLUMN),
+ errmsg("column \"%s\" named in partition key does not exist",
+ pelem->name)));
+ }
+ attform = (Form_pg_attribute) GETSTRUCT(atttuple);
+
+ if (attform->attnum <= 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_COLUMN),
+ errmsg("cannot use system column \"%s\" in partition key",
+ pelem->name)));
+
+ partattrs[attn] = attform->attnum;
+ atttype = attform->atttypid;
+ ReleaseSysCache(atttuple);
+ }
+ else
+ {
+ /* Partition key expression */
+ Node *expr = pelem->expr;
+
+ Assert(expr != NULL);
+ atttype = exprType(expr);
+
+ if (IsA(expr, CollateExpr))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("cannot use COLLATE in partition key expression")));
+
+ if (IsA(expr, Var) &&
+ ((Var *) expr)->varattno != InvalidAttrNumber)
+ {
+ /*
+ * User wrote "(column)" or "(column COLLATE something)".
+ * Treat it like simple attribute anyway.
+ */
+ partattrs[attn] = ((Var *) expr)->varattno;
+ }
+ else
+ {
+ partattrs[attn] = 0; /* marks expression */
+ *partexprs = lappend(*partexprs, expr);
+
+ /*
+ * transformExpr() should have already rejected subqueries,
+ * aggregates, and window functions, based on the EXPR_KIND_
+ * for a partition key expression.
+ */
+
+ /*
+ * An expression using mutable functions is probably wrong even
+ * even to use in a partition key
+ */
+ expr = (Node *) expression_planner((Expr *) expr);
+
+ if (IsA(expr, Const))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("cannot use a constant expression as partition key")));
+
+ if (contain_mutable_functions(expr))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("functions in partition key expression must be marked IMMUTABLE")));
+ }
+ }
+
+ /*
+ * Identify the opclass to use. At the moment, we use "btree" operators
+ * that seems enough for list and range partitioning.
+ */
+ if (!pelem->opclass)
+ {
+ opclassOid = GetDefaultOpClass(atttype, BTREE_AM_OID);
+
+ if (!OidIsValid(opclassOid))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("data type %s has no default btree operator class",
+ format_type_be(atttype)),
+ errhint("You must specify an existing btree operator class or define one for the type.")));
+ }
+ else
+ opclassOid = GetIndexOpClass(pelem->opclass,
+ atttype,
+ "btree",
+ BTREE_AM_OID);
+
+ partopclass[attn++] = opclassOid;
+ }
+}
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 08ed990..dfaac9f 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3001,6 +3001,7 @@ CopyCreateStmtFields(const CreateStmt *from, CreateStmt *newnode)
COPY_NODE_FIELD(relation);
COPY_NODE_FIELD(tableElts);
COPY_NODE_FIELD(inhRelations);
+ COPY_NODE_FIELD(partby);
COPY_NODE_FIELD(ofTypename);
COPY_NODE_FIELD(constraints);
COPY_NODE_FIELD(options);
@@ -4156,6 +4157,32 @@ _copyAlterPolicyStmt(const AlterPolicyStmt *from)
return newnode;
}
+static PartitionBy *
+_copyPartitionBy(const PartitionBy *from)
+{
+
+ PartitionBy *newnode = makeNode(PartitionBy);
+
+ COPY_SCALAR_FIELD(strategy);
+ COPY_NODE_FIELD(partParams);
+ COPY_LOCATION_FIELD(location);
+
+ return newnode;
+}
+
+static PartitionElem *
+_copyPartitionElem(const PartitionElem *from)
+{
+ PartitionElem *newnode = makeNode(PartitionElem);
+
+ COPY_STRING_FIELD(name);
+ COPY_NODE_FIELD(expr);
+ COPY_NODE_FIELD(opclass);
+ COPY_LOCATION_FIELD(location);
+
+ return newnode;
+}
+
/* ****************************************************************
* pg_list.h copy functions
* ****************************************************************
@@ -5049,6 +5076,12 @@ copyObject(const void *from)
case T_RoleSpec:
retval = _copyRoleSpec(from);
break;
+ case T_PartitionBy:
+ retval = _copyPartitionBy(from);
+ break;
+ case T_PartitionElem:
+ retval = _copyPartitionElem(from);
+ break;
default:
elog(ERROR, "unrecognized node type: %d", (int) nodeTag(from));
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index c5ccc42..56325fe 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1156,6 +1156,7 @@ _equalCreateStmt(const CreateStmt *a, const CreateStmt *b)
COMPARE_NODE_FIELD(relation);
COMPARE_NODE_FIELD(tableElts);
COMPARE_NODE_FIELD(inhRelations);
+ COMPARE_NODE_FIELD(partby);
COMPARE_NODE_FIELD(ofTypename);
COMPARE_NODE_FIELD(constraints);
COMPARE_NODE_FIELD(options);
@@ -2619,6 +2620,27 @@ _equalRoleSpec(const RoleSpec *a, const RoleSpec *b)
return true;
}
+static bool
+_equalPartitionBy(const PartitionBy *a, const PartitionBy *b)
+{
+ COMPARE_SCALAR_FIELD(strategy);
+ COMPARE_NODE_FIELD(partParams);
+ COMPARE_LOCATION_FIELD(location);
+
+ return true;
+}
+
+static bool
+_equalPartitionElem(const PartitionElem *a, const PartitionElem *b)
+{
+ COMPARE_STRING_FIELD(name);
+ COMPARE_NODE_FIELD(expr);
+ COMPARE_NODE_FIELD(opclass);
+ COMPARE_LOCATION_FIELD(location);
+
+ return true;
+}
+
/*
* Stuff from pg_list.h
*/
@@ -3369,6 +3391,12 @@ equal(const void *a, const void *b)
case T_RoleSpec:
retval = _equalRoleSpec(a, b);
break;
+ case T_PartitionBy:
+ retval = _equalPartitionBy(a, b);
+ break;
+ case T_PartitionElem:
+ retval = _equalPartitionElem(a, b);
+ break;
default:
elog(ERROR, "unrecognized node type: %d",
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 389808c..b02699a 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2359,6 +2359,7 @@ _outCreateStmtInfo(StringInfo str, const CreateStmt *node)
WRITE_NODE_FIELD(relation);
WRITE_NODE_FIELD(tableElts);
WRITE_NODE_FIELD(inhRelations);
+ WRITE_NODE_FIELD(partby);
WRITE_NODE_FIELD(ofTypename);
WRITE_NODE_FIELD(constraints);
WRITE_NODE_FIELD(options);
@@ -3207,6 +3208,26 @@ _outConstraint(StringInfo str, const Constraint *node)
}
}
+static void
+_outPartitionBy(StringInfo str, const PartitionBy *node)
+{
+ WRITE_NODE_TYPE("PARTITIONBY");
+
+ WRITE_CHAR_FIELD(strategy);
+ WRITE_NODE_FIELD(partParams);
+ WRITE_LOCATION_FIELD(location);
+}
+
+static void
+_outPartitionElem(StringInfo str, const PartitionElem *node)
+{
+ WRITE_NODE_TYPE("PARTITIONELEM");
+
+ WRITE_STRING_FIELD(name);
+ WRITE_NODE_FIELD(expr);
+ WRITE_NODE_FIELD(opclass);
+ WRITE_LOCATION_FIELD(location);
+}
/*
* outNode -
@@ -3783,6 +3804,12 @@ outNode(StringInfo str, const void *obj)
case T_XmlSerialize:
_outXmlSerialize(str, obj);
break;
+ case T_PartitionBy:
+ _outPartitionBy(str, obj);
+ break;
+ case T_PartitionElem:
+ _outPartitionElem(str, obj);
+ break;
default:
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 2c950f9..3757eee 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -227,6 +227,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
struct ImportQual *importqual;
InsertStmt *istmt;
VariableSetStmt *vsetstmt;
+ PartitionElem *partelem;
+ PartitionBy *partby;
}
%type <node> stmt schema_stmt
@@ -539,6 +541,9 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
opt_frame_clause frame_extent frame_bound
%type <str> opt_existing_window_name
%type <boolean> opt_if_not_exists
+%type <partby> PartitionBy OptPartitionBy
+%type <partelem> part_elem
+%type <list> part_params
/*
* Non-keyword token types. These are hard-wired into the "flex" lexer.
@@ -603,7 +608,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
KEY
LABEL LANGUAGE LARGE_P LAST_P LATERAL_P
- LEADING LEAKPROOF LEAST LEFT LEVEL LIKE LIMIT LISTEN LOAD LOCAL
+ LEADING LEAKPROOF LEAST LEFT LEVEL LIKE LIMIT LIST LISTEN LOAD LOCAL
LOCALTIME LOCALTIMESTAMP LOCATION LOCK_P LOCKED LOGGED
MAPPING MATCH MATERIALIZED MAXVALUE METHOD MINUTE_P MINVALUE MODE MONTH_P MOVE
@@ -2808,69 +2813,75 @@ copy_generic_opt_arg_list_item:
*****************************************************************************/
CreateStmt: CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
- OptInherit OptWith OnCommitOption OptTableSpace
+ OptInherit OptPartitionBy OptWith OnCommitOption OptTableSpace
{
CreateStmt *n = makeNode(CreateStmt);
$4->relpersistence = $2;
n->relation = $4;
n->tableElts = $6;
n->inhRelations = $8;
+ n->partby = $9;
n->ofTypename = NULL;
n->constraints = NIL;
- n->options = $9;
- n->oncommit = $10;
- n->tablespacename = $11;
+ n->options = $10;
+ n->oncommit = $11;
+ n->tablespacename = $12;
n->if_not_exists = false;
$$ = (Node *)n;
}
| CREATE OptTemp TABLE IF_P NOT EXISTS qualified_name '('
- OptTableElementList ')' OptInherit OptWith OnCommitOption
- OptTableSpace
+ OptTableElementList ')' OptInherit OptPartitionBy OptWith
+ OnCommitOption OptTableSpace
{
CreateStmt *n = makeNode(CreateStmt);
$7->relpersistence = $2;
n->relation = $7;
n->tableElts = $9;
n->inhRelations = $11;
+ n->partby = $12;
n->ofTypename = NULL;
n->constraints = NIL;
- n->options = $12;
- n->oncommit = $13;
- n->tablespacename = $14;
+ n->options = $13;
+ n->oncommit = $14;
+ n->tablespacename = $15;
n->if_not_exists = true;
$$ = (Node *)n;
}
| CREATE OptTemp TABLE qualified_name OF any_name
- OptTypedTableElementList OptWith OnCommitOption OptTableSpace
+ OptTypedTableElementList OptPartitionBy OptWith OnCommitOption
+ OptTableSpace
{
CreateStmt *n = makeNode(CreateStmt);
$4->relpersistence = $2;
n->relation = $4;
n->tableElts = $7;
n->inhRelations = NIL;
+ n->partby = $8;
n->ofTypename = makeTypeNameFromNameList($6);
n->ofTypename->location = @6;
n->constraints = NIL;
- n->options = $8;
- n->oncommit = $9;
- n->tablespacename = $10;
+ n->options = $9;
+ n->oncommit = $10;
+ n->tablespacename = $11;
n->if_not_exists = false;
$$ = (Node *)n;
}
| CREATE OptTemp TABLE IF_P NOT EXISTS qualified_name OF any_name
- OptTypedTableElementList OptWith OnCommitOption OptTableSpace
+ OptTypedTableElementList OptPartitionBy OptWith OnCommitOption
+ OptTableSpace
{
CreateStmt *n = makeNode(CreateStmt);
$7->relpersistence = $2;
n->relation = $7;
n->tableElts = $10;
n->inhRelations = NIL;
+ n->partby = $11;
n->ofTypename = makeTypeNameFromNameList($9);
n->ofTypename->location = @9;
n->constraints = NIL;
- n->options = $11;
- n->oncommit = $12;
- n->tablespacename = $13;
+ n->options = $12;
+ n->oncommit = $13;
+ n->tablespacename = $14;
n->if_not_exists = true;
$$ = (Node *)n;
}
@@ -3415,6 +3426,68 @@ OptInherit: INHERITS '(' qualified_name_list ')' { $$ = $3; }
| /*EMPTY*/ { $$ = NIL; }
;
+/* Optional partition key definition */
+OptPartitionBy: PartitionBy { $$ = $1; }
+ | /*EMPTY*/ { $$ = NULL; }
+ ;
+
+PartitionBy: PARTITION BY RANGE '(' part_params ')'
+ {
+ PartitionBy *n = makeNode(PartitionBy);
+
+ n->strategy = PARTITION_STRAT_RANGE;
+ n->partParams = $5;
+ n->location = @1;
+
+ $$ = n;
+ }
+ | PARTITION BY LIST '(' part_params ')'
+ {
+ PartitionBy *n = makeNode(PartitionBy);
+
+ n->strategy = PARTITION_STRAT_LIST;
+ n->partParams = $5;
+ n->location = @1;
+
+ $$ = n;
+ }
+ ;
+
+part_params: part_elem { $$ = list_make1($1); }
+ | part_params ',' part_elem { $$ = lappend($1, $3); }
+ ;
+
+part_elem: ColId opt_class
+ {
+ PartitionElem *n = makeNode(PartitionElem);
+
+ n->name = $1;
+ n->expr = NULL;
+ n->opclass = $2;
+ n->location = @1;
+ $$ = n;
+ }
+ | func_expr_windowless opt_class
+ {
+ PartitionElem *n = makeNode(PartitionElem);
+
+ n->name = NULL;
+ n->expr = $1;
+ n->opclass = $2;
+ n->location = @1;
+ $$ = n;
+ }
+ | '(' a_expr ')' opt_class
+ {
+ PartitionElem *n = makeNode(PartitionElem);
+
+ n->name = NULL;
+ n->expr = $2;
+ n->opclass = $4;
+ n->location = @1;
+ $$ = n;
+ }
+ ;
/* WITH (options) is preferred, WITH OIDS and WITHOUT OIDS are legacy forms */
OptWith:
WITH reloptions { $$ = $2; }
@@ -13840,6 +13913,7 @@ unreserved_keyword:
| LAST_P
| LEAKPROOF
| LEVEL
+ | LIST
| LISTEN
| LOAD
| LOCAL
diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c
index 91bfe66..b56fd4f 100644
--- a/src/backend/parser/parse_agg.c
+++ b/src/backend/parser/parse_agg.c
@@ -486,6 +486,14 @@ check_agglevels_and_constraints(ParseState *pstate, Node *expr)
err = _("grouping operations are not allowed in trigger WHEN conditions");
break;
+ case EXPR_KIND_PARTITION_KEY:
+ if (isAgg)
+ err = _("aggregate functions are not allowed in partition key expressions");
+ else
+ err = _("grouping operations are not allowed in partition key expressions");
+
+ break;
+
/*
* There is intentionally no default: case here, so that the
@@ -843,6 +851,9 @@ transformWindowFuncCall(ParseState *pstate, WindowFunc *wfunc,
case EXPR_KIND_TRIGGER_WHEN:
err = _("window functions are not allowed in trigger WHEN conditions");
break;
+ case EXPR_KIND_PARTITION_KEY:
+ err = _("window functions are not allowed in partition key expressions");
+ break;
/*
* There is intentionally no default: case here, so that the
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 8b28516..b53b7f1 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -1723,6 +1723,9 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
case EXPR_KIND_TRIGGER_WHEN:
err = _("cannot use subquery in trigger WHEN condition");
break;
+ case EXPR_KIND_PARTITION_KEY:
+ err = _("cannot use subquery in partition key expressions");
+ break;
/*
* There is intentionally no default: case here, so that the
@@ -3249,6 +3252,8 @@ ParseExprKindName(ParseExprKind exprKind)
return "EXECUTE";
case EXPR_KIND_TRIGGER_WHEN:
return "WHEN";
+ case EXPR_KIND_PARTITION_KEY:
+ return "partition key expression";
/*
* There is intentionally no default: case here, so that the
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 6528494..e267f74 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -87,6 +87,7 @@ typedef struct
List *alist; /* "after list" of things to do after creating
* the table */
IndexStmt *pkey; /* PRIMARY KEY index, if any */
+ bool ispartitioned; /* true if table is partitioned */
} CreateStmtContext;
/* State shared by transformCreateSchemaStmt and its subroutines */
@@ -229,6 +230,7 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
cxt.blist = NIL;
cxt.alist = NIL;
cxt.pkey = NULL;
+ cxt.ispartitioned = stmt->partby != NULL;
/*
* Notice that we allow OIDs here only for plain tables, even though
@@ -247,6 +249,24 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
if (stmt->ofTypename)
transformOfType(&cxt, stmt->ofTypename);
+ if (stmt->partby)
+ {
+ int partnatts = list_length(stmt->partby->partParams);
+
+ if (partnatts > PARTITION_MAX_KEYS)
+ ereport(ERROR,
+ (errcode(ERRCODE_TOO_MANY_COLUMNS),
+ errmsg("cannot use more than %d columns in partition key",
+ PARTITION_MAX_KEYS)));
+
+ if (stmt->partby->strategy == PARTITION_STRAT_LIST &&
+ partnatts > 1)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("cannot use more than one column in partition key"),
+ errdetail("Only one column allowed with list partitioning.")));
+ }
+
/*
* Run through each primary element in the table creation clause. Separate
* column defs from constraints, and do preliminary analysis. We have to
@@ -582,6 +602,12 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
errmsg("primary key constraints are not supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
+ if (cxt->ispartitioned)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("primary key constraints are not supported on partitioned tables"),
+ parser_errposition(cxt->pstate,
+ constraint->location)));
/* FALL THRU */
case CONSTR_UNIQUE:
@@ -591,6 +617,12 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
errmsg("unique constraints are not supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
+ if (cxt->ispartitioned)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("unique constraints are not supported on partitioned tables"),
+ parser_errposition(cxt->pstate,
+ constraint->location)));
if (constraint->keys == NIL)
constraint->keys = list_make1(makeString(column->colname));
cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
@@ -608,6 +640,12 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
errmsg("foreign key constraints are not supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
+ if (cxt->ispartitioned)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("foreign key constraints are not supported on partitioned tables"),
+ parser_errposition(cxt->pstate,
+ constraint->location)));
/*
* Fill in the current attribute's name and throw it into the
@@ -673,6 +711,12 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
errmsg("primary key constraints are not supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
+ if (cxt->ispartitioned)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("primary key constraints are not supported on partitioned tables"),
+ parser_errposition(cxt->pstate,
+ constraint->location)));
cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
break;
@@ -683,6 +727,12 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
errmsg("unique constraints are not supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
+ if (cxt->ispartitioned)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("unique constraints are not supported on partitioned tables"),
+ parser_errposition(cxt->pstate,
+ constraint->location)));
cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
break;
@@ -693,6 +743,12 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
errmsg("exclusion constraints are not supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
+ if (cxt->ispartitioned)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("exclusion constraints are not supported on partitioned tables"),
+ parser_errposition(cxt->pstate,
+ constraint->location)));
cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
break;
@@ -707,6 +763,12 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
errmsg("foreign key constraints are not supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
+ if (cxt->ispartitioned)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("foreign key constraints are not supported on partitioned tables"),
+ parser_errposition(cxt->pstate,
+ constraint->location)));
cxt->fkconstraints = lappend(cxt->fkconstraints, constraint);
break;
@@ -2515,6 +2577,7 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
cxt.blist = NIL;
cxt.alist = NIL;
cxt.pkey = NULL;
+ cxt.ispartitioned = false;
/*
* The only subtypes that currently require parse transformation handling
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index b064eb4..28024f8 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -42,6 +42,8 @@ extern bool CheckIndexCompatible(Oid oldId,
List *attributeList,
List *exclusionOpNames);
extern Oid GetDefaultOpClass(Oid type_id, Oid am_id);
+extern Oid GetIndexOpClass(List *opclass, Oid attrType,
+ char *accessMethodName, Oid accessMethodId);
/* commands/functioncmds.c */
extern ObjectAddress CreateFunction(CreateFunctionStmt *stmt, const char *queryString);
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 5953db4..d96f083 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -451,6 +451,8 @@ typedef enum NodeTag
T_OnConflictClause,
T_CommonTableExpr,
T_RoleSpec,
+ T_PartitionElem,
+ T_PartitionBy,
/*
* TAGS FOR REPLICATION GRAMMAR PARSE NODES (replnodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 714cf15..4f697b8 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -696,6 +696,40 @@ typedef struct XmlSerialize
int location; /* token location, or -1 if unknown */
} XmlSerialize;
+/* Partitioning related definitions */
+
+/*
+ * PartitionElem - a partition key column
+ *
+ * 'name' Name of the table column included in the key
+ * 'expr' Expression node tree of expressional key column
+ * 'opclass' Operator class name associated with the column
+ */
+typedef struct PartitionElem
+{
+ NodeTag type;
+ char *name; /* name of column to partition on, or NULL */
+ Node *expr; /* expression to partition on, or NULL */
+ List *opclass; /* name of desired opclass; NIL = default */
+ int location; /* token location, or -1 if unknown */
+} PartitionElem;
+
+/*
+ * PartitionBy - partition key definition including the strategy
+ *
+ * 'strategy' partition strategy to use (one of the below defined)
+ * 'partParams' List of PartitionElems, one for each key column
+ */
+#define PARTITION_STRAT_LIST 'l'
+#define PARTITION_STRAT_RANGE 'r'
+
+typedef struct PartitionBy
+{
+ NodeTag type;
+ char strategy;
+ List *partParams;
+ int location; /* token location, or -1 if unknown */
+} PartitionBy;
/****************************************************************************
* Nodes for a Query tree
@@ -1750,6 +1784,7 @@ typedef struct CreateStmt
List *tableElts; /* column definitions (list of ColumnDef) */
List *inhRelations; /* relations to inherit from (list of
* inhRelation) */
+ PartitionBy *partby; /* PARTITION BY clause */
TypeName *ofTypename; /* OF typename */
List *constraints; /* constraints (list of Constraint nodes) */
List *options; /* options from WITH clause */
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 17ffef5..40da67a 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -227,6 +227,7 @@ PG_KEYWORD("left", LEFT, TYPE_FUNC_NAME_KEYWORD)
PG_KEYWORD("level", LEVEL, UNRESERVED_KEYWORD)
PG_KEYWORD("like", LIKE, TYPE_FUNC_NAME_KEYWORD)
PG_KEYWORD("limit", LIMIT, RESERVED_KEYWORD)
+PG_KEYWORD("list", LIST, UNRESERVED_KEYWORD)
PG_KEYWORD("listen", LISTEN, UNRESERVED_KEYWORD)
PG_KEYWORD("load", LOAD, UNRESERVED_KEYWORD)
PG_KEYWORD("local", LOCAL, UNRESERVED_KEYWORD)
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index e3e359c..a13c6fb 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -64,7 +64,8 @@ typedef enum ParseExprKind
EXPR_KIND_ALTER_COL_TRANSFORM, /* transform expr in ALTER COLUMN TYPE */
EXPR_KIND_EXECUTE_PARAMETER, /* parameter value in EXECUTE */
EXPR_KIND_TRIGGER_WHEN, /* WHEN condition in CREATE TRIGGER */
- EXPR_KIND_POLICY /* USING or WITH CHECK expr in policy */
+ EXPR_KIND_POLICY, /* USING or WITH CHECK expr in policy */
+ EXPR_KIND_PARTITION_KEY /* partition key expression */
} ParseExprKind;
diff --git a/src/include/pg_config_manual.h b/src/include/pg_config_manual.h
index a2b2b61..01c6c09 100644
--- a/src/include/pg_config_manual.h
+++ b/src/include/pg_config_manual.h
@@ -46,6 +46,11 @@
#define INDEX_MAX_KEYS 32
/*
+ * Maximum number of columns in a partition key
+ */
+#define PARTITION_MAX_KEYS 32
+
+/*
* Set the upper and lower bounds of sequence values.
*/
#define SEQ_MAXVALUE PG_INT64_MAX
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 41ceb87..b3a392f 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -253,3 +253,70 @@ DROP TABLE as_select1;
-- check that the oid column is added before the primary key is checked
CREATE TABLE oid_pk (f1 INT, PRIMARY KEY(oid)) WITH OIDS;
DROP TABLE oid_pk;
+--
+-- partitioned tables
+--
+-- cannot use system columns in partition key
+CREATE TABLE fail_oid_pk (
+ a int
+) PARTITION BY RANGE (oid) WITH OIDS; -- fail
+ERROR: cannot use system column "oid" in partition key
+-- cannot use more than 1 column as partition key for list partitioned table
+CREATE TABLE two_col_tab (
+ a1 int,
+ a2 int
+) PARTITION BY LIST (a1, a2); -- fail
+ERROR: cannot use more than one column in partition key
+DETAIL: Only one column allowed with list partitioning.
+-- Partitioning requires to use columns with data type having a suitable
+-- btree operator class
+CREATE TABLE point_tab (
+ a point
+) PARTITION BY LIST (a); -- fail
+ERROR: data type point has no default btree operator class
+HINT: You must specify an existing btree operator class or define one for the type.
+CREATE TABLE point_tab (
+ a point
+) PARTITION BY LIST (a point_ops); -- fail
+ERROR: operator class "point_ops" does not exist for access method "btree"
+CREATE TABLE point_tab (
+ a point
+) PARTITION BY RANGE (a); -- fail
+ERROR: data type point has no default btree operator class
+HINT: You must specify an existing btree operator class or define one for the type.
+CREATE TABLE point_tab (
+ a point
+) PARTITION BY RANGE (a point_ops); -- fail
+ERROR: operator class "point_ops" does not exist for access method "btree"
+-- fail to create a partitioned table with PRIMARY KEY constraint
+CREATE TABLE pk_fail (
+ a int PRIMARY KEY
+) PARTITION BY RANGE (a);
+ERROR: primary key constraints are not supported on partitioned tables
+LINE 2: a int PRIMARY KEY
+ ^
+-- fail to create a partitioned table with foreign key constraint
+CREATE TABLE pk_rel(
+ a int PRIMARY KEY
+);
+CREATE TABLE fk_fail (
+ a int REFERENCES pk_rel(a)
+) PARTITION BY RANGE (a);
+ERROR: foreign key constraints are not supported on partitioned tables
+LINE 2: a int REFERENCES pk_rel(a)
+ ^
+DROP TABLE pk_rel;
+-- fail to create a partitioned table with prohibited expressions in key
+CREATE TABLE fail_const_key (
+ a int
+) PARTITION BY RANGE (('a'));
+ERROR: cannot use a constant expression as partition key
+CREATE TABLE fail_agg_in_key (
+ a int
+) PARTITION BY RANGE ((avg(a)));
+ERROR: aggregate functions are not allowed in partition key expressions
+CREATE TABLE fail_window_fun_in_key (
+ a int,
+ b int
+) PARTITION BY RANGE ((avg(a) OVER (PARTITION BY b)));
+ERROR: window functions are not allowed in partition key expressions
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 78bdc8b..de20fff 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -269,3 +269,59 @@ DROP TABLE as_select1;
-- check that the oid column is added before the primary key is checked
CREATE TABLE oid_pk (f1 INT, PRIMARY KEY(oid)) WITH OIDS;
DROP TABLE oid_pk;
+
+--
+-- partitioned tables
+--
+
+-- cannot use system columns in partition key
+CREATE TABLE fail_oid_pk (
+ a int
+) PARTITION BY RANGE (oid) WITH OIDS; -- fail
+
+-- cannot use more than 1 column as partition key for list partitioned table
+CREATE TABLE two_col_tab (
+ a1 int,
+ a2 int
+) PARTITION BY LIST (a1, a2); -- fail
+
+-- Partitioning requires to use columns with data type having a suitable
+-- btree operator class
+CREATE TABLE point_tab (
+ a point
+) PARTITION BY LIST (a); -- fail
+CREATE TABLE point_tab (
+ a point
+) PARTITION BY LIST (a point_ops); -- fail
+CREATE TABLE point_tab (
+ a point
+) PARTITION BY RANGE (a); -- fail
+CREATE TABLE point_tab (
+ a point
+) PARTITION BY RANGE (a point_ops); -- fail
+
+-- fail to create a partitioned table with PRIMARY KEY constraint
+CREATE TABLE pk_fail (
+ a int PRIMARY KEY
+) PARTITION BY RANGE (a);
+
+-- fail to create a partitioned table with foreign key constraint
+CREATE TABLE pk_rel(
+ a int PRIMARY KEY
+);
+CREATE TABLE fk_fail (
+ a int REFERENCES pk_rel(a)
+) PARTITION BY RANGE (a);
+DROP TABLE pk_rel;
+
+-- fail to create a partitioned table with prohibited expressions in key
+CREATE TABLE fail_const_key (
+ a int
+) PARTITION BY RANGE (('a'));
+CREATE TABLE fail_agg_in_key (
+ a int
+) PARTITION BY RANGE ((avg(a)));
+CREATE TABLE fail_window_fun_in_key (
+ a int,
+ b int
+) PARTITION BY RANGE ((avg(a) OVER (PARTITION BY b)));
--
1.7.1
[text/x-diff] 0002-Add-a-IGNORE-dependency-type-v6.patch (3.0K, ../../19a3733b-ab58-b993-c858-ec8fc578ddfd@lab.ntt.co.jp/3-0002-Add-a-IGNORE-dependency-type-v6.patch)
download | inline diff:
From 96d7662e7e4eb571cc97db179ecb9601d9e16a99 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Fri, 13 May 2016 12:10:10 +0900
Subject: [PATCH 2/7] Add a IGNORE dependency type.
This new type of dependency is mainly to be used internally as a signal
that no dependency should be created between the objects in question.
Use this, for example, when there is a possibility that redundant self-
dependencies could be created in certain situations - imagine creation
of dependencies for objects referenced in expressions in the partition
key of a table; it is inevitable that vars of the table in question
would be encountered which must be ignored.
---
src/backend/catalog/dependency.c | 2 ++
src/backend/catalog/pg_depend.c | 3 +++
src/include/catalog/dependency.h | 8 +++++++-
3 files changed, 12 insertions(+), 1 deletions(-)
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index 04d7840..607274d 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -717,6 +717,7 @@ findDependentObjects(const ObjectAddress *object,
getObjectDescription(object));
break;
default:
+ Assert(foundDep->deptype != DEPENDENCY_IGNORE);
elog(ERROR, "unrecognized dependency type '%c' for %s",
foundDep->deptype, getObjectDescription(object));
break;
@@ -813,6 +814,7 @@ findDependentObjects(const ObjectAddress *object,
subflags = 0; /* keep compiler quiet */
break;
default:
+ Assert(foundDep->deptype != DEPENDENCY_IGNORE);
elog(ERROR, "unrecognized dependency type '%c' for %s",
foundDep->deptype, getObjectDescription(object));
subflags = 0; /* keep compiler quiet */
diff --git a/src/backend/catalog/pg_depend.c b/src/backend/catalog/pg_depend.c
index 7a0713e..6e71b44 100644
--- a/src/backend/catalog/pg_depend.c
+++ b/src/backend/catalog/pg_depend.c
@@ -65,6 +65,9 @@ recordMultipleDependencies(const ObjectAddress *depender,
bool nulls[Natts_pg_depend];
Datum values[Natts_pg_depend];
+ if (behavior == DEPENDENCY_IGNORE)
+ return; /* nothing to do */
+
if (nreferenced <= 0)
return; /* nothing to do */
diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h
index 09b36c5..d44edc6 100644
--- a/src/include/catalog/dependency.h
+++ b/src/include/catalog/dependency.h
@@ -67,6 +67,11 @@
* created only during initdb. The fields for the dependent object
* contain zeroes.
*
+ * DEPENDENCY_IGNORE ('g'): like DEPENDENCY_PIN, there is no dependent
+ * object; this type of entry is a signal that no dependency should be
+ * created between the objects in question. However, unlike pin,
+ * dependencies, these never make it to pg_depend.
+ *
* Other dependency flavors may be needed in future.
*/
@@ -77,7 +82,8 @@ typedef enum DependencyType
DEPENDENCY_INTERNAL = 'i',
DEPENDENCY_EXTENSION = 'e',
DEPENDENCY_AUTO_EXTENSION = 'x',
- DEPENDENCY_PIN = 'p'
+ DEPENDENCY_PIN = 'p',
+ DEPENDENCY_IGNORE = 'g'
} DependencyType;
/*
--
1.7.1
[text/x-diff] 0003-Infrastructure-for-creation-of-partitioned-tables-v6.patch (87.0K, ../../19a3733b-ab58-b993-c858-ec8fc578ddfd@lab.ntt.co.jp/4-0003-Infrastructure-for-creation-of-partitioned-tables-v6.patch)
download | inline diff:
From 9e2c097cf592fd4789fd8123cb2d2724f2547652 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Mon, 22 Feb 2016 17:38:55 +0900
Subject: [PATCH 3/7] Infrastructure for creation of partitioned tables.
First, this adds a new system catalog pg_partitioned with:
partedrelid oid
partstrat char
partnatts int16
partattrs int2vector
partclass oidvector
partexprs pg_node_tree
There is a unique index on partedrelid and associated syscache PARTEDRELID.
Second, a new relkind RELKIND_PARTITIONED_REL is added to pg_class. Tables
created with CREATE TABLE PARTITION BY are of this relkind. Some special
properties:
* Has a pg_partitioned entry
* Access to them unconditionally overrides specified inheritance option
* Dropping or altering type of columns that are part of the partition key
is disallowed
* Changing inheritance (ALTER TABLE table INHERIT parent) and linking to
a type (ALTER TABLE table OF type) is disallowed
Partition key is represented using struct PartitionKey and built during the
first relcache build of a partitioned relation. It is stored in rd_partkey
using the cache context.
---
doc/src/sgml/catalogs.sgml | 99 ++++++
src/backend/access/common/reloptions.c | 2 +
src/backend/catalog/Makefile | 4 +-
src/backend/catalog/aclchk.c | 2 +
src/backend/catalog/heap.c | 19 +-
src/backend/catalog/objectaddress.c | 10 +-
src/backend/catalog/partition.c | 450 ++++++++++++++++++++++++++++
src/backend/commands/analyze.c | 2 +
src/backend/commands/copy.c | 6 +
src/backend/commands/indexcmds.c | 3 +-
src/backend/commands/lockcmds.c | 2 +-
src/backend/commands/policy.c | 2 +-
src/backend/commands/seclabel.c | 1 +
src/backend/commands/sequence.c | 1 +
src/backend/commands/tablecmds.c | 171 ++++++++++-
src/backend/commands/trigger.c | 7 +-
src/backend/commands/vacuum.c | 1 +
src/backend/executor/execMain.c | 2 +
src/backend/executor/nodeModifyTable.c | 1 +
src/backend/parser/parse_agg.c | 6 +-
src/backend/parser/parse_clause.c | 8 +
src/backend/parser/parse_expr.c | 2 +-
src/backend/parser/parse_relation.c | 6 +-
src/backend/parser/parse_utilcmd.c | 4 +-
src/backend/rewrite/rewriteDefine.c | 1 +
src/backend/rewrite/rewriteHandler.c | 1 +
src/backend/tcop/utility.c | 5 +-
src/backend/utils/adt/ruleutils.c | 147 +++++++++
src/backend/utils/cache/relcache.c | 14 +-
src/backend/utils/cache/syscache.c | 12 +
src/bin/pg_dump/pg_dump.c | 36 ++-
src/bin/pg_dump/pg_dump.h | 1 +
src/bin/psql/describe.c | 61 +++-
src/bin/psql/tab-complete.c | 6 +-
src/include/catalog/indexing.h | 3 +
src/include/catalog/partition.h | 55 ++++
src/include/catalog/pg_class.h | 1 +
src/include/catalog/pg_partitioned.h | 67 ++++
src/include/catalog/pg_proc.h | 2 +
src/include/commands/defrem.h | 4 +-
src/include/utils/builtins.h | 1 +
src/include/utils/rel.h | 8 +
src/include/utils/syscache.h | 1 +
src/test/regress/expected/create_table.out | 30 ++-
src/test/regress/expected/sanity_check.out | 1 +
src/test/regress/sql/create_table.sql | 21 ++
46 files changed, 1231 insertions(+), 58 deletions(-)
create mode 100644 src/backend/catalog/partition.c
create mode 100644 src/include/catalog/partition.h
create mode 100644 src/include/catalog/pg_partitioned.h
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 7dc7d1f..1c4e789 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -226,6 +226,11 @@
</row>
<row>
+ <entry><link linkend="catalog-pg-partitioned"><structname>pg_partitioned</structname></link></entry>
+ <entry>information about partitioned tables, including the partition key</entry>
+ </row>
+
+ <row>
<entry><link linkend="catalog-pg-policy"><structname>pg_policy</structname></link></entry>
<entry>row-security policies</entry>
</row>
@@ -4674,6 +4679,100 @@
</sect1>
+ <sect1 id="catalog-pg-partitioned">
+ <title><structname>pg_partitioned</structname></title>
+
+ <indexterm zone="catalog-pg-partitioned">
+ <primary>pg_partitioned</primary>
+ </indexterm>
+
+ <para>
+ The catalog <structname>pg_partitioned</structname> stores information
+ about the partition key of partitioned tables.
+ </para>
+
+ <table>
+ <title><structname>pg_partitioned</> Columns</title>
+
+ <tgroup cols="4">
+ <thead>
+ <row>
+ <entry>Name</entry>
+ <entry>Type</entry>
+ <entry>References</entry>
+ <entry>Description</entry>
+ </row>
+ </thead>
+
+ <tbody>
+
+ <row>
+ <entry><structfield>partedrelid</structfield></entry>
+ <entry><type>oid</type></entry>
+ <entry><literal><link linkend="catalog-pg-class"><structname>pg_class</structname></link>.oid</literal></entry>
+ <entry>The OID of the <structname>pg_class</> entry for this partitioned table</entry>
+ </row>
+
+ <row>
+ <entry><structfield>partstrat</structfield></entry>
+ <entry><type>char</type></entry>
+ <entry></entry>
+ <entry>
+ Partitioning strategy (or method); <literal>l</> = list partitioned table,
+ <literal>r</> = range partitioned table
+ </entry>
+ </row>
+
+ <row>
+ <entry><structfield>partnatts</structfield></entry>
+ <entry><type>int2</type></entry>
+ <entry></entry>
+ <entry>The number of columns in partition key</entry>
+ </row>
+
+ <row>
+ <entry><structfield>partattrs</structfield></entry>
+ <entry><type>int2vector</type></entry>
+ <entry><literal><link linkend="catalog-pg-attribute"><structname>pg_attribute</structname></link>.attnum</literal></entry>
+ <entry>
+ This is an array of <structfield>partnatts</structfield> values that
+ indicate which table columns are used as partition key. For example,
+ a value of <literal>1 3</literal> would mean that the first and the
+ third table columns make up the partition key. A zero in this array
+ indicates that the corresponding partition key column is an expression
+ over the table columns, rather than a simple column reference.
+ </entry>
+ </row>
+
+ <row>
+ <entry><structfield>partclass</structfield></entry>
+ <entry><type>oidvector</type></entry>
+ <entry><literal><link linkend="catalog-pg-opclass"><structname>pg_opclass</structname></link>.oid</literal></entry>
+ <entry>
+ For each column in the partition key, this contains the OID of
+ the operator class to use. See
+ <link linkend="catalog-pg-opclass"><structname>pg_opclass</structname></link> for details.
+ </entry>
+ </row>
+
+ <row>
+ <entry><structfield>partexprs</structfield></entry>
+ <entry><type>pg_node_tree</type></entry>
+ <entry></entry>
+ <entry>
+ Expression trees (in <function>nodeToString()</function>
+ representation) for partition key columns that are not simple column
+ references. This is a list with one element for each zero
+ entry in <structfield>partkey</>. Null if all partition key columns
+ are simple references.
+ </entry>
+ </row>
+
+ </tbody>
+ </tgroup>
+ </table>
+ </sect1>
+
<sect1 id="catalog-pg-policy">
<title><structname>pg_policy</structname></title>
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 7448c7f..5a6777a 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -925,6 +925,7 @@ extractRelOptions(HeapTuple tuple, TupleDesc tupdesc,
case RELKIND_RELATION:
case RELKIND_TOASTVALUE:
case RELKIND_MATVIEW:
+ case RELKIND_PARTITIONED_REL:
options = heap_reloptions(classForm->relkind, datum, false);
break;
case RELKIND_VIEW:
@@ -1376,6 +1377,7 @@ heap_reloptions(char relkind, Datum reloptions, bool validate)
return (bytea *) rdopts;
case RELKIND_RELATION:
case RELKIND_MATVIEW:
+ case RELKIND_PARTITIONED_REL:
return default_reloptions(reloptions, validate, RELOPT_KIND_HEAP);
default:
/* other relkinds are not supported */
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index 1ce7610..4dc6300 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -11,7 +11,7 @@ top_builddir = ../../..
include $(top_builddir)/src/Makefile.global
OBJS = catalog.o dependency.o heap.o index.o indexing.o namespace.o aclchk.o \
- objectaccess.o objectaddress.o pg_aggregate.o pg_collation.o \
+ objectaccess.o objectaddress.o partition.o pg_aggregate.o pg_collation.o \
pg_constraint.o pg_conversion.o \
pg_depend.o pg_enum.o pg_inherits.o pg_largeobject.o pg_namespace.o \
pg_operator.o pg_proc.o pg_range.o pg_db_role_setting.o pg_shdepend.o \
@@ -41,7 +41,7 @@ POSTGRES_BKI_SRCS = $(addprefix $(top_srcdir)/src/include/catalog/,\
pg_foreign_data_wrapper.h pg_foreign_server.h pg_user_mapping.h \
pg_foreign_table.h pg_policy.h pg_replication_origin.h \
pg_default_acl.h pg_init_privs.h pg_seclabel.h pg_shseclabel.h \
- pg_collation.h pg_range.h pg_transform.h \
+ pg_collation.h pg_range.h pg_transform.h pg_partitioned.h\
toasting.h indexing.h \
)
diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
index d074e85..1818506 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -762,6 +762,8 @@ objectsInSchemaToOids(GrantObjectType objtype, List *nspnames)
case ACL_OBJECT_RELATION:
objs = getRelationsInNamespace(namespaceId, RELKIND_RELATION);
objects = list_concat(objects, objs);
+ objs = getRelationsInNamespace(namespaceId, RELKIND_PARTITIONED_REL);
+ objects = list_concat(objects, objs);
objs = getRelationsInNamespace(namespaceId, RELKIND_VIEW);
objects = list_concat(objects, objs);
objs = getRelationsInNamespace(namespaceId, RELKIND_MATVIEW);
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index e997b57..932a48b 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -41,6 +41,7 @@
#include "catalog/heap.h"
#include "catalog/index.h"
#include "catalog/objectaccess.h"
+#include "catalog/partition.h"
#include "catalog/pg_attrdef.h"
#include "catalog/pg_collation.h"
#include "catalog/pg_constraint.h"
@@ -1101,9 +1102,10 @@ heap_create_with_catalog(const char *relname,
{
/* Use binary-upgrade override for pg_class.oid/relfilenode? */
if (IsBinaryUpgrade &&
- (relkind == RELKIND_RELATION || relkind == RELKIND_SEQUENCE ||
- relkind == RELKIND_VIEW || relkind == RELKIND_MATVIEW ||
- relkind == RELKIND_COMPOSITE_TYPE || relkind == RELKIND_FOREIGN_TABLE))
+ (relkind == RELKIND_RELATION || relkind == RELKIND_PARTITIONED_REL ||
+ relkind == RELKIND_SEQUENCE || relkind == RELKIND_VIEW ||
+ relkind == RELKIND_MATVIEW || relkind == RELKIND_COMPOSITE_TYPE ||
+ relkind == RELKIND_FOREIGN_TABLE))
{
if (!OidIsValid(binary_upgrade_next_heap_pg_class_oid))
ereport(ERROR,
@@ -1134,6 +1136,7 @@ heap_create_with_catalog(const char *relname,
switch (relkind)
{
case RELKIND_RELATION:
+ case RELKIND_PARTITIONED_REL:
case RELKIND_VIEW:
case RELKIND_MATVIEW:
case RELKIND_FOREIGN_TABLE:
@@ -1178,6 +1181,7 @@ heap_create_with_catalog(const char *relname,
* such is an implementation detail: toast tables, sequences and indexes.
*/
if (IsUnderPostmaster && (relkind == RELKIND_RELATION ||
+ relkind == RELKIND_PARTITIONED_REL ||
relkind == RELKIND_VIEW ||
relkind == RELKIND_MATVIEW ||
relkind == RELKIND_FOREIGN_TABLE ||
@@ -1353,7 +1357,8 @@ heap_create_with_catalog(const char *relname,
if (relpersistence == RELPERSISTENCE_UNLOGGED)
{
Assert(relkind == RELKIND_RELATION || relkind == RELKIND_MATVIEW ||
- relkind == RELKIND_TOASTVALUE);
+ relkind == RELKIND_TOASTVALUE || relkind == RELKIND_PARTITIONED_REL);
+
heap_create_init_fork(new_rel_desc);
}
@@ -1800,6 +1805,12 @@ heap_drop_with_catalog(Oid relid)
}
/*
+ * If a partitioned table, delete the pg_partitioned tuples.
+ */
+ if (rel->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+ RemovePartitionKeyByRelId(relid);
+
+ /*
* Schedule unlinking of the relation's physical files at commit.
*/
if (rel->rd_rel->relkind != RELKIND_VIEW &&
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index 1324461..215375e 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -1204,7 +1204,8 @@ get_relation_by_qualified_name(ObjectType objtype, List *objname,
RelationGetRelationName(relation))));
break;
case OBJECT_TABLE:
- if (relation->rd_rel->relkind != RELKIND_RELATION)
+ if (relation->rd_rel->relkind != RELKIND_RELATION &&
+ relation->rd_rel->relkind != RELKIND_PARTITIONED_REL)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not a table",
@@ -3252,6 +3253,10 @@ getRelationDescription(StringInfo buffer, Oid relid)
appendStringInfo(buffer, _("table %s"),
relname);
break;
+ case RELKIND_PARTITIONED_REL:
+ appendStringInfo(buffer, _("partitioned table %s"),
+ relname);
+ break;
case RELKIND_INDEX:
appendStringInfo(buffer, _("index %s"),
relname);
@@ -3708,6 +3713,9 @@ getRelationTypeDescription(StringInfo buffer, Oid relid, int32 objectSubId)
case RELKIND_RELATION:
appendStringInfoString(buffer, "table");
break;
+ case RELKIND_PARTITIONED_REL:
+ appendStringInfoString(buffer, "partitioned table");
+ break;
case RELKIND_INDEX:
appendStringInfoString(buffer, "index");
break;
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
new file mode 100644
index 0000000..210a633
--- /dev/null
+++ b/src/backend/catalog/partition.c
@@ -0,0 +1,450 @@
+/*-------------------------------------------------------------------------
+ *
+ * partition.c
+ * Partitioning related utility functions.
+ *
+ * Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ * src/backend/utils/misc/partition.c
+ *
+ *-------------------------------------------------------------------------
+*/
+
+#include "postgres.h"
+
+#include "access/heapam.h"
+#include "access/htup_details.h"
+#include "access/nbtree.h"
+#include "catalog/dependency.h"
+#include "catalog/indexing.h"
+#include "catalog/objectaddress.h"
+#include "catalog/partition.h"
+#include "catalog/pg_collation.h"
+#include "catalog/pg_opclass.h"
+#include "catalog/pg_partitioned.h"
+#include "catalog/pg_type.h"
+#include "executor/executor.h"
+#include "miscadmin.h"
+#include "nodes/nodeFuncs.h"
+#include "optimizer/clauses.h"
+#include "optimizer/planmain.h"
+#include "storage/lmgr.h"
+#include "utils/array.h"
+#include "utils/builtins.h"
+#include "utils/datum.h"
+#include "utils/memutils.h"
+#include "utils/fmgroids.h"
+#include "utils/inval.h"
+#include "utils/lsyscache.h"
+#include "utils/syscache.h"
+
+static PartitionKey CopyPartitionKey(PartitionKey fromkey);
+static KeyTypeCollInfo *copy_key_type_coll_info(int nkeycols,
+ KeyTypeCollInfo *tcinfo);
+static void free_key_type_coll_info(KeyTypeCollInfo *tcinfo);
+
+/*
+ * StorePartitionKey
+ * Store the partition keys of rel into pg_partitioned catalog
+ */
+void
+StorePartitionKey(Relation rel,
+ char strategy,
+ int16 partnatts,
+ AttrNumber *partattrs,
+ List *partexprs,
+ Oid *partopclass)
+{
+ int i;
+ int2vector *partattrs_vec;
+ oidvector *partopclass_vec;
+ Datum partexprsDatum;
+ Relation pg_partitioned;
+ HeapTuple tuple;
+ Datum values[Natts_pg_partitioned];
+ bool nulls[Natts_pg_partitioned];
+ ObjectAddress myself;
+ ObjectAddress referenced;
+
+ Assert(rel->rd_rel->relkind == RELKIND_PARTITIONED_REL);
+
+ tuple = SearchSysCache1(PARTEDRELID,
+ ObjectIdGetDatum(RelationGetRelid(rel)));
+ /* Cannot already exist */
+ Assert(!HeapTupleIsValid(tuple));
+
+ /*
+ * Copy the partition key, opclass info into arrays (should we
+ * make the caller pass them like this to start with?)
+ */
+ partattrs_vec = buildint2vector(partattrs, partnatts);
+ partopclass_vec = buildoidvector(partopclass, partnatts);
+
+ /* Convert the partition key expressions (if any) to a text datum */
+ if (partexprs)
+ {
+ char *exprsString;
+
+ exprsString = nodeToString(partexprs);
+ partexprsDatum = CStringGetTextDatum(exprsString);
+ pfree(exprsString);
+ }
+ else
+ partexprsDatum = (Datum) 0;
+
+ pg_partitioned = heap_open(PartitionedRelationId, RowExclusiveLock);
+
+ MemSet(nulls, false, sizeof(nulls));
+
+ /* Only this can ever be NULL */
+ if (!partexprsDatum)
+ nulls[Anum_pg_partitioned_partexprs - 1] = true;
+
+ values[Anum_pg_partitioned_partedrelid - 1] = ObjectIdGetDatum(RelationGetRelid(rel));
+ values[Anum_pg_partitioned_partstrat - 1] = CharGetDatum(strategy);
+ values[Anum_pg_partitioned_partnatts - 1] = Int16GetDatum(partnatts);
+ values[Anum_pg_partitioned_partattrs - 1] = PointerGetDatum(partattrs_vec);
+ values[Anum_pg_partitioned_partclass - 1] = PointerGetDatum(partopclass_vec);
+ values[Anum_pg_partitioned_partexprs - 1] = partexprsDatum;
+
+ tuple = heap_form_tuple(RelationGetDescr(pg_partitioned), values, nulls);
+
+ simple_heap_insert(pg_partitioned, tuple);
+
+ /* Update the indexes on pg_partitioned */
+ CatalogUpdateIndexes(pg_partitioned, tuple);
+
+ /* Make this relation dependent on a few things: */
+ myself.classId = RelationRelationId;
+ myself.objectId = RelationGetRelid(rel);;
+ myself.objectSubId = 0;
+
+ /* Operator class per key column */
+ for (i = 0; i < partnatts; i++)
+ {
+ referenced.classId = OperatorClassRelationId;
+ referenced.objectId = partopclass[i];
+ referenced.objectSubId = 0;
+
+ recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+ }
+
+ /*
+ * Things inside key expressions ignoring the vars of the table which
+ * is achieved by asking the dependency-tracking system to ignore any
+ * dependencies to self (table) or more appropriately own attributes.
+ */
+ if (partexprs)
+ recordDependencyOnSingleRelExpr(&myself,
+ (Node *) partexprs,
+ RelationGetRelid(rel),
+ DEPENDENCY_NORMAL,
+ DEPENDENCY_IGNORE);
+ /* Tell world about the key */
+ CacheInvalidateRelcache(rel);
+
+ heap_close(pg_partitioned, RowExclusiveLock);
+ heap_freetuple(tuple);
+}
+
+/*
+ * RemovePartitionKeyByRelId
+ * Remove pg_partitioned entry for a relation
+ */
+void
+RemovePartitionKeyByRelId(Oid relid)
+{
+ Relation rel;
+ HeapTuple tuple;
+
+ rel = heap_open(PartitionedRelationId, RowExclusiveLock);
+
+ tuple = SearchSysCache1(PARTEDRELID, ObjectIdGetDatum(relid));
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "cache lookup failed for partition key of relation %u", relid);
+
+ simple_heap_delete(rel, &tuple->t_self);
+
+ /* Update the indexes on pg_partitioned */
+ CatalogUpdateIndexes(rel, tuple);
+
+ ReleaseSysCache(tuple);
+ heap_close(rel, RowExclusiveLock);
+}
+
+/*
+ * RelationBuildPartitionKey
+ * Build and attach to relcache partition key data of relation
+ *
+ * Note that the partition key data attached to a relcache entry must be
+ * stored CacheMemoryContext to ensure it survives as long as the relcache
+ * entry. But we should be running in a less long-lived working context.
+ * To avoid leaking cache memory if this routine fails partway through,
+ * we build in working memory and then copy the completed structure into
+ * cache memory.
+ */
+void
+RelationBuildPartitionKey(Relation relation)
+{
+ Form_pg_partitioned form;
+ Relation catalog;
+ HeapTuple tuple;
+ bool isnull;
+ int i;
+ PartitionKey key;
+ int2vector *partattrs;
+ oidvector *opclass;
+ KeyTypeCollInfo *tcinfo;
+ ListCell *partexprs_item;
+ Datum datum;
+ MemoryContext oldcxt;
+
+ tuple = SearchSysCache1(PARTEDRELID,
+ ObjectIdGetDatum(RelationGetRelid(relation)));
+ /*
+ * Happens when we have created our pg_class entry but not the
+ * pg_partitioned entry yet.
+ */
+ if (!HeapTupleIsValid(tuple))
+ return;
+
+ form = (Form_pg_partitioned) GETSTRUCT(tuple);
+
+ /* Allocate in the hopefully short-lived working context */
+ key = (PartitionKey) palloc0(sizeof(PartitionKeyData));
+ key->strategy = form->partstrat;
+ key->partnatts = form->partnatts;
+
+ /* Open the catalog for its tuple descriptor */
+ catalog = heap_open(PartitionedRelationId, AccessShareLock);
+ datum = fastgetattr(tuple, Anum_pg_partitioned_partattrs,
+ RelationGetDescr(catalog),
+ &isnull);
+ Assert(!isnull);
+ partattrs = (int2vector *) DatumGetPointer(datum);
+
+ datum = fastgetattr(tuple, Anum_pg_partitioned_partclass,
+ RelationGetDescr(catalog),
+ &isnull);
+ Assert(!isnull);
+ opclass = (oidvector *) DatumGetPointer(datum);
+
+ datum = heap_getattr(tuple,
+ Anum_pg_partitioned_partexprs,
+ RelationGetDescr(catalog),
+ &isnull);
+
+ if (!isnull)
+ {
+ char *exprsString;
+ Node *exprs;
+
+ exprsString = TextDatumGetCString(datum);
+ exprs = stringToNode(exprsString);
+ pfree(exprsString);
+
+ /*
+ * Run the expressions through eval_const_expressions. This is
+ * not just an optimization, but is necessary, because eventually
+ * the planner will be comparing them to similarly-processed qual
+ * clauses, and may fail to detect valid matches without this.
+ * We don't bother with canonicalize_qual, however.
+ */
+ exprs = eval_const_expressions(NULL, (Node *) exprs);
+
+ /* May as well fix opfuncids too */
+ fix_opfuncids((Node *) exprs);
+ key->partexprs = (List *) exprs;
+ }
+
+ key->partattrs = (AttrNumber *) palloc0(key->partnatts * sizeof(AttrNumber));
+ key->partopfamily = (Oid *) palloc0(key->partnatts * sizeof(Oid));
+ key->partsupfunc = (FmgrInfo *) palloc0(key->partnatts * sizeof(FmgrInfo));
+
+ /* Gather type and collation info as well */
+ key->tcinfo = tcinfo = (KeyTypeCollInfo *) palloc0(sizeof(KeyTypeCollInfo));
+ tcinfo->typid = (Oid *) palloc0(key->partnatts * sizeof(Oid));
+ tcinfo->typmod = (int32 *) palloc0(key->partnatts * sizeof(int32));
+ tcinfo->typlen = (int16 *) palloc0(key->partnatts * sizeof(int16));
+ tcinfo->typbyval = (bool *) palloc0(key->partnatts * sizeof(bool));
+ tcinfo->typalign = (char *) palloc0(key->partnatts * sizeof(char));
+ tcinfo->typcoll = (Oid *) palloc0(key->partnatts * sizeof(Oid));
+
+ /*
+ * Copy partattrs. Further, determine and store the opfamily and
+ * btree support function per attribute.
+ */
+ partexprs_item = list_head(key->partexprs);
+ for (i = 0; i < key->partnatts; i++)
+ {
+ HeapTuple tuple;
+ AttrNumber attno;
+ Form_pg_opclass form;
+ Oid funcid;
+
+ key->partattrs[i] = attno = partattrs->values[i];
+
+ /* Collect type information */
+ if (attno != InvalidAttrNumber)
+ {
+ tcinfo->typid[i] = relation->rd_att->attrs[attno - 1]->atttypid;
+ tcinfo->typmod[i] = relation->rd_att->attrs[attno - 1]->atttypmod;
+ tcinfo->typcoll[i] = relation->rd_att->attrs[attno - 1]->attcollation;
+ }
+ else
+ {
+ tcinfo->typid[i] = exprType(lfirst(partexprs_item));
+ tcinfo->typmod[i] = exprTypmod(lfirst(partexprs_item));
+ tcinfo->typcoll[i] = exprCollation(lfirst(partexprs_item));
+ partexprs_item = lnext(partexprs_item);
+ }
+ get_typlenbyvalalign(tcinfo->typid[i],
+ &tcinfo->typlen[i],
+ &tcinfo->typbyval[i],
+ &tcinfo->typalign[i]);
+
+ tuple = SearchSysCache1(CLAOID, ObjectIdGetDatum(opclass->values[i]));
+
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "cache lookup failed for opclass %u", opclass->values[i]);
+
+ form = (Form_pg_opclass) GETSTRUCT(tuple);
+ key->partopfamily[i] = form->opcfamily;
+
+ /*
+ * Btree support function is good to cover the list and range
+ * partitionig related needs.
+ */
+ funcid = get_opfamily_proc(form->opcfamily,
+ form->opcintype, form->opcintype,
+ BTORDER_PROC);
+
+ fmgr_info(funcid, &key->partsupfunc[i]);
+ ReleaseSysCache(tuple);
+ }
+
+ ReleaseSysCache(tuple);
+ heap_close(catalog, AccessShareLock);
+
+ /* Success --- now copy to the cache memory */
+ oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
+ relation->rd_partkey = CopyPartitionKey(key);
+ MemoryContextSwitchTo(oldcxt);
+
+ FreePartitionKey(key);
+}
+
+/*
+ * CopyPartitionKey
+ *
+ * The copy is allocated in the current memory context.
+ */
+static PartitionKey
+CopyPartitionKey(PartitionKey fromkey)
+{
+ PartitionKey newkey;
+
+ newkey = (PartitionKey) palloc0(sizeof(PartitionKeyData));
+
+ newkey->strategy = fromkey->strategy;
+ newkey->partnatts = fromkey->partnatts;
+
+ newkey->partattrs = (AttrNumber *)
+ palloc(newkey->partnatts * sizeof(AttrNumber));
+ memcpy(newkey->partattrs, fromkey->partattrs,
+ newkey->partnatts * sizeof(AttrNumber));
+
+ newkey->partopfamily = (Oid *) palloc(newkey->partnatts * sizeof(Oid));
+ memcpy(newkey->partopfamily, fromkey->partopfamily,
+ newkey->partnatts * sizeof(Oid));
+
+ newkey->partsupfunc = (FmgrInfo *)
+ palloc(newkey->partnatts * sizeof(FmgrInfo));
+ memcpy(newkey->partsupfunc, fromkey->partsupfunc,
+ newkey->partnatts * sizeof(FmgrInfo));
+
+ newkey->partexprs = copyObject(fromkey->partexprs);
+ newkey->tcinfo = copy_key_type_coll_info(newkey->partnatts,
+ fromkey->tcinfo);
+
+ return newkey;
+}
+
+/*
+ * copy_key_type_coll_info
+ *
+ * The copy is allocated in the current memory context.
+ */
+static KeyTypeCollInfo *
+copy_key_type_coll_info(int nkeycols, KeyTypeCollInfo *tcinfo)
+{
+ KeyTypeCollInfo *result = (KeyTypeCollInfo *)
+ palloc0(sizeof(KeyTypeCollInfo));
+
+ result->typid = (Oid *) palloc0(nkeycols * sizeof(Oid));
+ memcpy(result->typid, tcinfo->typid, nkeycols * sizeof(Oid));
+
+ result->typmod = (int32 *) palloc0(nkeycols * sizeof(int32));
+ memcpy(result->typmod, tcinfo->typmod, nkeycols * sizeof(int32));
+
+ result->typlen = (int16 *) palloc0(nkeycols * sizeof(int16));
+ memcpy(result->typlen, tcinfo->typlen, nkeycols * sizeof(int16));
+
+ result->typbyval = (bool *) palloc0(nkeycols * sizeof(bool));
+ memcpy(result->typbyval, tcinfo->typbyval, nkeycols * sizeof(bool));
+
+ result->typalign = (char *) palloc0(nkeycols * sizeof(bool));
+ memcpy(result->typalign, tcinfo->typalign, nkeycols * sizeof(char));
+
+ result->typcoll = (Oid *) palloc0(nkeycols * sizeof(Oid));
+ memcpy(result->typcoll, tcinfo->typcoll, nkeycols * sizeof(Oid));
+
+ return result;
+}
+
+/*
+ * free_key_type_info
+ */
+static void
+free_key_type_coll_info(KeyTypeCollInfo *tcinfo)
+{
+ Assert(tcinfo != NULL);
+
+ pfree(tcinfo->typid);
+ pfree(tcinfo->typmod);
+ pfree(tcinfo->typlen);
+ pfree(tcinfo->typbyval);
+ pfree(tcinfo->typalign);
+ pfree(tcinfo->typcoll);
+ pfree(tcinfo);
+}
+
+/*
+ * Does relid have a pg_partitioned entry?
+ */
+bool
+relid_is_partitioned(Oid relid)
+{
+ return SearchSysCacheExists1(PARTEDRELID, ObjectIdGetDatum(relid));
+}
+
+/*
+ * FreePartitionKeys
+ */
+void
+FreePartitionKey(PartitionKey key)
+{
+ if (key == NULL)
+ return;
+
+ pfree(key->partattrs);
+ pfree(key->partopfamily);
+ pfree(key->partsupfunc);
+ if (key->partexprs)
+ pfree(key->partexprs);
+ free_key_type_coll_info(key->tcinfo);
+ pfree(key);
+}
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index 97059e5..ede0df2 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -201,6 +201,7 @@ analyze_rel(Oid relid, RangeVar *relation, int options,
* locked the relation.
*/
if (onerel->rd_rel->relkind == RELKIND_RELATION ||
+ onerel->rd_rel->relkind == RELKIND_PARTITIONED_REL ||
onerel->rd_rel->relkind == RELKIND_MATVIEW)
{
/* Regular table, so we'll use the regular row acquisition function */
@@ -1323,6 +1324,7 @@ acquire_inherited_sample_rows(Relation onerel, int elevel,
/* Check table type (MATVIEW can't happen, but might as well allow) */
if (childrel->rd_rel->relkind == RELKIND_RELATION ||
+ childrel->rd_rel->relkind == RELKIND_PARTITIONED_REL ||
childrel->rd_rel->relkind == RELKIND_MATVIEW)
{
/* Regular table, so use the regular row acquisition function */
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 28dcd34..7623aaf 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -1718,6 +1718,12 @@ BeginCopyTo(Relation rel,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot copy from sequence \"%s\"",
RelationGetRelationName(rel))));
+ else if (rel->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot copy from partitioned table \"%s\"",
+ RelationGetRelationName(rel)),
+ errhint("Try the COPY (SELECT ...) TO variant.")));
else
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 4ef6b80..e8d25e8 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -369,7 +369,8 @@ DefineIndex(Oid relationId,
namespaceId = RelationGetNamespace(rel);
if (rel->rd_rel->relkind != RELKIND_RELATION &&
- rel->rd_rel->relkind != RELKIND_MATVIEW)
+ rel->rd_rel->relkind != RELKIND_MATVIEW &&
+ rel->rd_rel->relkind != RELKIND_PARTITIONED_REL)
{
if (rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
diff --git a/src/backend/commands/lockcmds.c b/src/backend/commands/lockcmds.c
index 175d1f3..62671a8 100644
--- a/src/backend/commands/lockcmds.c
+++ b/src/backend/commands/lockcmds.c
@@ -88,7 +88,7 @@ RangeVarCallbackForLockTable(const RangeVar *rv, Oid relid, Oid oldrelid,
* check */
/* Currently, we only allow plain tables to be locked */
- if (relkind != RELKIND_RELATION)
+ if (relkind != RELKIND_RELATION && relkind != RELKIND_PARTITIONED_REL)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not a table",
diff --git a/src/backend/commands/policy.c b/src/backend/commands/policy.c
index 93d15e4..de75da1 100644
--- a/src/backend/commands/policy.c
+++ b/src/backend/commands/policy.c
@@ -88,7 +88,7 @@ RangeVarCallbackForPolicy(const RangeVar *rv, Oid relid, Oid oldrelid,
rv->relname)));
/* Relation type MUST be a table. */
- if (relkind != RELKIND_RELATION)
+ if (relkind != RELKIND_RELATION && relkind != RELKIND_PARTITIONED_REL)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not a table", rv->relname)));
diff --git a/src/backend/commands/seclabel.c b/src/backend/commands/seclabel.c
index 5bd7e12..8bc8638 100644
--- a/src/backend/commands/seclabel.c
+++ b/src/backend/commands/seclabel.c
@@ -107,6 +107,7 @@ ExecSecLabelStmt(SecLabelStmt *stmt)
* are the only relkinds for which pg_dump will dump labels).
*/
if (relation->rd_rel->relkind != RELKIND_RELATION &&
+ relation->rd_rel->relkind != RELKIND_PARTITIONED_REL &&
relation->rd_rel->relkind != RELKIND_VIEW &&
relation->rd_rel->relkind != RELKIND_MATVIEW &&
relation->rd_rel->relkind != RELKIND_COMPOSITE_TYPE &&
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index c98f981..5dcd4cf 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -1467,6 +1467,7 @@ process_owned_by(Relation seqrel, List *owned_by)
/* Must be a regular or foreign table */
if (!(tablerel->rd_rel->relkind == RELKIND_RELATION ||
+ tablerel->rd_rel->relkind == RELKIND_PARTITIONED_REL ||
tablerel->rd_rel->relkind == RELKIND_FOREIGN_TABLE))
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 7c82f39..c0a9e6a 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -29,6 +29,7 @@
#include "catalog/indexing.h"
#include "catalog/namespace.h"
#include "catalog/objectaccess.h"
+#include "catalog/partition.h"
#include "catalog/pg_am.h"
#include "catalog/pg_collation.h"
#include "catalog/pg_constraint.h"
@@ -262,6 +263,12 @@ struct DropRelationCallbackState
bool concurrent;
};
+/* for find_attr_reference_walker */
+typedef struct
+{
+ AttrNumber attnum;
+} find_attr_reference_context;
+
/* Alter table target-type flags for ATSimplePermissions */
#define ATT_TABLE 0x0001
#define ATT_VIEW 0x0002
@@ -438,6 +445,9 @@ static void ComputePartitionAttrs(Oid relid, List *partParams,
AttrNumber *partattrs,
List **partexprs,
Oid *partoplass);
+static bool find_attr_reference_walker(Node *node,
+ find_attr_reference_context *context);
+static bool is_partition_attr(Relation rel, AttrNumber attnum, bool *is_expr);
/* ----------------------------------------------------------------
@@ -601,7 +611,8 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
* affect other relkinds, but it would complicate interpretOidsOption().
*/
localHasOids = interpretOidsOption(stmt->options,
- (relkind == RELKIND_RELATION));
+ (relkind == RELKIND_RELATION ||
+ relkind == RELKIND_PARTITIONED_REL));
descriptor->tdhasoid = (localHasOids || parentOidCount > 0);
/*
@@ -715,9 +726,10 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
AddRelationNewConstraints(rel, rawDefaults, stmt->constraints,
true, true, false);
- /* Process partition key, if any */
+ /* Process and store partition key, if any */
if (stmt->partby)
{
+ int partnatts;
AttrNumber partattrs[PARTITION_MAX_KEYS];
Oid partopclass[PARTITION_MAX_KEYS];
List *partexprs = NIL;
@@ -725,6 +737,10 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
stmt->partby = transformPartitionBy(rel, stmt->partby);
ComputePartitionAttrs(relationId, stmt->partby->partParams,
partattrs, &partexprs, partopclass);
+
+ partnatts = list_length(stmt->partby->partParams);
+ StorePartitionKey(rel, stmt->partby->strategy, partnatts,
+ partattrs, partexprs, partopclass);
}
ObjectAddressSet(address, RelationRelationId, relationId);
@@ -972,7 +988,15 @@ RangeVarCallbackForDropRelation(const RangeVar *rel, Oid relOid, Oid oldRelOid,
return; /* concurrently dropped, so nothing to do */
classform = (Form_pg_class) GETSTRUCT(tuple);
- if (classform->relkind != relkind)
+ /*
+ * Both normal and partitioned tables are dropped using DROP TABLE.
+ * RemoveRelations however never passes RELKIND_PARTITIONED_REL as
+ * relkind for OBJECT_TABLE relations. A mismatch below may have
+ * to do with that. So, check.
+ */
+ if (classform->relkind != relkind &&
+ (relkind == RELKIND_RELATION &&
+ classform->relkind != RELKIND_PARTITIONED_REL))
DropErrorMsgWrongType(rel->relname, classform->relkind, relkind);
/* Allow DROP to either table owner or schema owner */
@@ -1310,7 +1334,8 @@ truncate_check_rel(Relation rel)
AclResult aclresult;
/* Only allow truncate on regular tables */
- if (rel->rd_rel->relkind != RELKIND_RELATION)
+ if (rel->rd_rel->relkind != RELKIND_RELATION &&
+ rel->rd_rel->relkind != RELKIND_PARTITIONED_REL)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not a table",
@@ -1538,8 +1563,16 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
*/
relation = heap_openrv(parent, ShareUpdateExclusiveLock);
+ /* Cannot inherit from partitioned tables */
+ if (relation->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot inherit from partitioned table \"%s\"",
+ parent->relname)));
+
if (relation->rd_rel->relkind != RELKIND_RELATION &&
- relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
+ relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE &&
+ relation->rd_rel->relkind != RELKIND_PARTITIONED_REL)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("inherited relation \"%s\" is not a table or foreign table",
@@ -2179,6 +2212,7 @@ renameatt_check(Oid myrelid, Form_pg_class classform, bool recursing)
* restriction.
*/
if (relkind != RELKIND_RELATION &&
+ relkind != RELKIND_PARTITIONED_REL &&
relkind != RELKIND_VIEW &&
relkind != RELKIND_MATVIEW &&
relkind != RELKIND_COMPOSITE_TYPE &&
@@ -4308,6 +4342,7 @@ ATSimplePermissions(Relation rel, int allowed_targets)
switch (rel->rd_rel->relkind)
{
case RELKIND_RELATION:
+ case RELKIND_PARTITIONED_REL:
actual_target = ATT_TABLE;
break;
case RELKIND_VIEW:
@@ -4544,6 +4579,7 @@ find_composite_type_dependencies(Oid typeOid, Relation origRelation,
att = rel->rd_att->attrs[pg_depend->objsubid - 1];
if (rel->rd_rel->relkind == RELKIND_RELATION ||
+ rel->rd_rel->relkind == RELKIND_PARTITIONED_REL ||
rel->rd_rel->relkind == RELKIND_MATVIEW)
{
if (origTypeName)
@@ -5434,6 +5470,7 @@ ATPrepSetStatistics(Relation rel, const char *colName, Node *newValue, LOCKMODE
* allowSystemTableMods to be turned on.
*/
if (rel->rd_rel->relkind != RELKIND_RELATION &&
+ rel->rd_rel->relkind != RELKIND_PARTITIONED_REL &&
rel->rd_rel->relkind != RELKIND_MATVIEW &&
rel->rd_rel->relkind != RELKIND_INDEX &&
rel->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
@@ -5708,6 +5745,73 @@ ATPrepDropColumn(List **wqueue, Relation rel, bool recurse, bool recursing,
cmd->subtype = AT_DropColumnRecurse;
}
+/* Checks if a Var node is for a given attnum */
+static bool
+find_attr_reference_walker(Node *node, find_attr_reference_context *context)
+{
+ if (node == NULL)
+ return false;
+
+ if (IsA(node, Var))
+ {
+ Var *variable = (Var *) node;
+ AttrNumber attnum = variable->varattno;
+
+ if (attnum == context->attnum)
+ return true;
+ }
+
+ return expression_tree_walker(node, find_attr_reference_walker, context);
+}
+
+/*
+ * Checks if attnum is a partition attribute for rel
+ *
+ * Sets *is_expr if attnum is found to be referenced in some partition key
+ * expression.
+ */
+static bool
+is_partition_attr(Relation rel, AttrNumber attnum, bool *is_expr)
+{
+ PartitionKey key;
+ ListCell *partexpr_item;
+ int i;
+
+ if (rel->rd_rel->relkind != RELKIND_PARTITIONED_REL)
+ {
+ *is_expr = false;
+ return false;
+ }
+
+ key = RelationGetPartitionKey(rel);
+
+ partexpr_item = list_head(key->partexprs);
+ for (i = 0; i < key->partnatts; i++)
+ {
+ AttrNumber partatt = key->partattrs[i];
+
+ if(partatt != 0)
+ {
+ *is_expr = false;
+ if (attnum == partatt)
+ return true;
+ }
+ else
+ {
+ find_attr_reference_context context;
+
+ *is_expr = true;
+ context.attnum = attnum;
+ if (find_attr_reference_walker(lfirst(partexpr_item), &context))
+ return true;
+
+ partexpr_item = lnext(partexpr_item);
+ }
+ }
+
+ return false;
+}
+
/*
* Return value is the address of the dropped column.
*/
@@ -5722,6 +5826,7 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
AttrNumber attnum;
List *children;
ObjectAddress object;
+ bool is_expr;
/* At top level, permission check was done in ATPrepCmd, else do it */
if (recursing)
@@ -5766,6 +5871,19 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
errmsg("cannot drop inherited column \"%s\"",
colName)));
+ /* Don't drop columns used in partition key */
+ if (is_partition_attr(rel, attnum, &is_expr))
+ {
+ if (!is_expr)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("cannot drop column named in partition key")));
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("cannot drop column referenced in partition key expression")));
+ }
+
ReleaseSysCache(tuple);
/*
@@ -6284,6 +6402,12 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
* Validity checks (permission checks wait till we have the column
* numbers)
*/
+ if (pkrel->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot reference relation \"%s\"", RelationGetRelationName(pkrel)),
+ errdetail("Referencing partitioned tables in foreign key constraints is not supported.")));
+
if (pkrel->rd_rel->relkind != RELKIND_RELATION)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
@@ -7878,6 +8002,7 @@ ATPrepAlterColumnType(List **wqueue,
NewColumnValue *newval;
ParseState *pstate = make_parsestate(NULL);
AclResult aclresult;
+ bool is_expr;
if (rel->rd_rel->reloftype && !recursing)
ereport(ERROR,
@@ -7908,6 +8033,19 @@ ATPrepAlterColumnType(List **wqueue,
errmsg("cannot alter inherited column \"%s\"",
colName)));
+ /* Don't alter columns used in partition key */
+ if (is_partition_attr(rel, attnum, &is_expr))
+ {
+ if (!is_expr)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("cannot alter column named in partition key")));
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("cannot alter column referenced in partition key expression")));
+ }
+
/* Look up the target type */
typenameTypeIdAndMod(NULL, typeName, &targettype, &targettypmod);
@@ -7923,7 +8061,8 @@ ATPrepAlterColumnType(List **wqueue,
list_make1_oid(rel->rd_rel->reltype),
false);
- if (tab->relkind == RELKIND_RELATION)
+ if (tab->relkind == RELKIND_RELATION ||
+ tab->relkind == RELKIND_PARTITIONED_REL)
{
/*
* Set up an expression to transform the old data value to the new
@@ -8950,6 +9089,7 @@ ATExecChangeOwner(Oid relationOid, Oid newOwnerId, bool recursing, LOCKMODE lock
switch (tuple_class->relkind)
{
case RELKIND_RELATION:
+ case RELKIND_PARTITIONED_REL:
case RELKIND_VIEW:
case RELKIND_MATVIEW:
case RELKIND_FOREIGN_TABLE:
@@ -9412,6 +9552,7 @@ ATExecSetRelOptions(Relation rel, List *defList, AlterTableType operation,
switch (rel->rd_rel->relkind)
{
case RELKIND_RELATION:
+ case RELKIND_PARTITIONED_REL:
case RELKIND_TOASTVALUE:
case RELKIND_MATVIEW:
(void) heap_reloptions(rel->rd_rel->relkind, newOptions, true);
@@ -9834,7 +9975,8 @@ AlterTableMoveAll(AlterTableMoveAllStmt *stmt)
/* Only move the object type requested */
if ((stmt->objtype == OBJECT_TABLE &&
- relForm->relkind != RELKIND_RELATION) ||
+ relForm->relkind != RELKIND_RELATION &&
+ relForm->relkind != RELKIND_PARTITIONED_REL) ||
(stmt->objtype == OBJECT_INDEX &&
relForm->relkind != RELKIND_INDEX) ||
(stmt->objtype == OBJECT_MATVIEW &&
@@ -10033,6 +10175,11 @@ ATPrepAddInherit(Relation child_rel)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot change inheritance of typed table")));
+
+ if (child_rel->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot change inheritance of partitioned table")));
}
/*
@@ -10084,6 +10231,12 @@ ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode)
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot inherit to temporary relation of another session")));
+ /* Prevent partitioned tables from becoming inheritance parents */
+ if (parent_rel->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot inherit from partitioned table \"%s\"", parent->relname)));
+
/*
* Check for duplicates in the list of parents, and determine the highest
* inhseqno already present; we'll use the next one for the new parent.
@@ -11462,6 +11615,7 @@ AlterTableNamespaceInternal(Relation rel, Oid oldNspOid, Oid nspOid,
/* Fix other dependent stuff */
if (rel->rd_rel->relkind == RELKIND_RELATION ||
+ rel->rd_rel->relkind == RELKIND_PARTITIONED_REL ||
rel->rd_rel->relkind == RELKIND_MATVIEW)
{
AlterIndexNamespaces(classRel, rel, oldNspOid, nspOid, objsMoved);
@@ -11911,7 +12065,7 @@ RangeVarCallbackOwnsTable(const RangeVar *relation,
if (!relkind)
return;
if (relkind != RELKIND_RELATION && relkind != RELKIND_TOASTVALUE &&
- relkind != RELKIND_MATVIEW)
+ relkind != RELKIND_MATVIEW && relkind != RELKIND_PARTITIONED_REL)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not a table or materialized view", relation->relname)));
@@ -12065,6 +12219,7 @@ RangeVarCallbackForAlterRelation(const RangeVar *rv, Oid relid, Oid oldrelid,
*/
if (IsA(stmt, AlterObjectSchemaStmt) &&
relkind != RELKIND_RELATION &&
+ relkind != RELKIND_PARTITIONED_REL &&
relkind != RELKIND_VIEW &&
relkind != RELKIND_MATVIEW &&
relkind != RELKIND_SEQUENCE &&
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index 6f728ff..c1e1c4d 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -174,7 +174,8 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
* Triggers must be on tables or views, and there are additional
* relation-type-specific restrictions.
*/
- if (rel->rd_rel->relkind == RELKIND_RELATION)
+ if (rel->rd_rel->relkind == RELKIND_RELATION ||
+ rel->rd_rel->relkind == RELKIND_PARTITIONED_REL)
{
/* Tables can't have INSTEAD OF triggers */
if (stmt->timing != TRIGGER_TYPE_BEFORE &&
@@ -1112,6 +1113,7 @@ RemoveTriggerById(Oid trigOid)
rel = heap_open(relid, AccessExclusiveLock);
if (rel->rd_rel->relkind != RELKIND_RELATION &&
+ rel->rd_rel->relkind != RELKIND_PARTITIONED_REL &&
rel->rd_rel->relkind != RELKIND_VIEW &&
rel->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
ereport(ERROR,
@@ -1218,7 +1220,8 @@ RangeVarCallbackForRenameTrigger(const RangeVar *rv, Oid relid, Oid oldrelid,
/* only tables and views can have triggers */
if (form->relkind != RELKIND_RELATION && form->relkind != RELKIND_VIEW &&
- form->relkind != RELKIND_FOREIGN_TABLE)
+ form->relkind != RELKIND_FOREIGN_TABLE &&
+ form->relkind != RELKIND_PARTITIONED_REL)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not a table, view, or foreign table",
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 25a55ab..973287f 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -1306,6 +1306,7 @@ vacuum_rel(Oid relid, RangeVar *relation, int options, VacuumParams *params)
* relation.
*/
if (onerel->rd_rel->relkind != RELKIND_RELATION &&
+ onerel->rd_rel->relkind != RELKIND_PARTITIONED_REL &&
onerel->rd_rel->relkind != RELKIND_MATVIEW &&
onerel->rd_rel->relkind != RELKIND_TOASTVALUE)
{
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index b5ced38..ab6ec1a 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -1019,6 +1019,7 @@ CheckValidResultRel(Relation resultRel, CmdType operation)
switch (resultRel->rd_rel->relkind)
{
case RELKIND_RELATION:
+ case RELKIND_PARTITIONED_REL:
/* OK */
break;
case RELKIND_SEQUENCE:
@@ -1152,6 +1153,7 @@ CheckValidRowMarkRel(Relation rel, RowMarkType markType)
switch (rel->rd_rel->relkind)
{
case RELKIND_RELATION:
+ case RELKIND_PARTITIONED_REL:
/* OK */
break;
case RELKIND_SEQUENCE:
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index e62c8aa..7ad1140 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1871,6 +1871,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
relkind = resultRelInfo->ri_RelationDesc->rd_rel->relkind;
if (relkind == RELKIND_RELATION ||
+ relkind == RELKIND_PARTITIONED_REL ||
relkind == RELKIND_MATVIEW)
{
j->jf_junkAttNo = ExecFindJunkAttribute(j, "ctid");
diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c
index b56fd4f..77ffd84 100644
--- a/src/backend/parser/parse_agg.c
+++ b/src/backend/parser/parse_agg.c
@@ -488,9 +488,9 @@ check_agglevels_and_constraints(ParseState *pstate, Node *expr)
break;
case EXPR_KIND_PARTITION_KEY:
if (isAgg)
- err = _("aggregate functions are not allowed in partition key expressions");
+ err = _("aggregate functions are not allowed in partition key expression");
else
- err = _("grouping operations are not allowed in partition key expressions");
+ err = _("grouping operations are not allowed in partition key expression");
break;
@@ -852,7 +852,7 @@ transformWindowFuncCall(ParseState *pstate, WindowFunc *wfunc,
err = _("window functions are not allowed in trigger WHEN conditions");
break;
case EXPR_KIND_PARTITION_KEY:
- err = _("window functions are not allowed in partition key expressions");
+ err = _("window functions are not allowed in partition key expression");
break;
/*
diff --git a/src/backend/parser/parse_clause.c b/src/backend/parser/parse_clause.c
index 751de4b..307e383 100644
--- a/src/backend/parser/parse_clause.c
+++ b/src/backend/parser/parse_clause.c
@@ -21,6 +21,7 @@
#include "access/tsmapi.h"
#include "catalog/catalog.h"
#include "catalog/heap.h"
+#include "catalog/partition.h"
#include "catalog/pg_am.h"
#include "catalog/pg_constraint_fn.h"
#include "catalog/pg_type.h"
@@ -200,6 +201,13 @@ setTargetTable(ParseState *pstate, RangeVar *relation,
relation->alias, inh, false);
pstate->p_target_rangetblentry = rte;
+ /*
+ * Override specified inheritance option, if relation is a partitioned
+ * table and it's a target of INSERT (ie, alsoSource == false).
+ */
+ if (alsoSource)
+ rte->inh |= relid_is_partitioned(rte->relid);
+
/* assume new rte is at end */
rtindex = list_length(pstate->p_rtable);
Assert(rte == rt_fetch(rtindex, pstate->p_rtable));
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index b53b7f1..147c6da 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -1724,7 +1724,7 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
err = _("cannot use subquery in trigger WHEN condition");
break;
case EXPR_KIND_PARTITION_KEY:
- err = _("cannot use subquery in partition key expressions");
+ err = _("cannot use subquery in partition key expression");
break;
/*
diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c
index 81332b5..abcc1d6 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -20,6 +20,7 @@
#include "access/sysattr.h"
#include "catalog/heap.h"
#include "catalog/namespace.h"
+#include "catalog/partition.h"
#include "catalog/pg_type.h"
#include "funcapi.h"
#include "nodes/makefuncs.h"
@@ -1216,9 +1217,12 @@ addRangeTableEntry(ParseState *pstate,
*
* The initial default on access checks is always check-for-READ-access,
* which is the right thing for all except target tables.
+ *
+ * Note: we override the specified inheritance option, if relation is a
+ * partitioned table.
*/
rte->lateral = false;
- rte->inh = inh;
+ rte->inh = inh || relid_is_partitioned(RelationGetRelid(rel));
rte->inFromCl = inFromCl;
rte->requiredPerms = ACL_SELECT;
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e267f74..465597d 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -33,6 +33,7 @@
#include "catalog/heap.h"
#include "catalog/index.h"
#include "catalog/namespace.h"
+#include "catalog/partition.h"
#include "catalog/pg_am.h"
#include "catalog/pg_collation.h"
#include "catalog/pg_constraint.h"
@@ -821,6 +822,7 @@ transformTableLikeClause(CreateStmtContext *cxt, TableLikeClause *table_like_cla
relation = relation_openrv(table_like_clause->relation, AccessShareLock);
if (relation->rd_rel->relkind != RELKIND_RELATION &&
+ relation->rd_rel->relkind != RELKIND_PARTITIONED_REL &&
relation->rd_rel->relkind != RELKIND_VIEW &&
relation->rd_rel->relkind != RELKIND_MATVIEW &&
relation->rd_rel->relkind != RELKIND_COMPOSITE_TYPE &&
@@ -2577,7 +2579,7 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
cxt.blist = NIL;
cxt.alist = NIL;
cxt.pkey = NULL;
- cxt.ispartitioned = false;
+ cxt.ispartitioned = relid_is_partitioned(relid);
/*
* The only subtypes that currently require parse transformation handling
diff --git a/src/backend/rewrite/rewriteDefine.c b/src/backend/rewrite/rewriteDefine.c
index 96f3f37..db80c90 100644
--- a/src/backend/rewrite/rewriteDefine.c
+++ b/src/backend/rewrite/rewriteDefine.c
@@ -260,6 +260,7 @@ DefineQueryRewrite(char *rulename,
* blocks them for users. Don't mention them in the error message.
*/
if (event_relation->rd_rel->relkind != RELKIND_RELATION &&
+ event_relation->rd_rel->relkind != RELKIND_PARTITIONED_REL &&
event_relation->rd_rel->relkind != RELKIND_MATVIEW &&
event_relation->rd_rel->relkind != RELKIND_VIEW)
ereport(ERROR,
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index a22a11e..f429dd6 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -1222,6 +1222,7 @@ rewriteTargetListUD(Query *parsetree, RangeTblEntry *target_rte,
TargetEntry *tle;
if (target_relation->rd_rel->relkind == RELKIND_RELATION ||
+ target_relation->rd_rel->relkind == RELKIND_PARTITIONED_REL ||
target_relation->rd_rel->relkind == RELKIND_MATVIEW)
{
/*
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index ac50c2a..220cbfc 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -975,10 +975,13 @@ ProcessUtilitySlow(Node *parsetree,
{
Datum toast_options;
static char *validnsps[] = HEAP_RELOPT_NAMESPACES;
+ char relkind = ((CreateStmt *) stmt)->partby != NULL
+ ? RELKIND_PARTITIONED_REL
+ : RELKIND_RELATION;
/* Create the table itself */
address = DefineRelation((CreateStmt *) stmt,
- RELKIND_RELATION,
+ relkind,
InvalidOid, NULL);
EventTriggerCollectSimpleCommand(address,
secondaryObject,
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 8cb3075..a9e077f 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -33,6 +33,7 @@
#include "catalog/pg_language.h"
#include "catalog/pg_opclass.h"
#include "catalog/pg_operator.h"
+#include "catalog/pg_partitioned.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_trigger.h"
#include "catalog/pg_type.h"
@@ -315,6 +316,7 @@ static char *pg_get_indexdef_worker(Oid indexrelid, int colno,
const Oid *excludeOps,
bool attrsOnly, bool showTblSpc,
int prettyFlags);
+static char *pg_get_partkeydef_worker(Oid relId, int prettyFlags);
static char *pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
int prettyFlags);
static text *pg_get_expr_worker(text *expr, Oid relid, const char *relname,
@@ -1297,6 +1299,151 @@ pg_get_indexdef_worker(Oid indexrelid, int colno,
return buf.data;
}
+/*
+ * pg_get_partkeydef
+ *
+ * Returns the partition key specification, ie, the following:
+ *
+ * PARTITION BY { RANGE | LIST } (column [USING opclass_name] [, ...])
+ */
+Datum
+pg_get_partkeydef(PG_FUNCTION_ARGS)
+{
+ Oid relationId = PG_GETARG_OID(0);
+ int prettyFlags;
+
+ prettyFlags = PRETTYFLAG_INDENT;
+ PG_RETURN_TEXT_P(string_to_text(pg_get_partkeydef_worker(relationId,
+ prettyFlags)));
+}
+
+/*
+ * Internal workhorse to decompile a partition key definition.
+ */
+static char *
+pg_get_partkeydef_worker(Oid relId, int prettyFlags)
+{
+ Form_pg_partitioned form;
+ HeapTuple tuple;
+ oidvector *partclass;
+ List *partexprs;
+ ListCell *partexpr_item;
+ List *context;
+ Datum datum;
+ bool isnull;
+ StringInfoData buf;
+ int keyno;
+ char *str;
+ char *sep;
+
+ tuple = SearchSysCache1(PARTEDRELID, ObjectIdGetDatum(relId));
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "cache lookup failed for partition key of %u", relId);
+
+ form = (Form_pg_partitioned) GETSTRUCT(tuple);
+
+ Assert(form->partedrelid == relId);
+
+ /* Must get partclass, and partexprs the hard way */
+ datum = SysCacheGetAttr(PARTEDRELID, tuple,
+ Anum_pg_partitioned_partclass, &isnull);
+ Assert(!isnull);
+ partclass = (oidvector *) DatumGetPointer(datum);
+
+ /*
+ * Get the partition key expressions, if any. (NOTE: we do not use the
+ * relcache versions of the expressions, because we want to display
+ * non-const-folded expressions.)
+ */
+ if (!heap_attisnull(tuple, Anum_pg_partitioned_partexprs))
+ {
+ Datum exprsDatum;
+ bool isnull;
+ char *exprsString;
+
+ exprsDatum = SysCacheGetAttr(PARTEDRELID, tuple,
+ Anum_pg_partitioned_partexprs, &isnull);
+ Assert(!isnull);
+ exprsString = TextDatumGetCString(exprsDatum);
+ partexprs = (List *) stringToNode(exprsString);
+ pfree(exprsString);
+ }
+ else
+ partexprs = NIL;
+
+ partexpr_item = list_head(partexprs);
+ context = deparse_context_for(get_relation_name(relId), relId);
+
+ /*
+ * Start the partition key definition.
+ */
+ initStringInfo(&buf);
+
+ appendStringInfo(&buf, "PARTITION BY ");
+
+ switch (form->partstrat)
+ {
+ case 'l':
+ appendStringInfo(&buf, "LIST");
+ break;
+ case 'r':
+ appendStringInfo(&buf, "RANGE");
+ break;
+ }
+
+ /*
+ * Report the partition key columns
+ */
+ appendStringInfo(&buf, " (");
+ sep = "";
+ for (keyno = 0; keyno < form->partnatts; keyno++)
+ {
+ AttrNumber attnum = form->partattrs.values[keyno];
+ Oid keycoltype;
+
+ appendStringInfoString(&buf, sep);
+ sep = ", ";
+ if (attnum != 0)
+ {
+ /* Simple partition key column */
+ char *attname;
+
+ attname = get_relid_attribute_name(relId, attnum);
+ appendStringInfoString(&buf, quote_identifier(attname));
+ keycoltype = get_atttype(relId, attnum);
+ }
+ else
+ {
+ /* partition key expression */
+ Node *partkey;
+
+ if (partexpr_item == NULL)
+ elog(ERROR, "too few entries in partexprs list");
+ partkey = (Node *) lfirst(partexpr_item);
+ partexpr_item = lnext(partexpr_item);
+ /* Deparse */
+ str = deparse_expression_pretty(partkey, context, false, false,
+ prettyFlags, 0);
+
+ /* Need parens if it's not a bare function call */
+ if (partkey && IsA(partkey, FuncExpr) &&
+ ((FuncExpr *) partkey)->funcformat == COERCE_EXPLICIT_CALL)
+ appendStringInfoString(&buf, str);
+ else
+ appendStringInfo(&buf, "(%s)", str);
+ keycoltype = exprType(partkey);
+ }
+
+ /* Add the operator class name, if not default */
+ get_opclass_name(partclass->values[keyno], keycoltype, &buf);
+ }
+ appendStringInfoChar(&buf, ')');
+
+ /* Clean up */
+ ReleaseSysCache(tuple);
+
+ return buf.data;
+}
/*
* pg_get_constraintdef
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 1b7b995..d66a1bf 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -40,6 +40,7 @@
#include "catalog/index.h"
#include "catalog/indexing.h"
#include "catalog/namespace.h"
+#include "catalog/partition.h"
#include "catalog/pg_am.h"
#include "catalog/pg_amproc.h"
#include "catalog/pg_attrdef.h"
@@ -431,6 +432,7 @@ RelationParseRelOptions(Relation relation, HeapTuple tuple)
switch (relation->rd_rel->relkind)
{
case RELKIND_RELATION:
+ case RELKIND_PARTITIONED_REL:
case RELKIND_TOASTVALUE:
case RELKIND_INDEX:
case RELKIND_VIEW:
@@ -1049,6 +1051,12 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
else
relation->rd_rsdesc = NULL;
+ /* if it's a partitioned table, initialize key info */
+ if (relation->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+ RelationBuildPartitionKey(relation);
+ else
+ relation->rd_partkey = NULL;
+
/*
* if it's an index, initialize index-related information
*/
@@ -2045,6 +2053,7 @@ RelationDestroyRelation(Relation relation, bool remember_tupdesc)
MemoryContextDelete(relation->rd_rulescxt);
if (relation->rd_rsdesc)
MemoryContextDelete(relation->rd_rsdesc->rscxt);
+ FreePartitionKey(relation->rd_partkey);
if (relation->rd_fdwroutine)
pfree(relation->rd_fdwroutine);
pfree(relation);
@@ -2986,7 +2995,9 @@ RelationBuildLocalRelation(const char *relname,
/* system relations and non-table objects don't have one */
if (!IsSystemNamespace(relnamespace) &&
- (relkind == RELKIND_RELATION || relkind == RELKIND_MATVIEW))
+ (relkind == RELKIND_RELATION ||
+ relkind == RELKIND_PARTITIONED_REL ||
+ relkind == RELKIND_MATVIEW))
rel->rd_rel->relreplident = REPLICA_IDENTITY_DEFAULT;
else
rel->rd_rel->relreplident = REPLICA_IDENTITY_NOTHING;
@@ -4898,6 +4909,7 @@ load_relcache_init_file(bool shared)
rel->rd_rulescxt = NULL;
rel->trigdesc = NULL;
rel->rd_rsdesc = NULL;
+ rel->rd_partkey = NULL;
rel->rd_indexprs = NIL;
rel->rd_indpred = NIL;
rel->rd_exclops = NULL;
diff --git a/src/backend/utils/cache/syscache.c b/src/backend/utils/cache/syscache.c
index 65ffe84..a6563fe 100644
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -48,6 +48,7 @@
#include "catalog/pg_opclass.h"
#include "catalog/pg_operator.h"
#include "catalog/pg_opfamily.h"
+#include "catalog/pg_partitioned.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_range.h"
#include "catalog/pg_rewrite.h"
@@ -568,6 +569,17 @@ static const struct cachedesc cacheinfo[] = {
},
8
},
+ {PartitionedRelationId, /* PARTEDRELID */
+ PartitionedRelidIndexId,
+ 1,
+ {
+ Anum_pg_partitioned_partedrelid,
+ 0,
+ 0,
+ 0
+ },
+ 128
+ },
{ProcedureRelationId, /* PROCNAMEARGSNSP */
ProcedureNameArgsNspIndexId,
3,
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 89cf027..e11af8b 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -1251,9 +1251,10 @@ expand_table_name_patterns(Archive *fout,
"SELECT c.oid"
"\nFROM pg_catalog.pg_class c"
"\n LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace"
- "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c')\n",
+ "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c')\n",
RELKIND_RELATION, RELKIND_SEQUENCE, RELKIND_VIEW,
- RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+ RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+ RELKIND_PARTITIONED_REL);
processSQLNamePattern(GetConnection(fout), query, cell->val, true,
false, "n.nspname", "c.relname", NULL,
"pg_catalog.pg_table_is_visible(c.oid)");
@@ -2115,6 +2116,9 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo, bool oids)
/* Skip FOREIGN TABLEs (no data to dump) */
if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
return;
+ /* Skip partitioned tables (data in partitions) */
+ if (tbinfo->relkind == RELKIND_PARTITIONED_REL)
+ return;
/* Don't dump data in unlogged tables, if so requested */
if (tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED &&
@@ -5193,6 +5197,7 @@ getTables(Archive *fout, int *numTables)
int i_reloftype;
int i_relpages;
int i_changed_acl;
+ int i_partkeydef;
/* Make sure we are in proper schema */
selectSourceSchema(fout, "pg_catalog");
@@ -5278,7 +5283,8 @@ getTables(Archive *fout, int *numTables)
"OR %s IS NOT NULL "
"OR %s IS NOT NULL"
"))"
- "AS changed_acl "
+ "AS changed_acl, "
+ "CASE WHEN c.relkind = 'P' THEN pg_catalog.pg_get_partkeydef(c.oid) ELSE NULL END AS partkeydef "
"FROM pg_class c "
"LEFT JOIN pg_depend d ON "
"(c.relkind = '%c' AND "
@@ -5290,7 +5296,7 @@ getTables(Archive *fout, int *numTables)
"(c.oid = pip.objoid "
"AND pip.classoid = 'pg_class'::regclass "
"AND pip.objsubid = 0) "
- "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c') "
+ "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c', '%c') "
"ORDER BY c.oid",
acl_subquery->data,
racl_subquery->data,
@@ -5304,7 +5310,8 @@ getTables(Archive *fout, int *numTables)
RELKIND_SEQUENCE,
RELKIND_RELATION, RELKIND_SEQUENCE,
RELKIND_VIEW, RELKIND_COMPOSITE_TYPE,
- RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+ RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+ RELKIND_PARTITIONED_REL);
destroyPQExpBuffer(acl_subquery);
destroyPQExpBuffer(racl_subquery);
@@ -5866,6 +5873,7 @@ getTables(Archive *fout, int *numTables)
i_toastreloptions = PQfnumber(res, "toast_reloptions");
i_reloftype = PQfnumber(res, "reloftype");
i_changed_acl = PQfnumber(res, "changed_acl");
+ i_partkeydef = PQfnumber(res, "partkeydef");
if (dopt->lockWaitTimeout && fout->remoteVersion >= 70300)
{
@@ -5936,6 +5944,7 @@ getTables(Archive *fout, int *numTables)
else
tblinfo[i].checkoption = pg_strdup(PQgetvalue(res, i, i_checkoption));
tblinfo[i].toast_reloptions = pg_strdup(PQgetvalue(res, i, i_toastreloptions));
+ tblinfo[i].partkeydef = pg_strdup(PQgetvalue(res, i, i_partkeydef));
/* other fields were zeroed above */
@@ -5979,7 +5988,9 @@ getTables(Archive *fout, int *numTables)
*
* We only need to lock the table for certain components; see pg_dump.h
*/
- if (tblinfo[i].dobj.dump && tblinfo[i].relkind == RELKIND_RELATION &&
+ if (tblinfo[i].dobj.dump &&
+ (tblinfo[i].relkind == RELKIND_RELATION ||
+ tblinfo->relkind == RELKIND_PARTITIONED_REL) &&
(tblinfo[i].dobj.dump & DUMP_COMPONENTS_REQUIRING_LOCK))
{
resetPQExpBuffer(query);
@@ -6067,7 +6078,10 @@ getInherits(Archive *fout, int *numInherits)
/* find all the inheritance information */
- appendPQExpBufferStr(query, "SELECT inhrelid, inhparent FROM pg_inherits");
+ appendPQExpBufferStr(query,
+ "SELECT inhrelid, inhparent "
+ "FROM pg_inherits "
+ "WHERE inhparent NOT IN (SELECT oid FROM pg_class WHERE relkind = 'P')");
res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
@@ -15438,6 +15452,9 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
appendPQExpBufferChar(q, ')');
}
+ if (tbinfo->relkind == RELKIND_PARTITIONED_REL)
+ appendPQExpBuffer(q, "\n%s", tbinfo->partkeydef);
+
if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
appendPQExpBuffer(q, "\nSERVER %s", fmtId(srvname));
}
@@ -15498,6 +15515,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
*/
if (dopt->binary_upgrade &&
(tbinfo->relkind == RELKIND_RELATION ||
+ tbinfo->relkind == RELKIND_PARTITIONED_REL ||
tbinfo->relkind == RELKIND_FOREIGN_TABLE))
{
for (j = 0; j < tbinfo->numatts; j++)
@@ -15516,7 +15534,8 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
appendStringLiteralAH(q, fmtId(tbinfo->dobj.name), fout);
appendPQExpBufferStr(q, "::pg_catalog.regclass;\n");
- if (tbinfo->relkind == RELKIND_RELATION)
+ if (tbinfo->relkind == RELKIND_RELATION ||
+ tbinfo->relkind == RELKIND_PARTITIONED_REL)
appendPQExpBuffer(q, "ALTER TABLE ONLY %s ",
fmtId(tbinfo->dobj.name));
else
@@ -15733,6 +15752,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
* dump properties we only have ALTER TABLE syntax for
*/
if ((tbinfo->relkind == RELKIND_RELATION ||
+ tbinfo->relkind == RELKIND_PARTITIONED_REL ||
tbinfo->relkind == RELKIND_MATVIEW) &&
tbinfo->relreplident != REPLICA_IDENTITY_DEFAULT)
{
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 2bfa2d9..0292859 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -310,6 +310,7 @@ typedef struct _tableInfo
bool *inhNotNull; /* true if NOT NULL is inherited */
struct _attrDefInfo **attrdefs; /* DEFAULT expressions */
struct _constraintInfo *checkexprs; /* CHECK constraints */
+ char *partkeydef; /* partition key definition */
/*
* Stuff computed only for dumpable tables.
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 2cdc5ac..71d6cdc 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -828,6 +828,7 @@ permissionsList(const char *pattern)
" WHEN 'm' THEN '%s'"
" WHEN 'S' THEN '%s'"
" WHEN 'f' THEN '%s'"
+ " WHEN 'P' THEN '%s'"
" END as \"%s\",\n"
" ",
gettext_noop("Schema"),
@@ -837,6 +838,7 @@ permissionsList(const char *pattern)
gettext_noop("materialized view"),
gettext_noop("sequence"),
gettext_noop("foreign table"),
+ gettext_noop("partitioned table"),
gettext_noop("Type"));
printACLColumn(&buf, "c.relacl");
@@ -883,7 +885,7 @@ permissionsList(const char *pattern)
appendPQExpBufferStr(&buf, "\nFROM pg_catalog.pg_class c\n"
" LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace\n"
- "WHERE c.relkind IN ('r', 'v', 'm', 'S', 'f')\n");
+ "WHERE c.relkind IN ('r', 'v', 'm', 'S', 'f', 'P')\n");
/*
* Unless a schema pattern is specified, we suppress system and temp
@@ -1527,8 +1529,8 @@ describeOneTableDetails(const char *schemaname,
* types, and foreign tables (c.f. CommentObject() in comment.c).
*/
if (tableinfo.relkind == 'r' || tableinfo.relkind == 'v' ||
- tableinfo.relkind == 'm' ||
- tableinfo.relkind == 'f' || tableinfo.relkind == 'c')
+ tableinfo.relkind == 'm' || tableinfo.relkind == 'f' ||
+ tableinfo.relkind == 'c' || tableinfo.relkind == 'P')
appendPQExpBufferStr(&buf, ", pg_catalog.col_description(a.attrelid, a.attnum)");
}
@@ -1593,6 +1595,14 @@ describeOneTableDetails(const char *schemaname,
printfPQExpBuffer(&title, _("Foreign table \"%s.%s\""),
schemaname, relationname);
break;
+ case 'P':
+ if (tableinfo.relpersistence == 'u')
+ printfPQExpBuffer(&title, _("Unlogged partitioned table \"%s.%s\""),
+ schemaname, relationname);
+ else
+ printfPQExpBuffer(&title, _("Partitioned table \"%s.%s\""),
+ schemaname, relationname);
+ break;
default:
/* untranslated unknown relkind */
printfPQExpBuffer(&title, "?%c? \"%s.%s\"",
@@ -1606,8 +1616,8 @@ describeOneTableDetails(const char *schemaname,
cols = 2;
if (tableinfo.relkind == 'r' || tableinfo.relkind == 'v' ||
- tableinfo.relkind == 'm' ||
- tableinfo.relkind == 'f' || tableinfo.relkind == 'c')
+ tableinfo.relkind == 'm' || tableinfo.relkind == 'f' ||
+ tableinfo.relkind == 'c' || tableinfo.relkind == 'P')
{
show_modifiers = true;
headers[cols++] = gettext_noop("Modifiers");
@@ -1627,12 +1637,12 @@ describeOneTableDetails(const char *schemaname,
{
headers[cols++] = gettext_noop("Storage");
if (tableinfo.relkind == 'r' || tableinfo.relkind == 'm' ||
- tableinfo.relkind == 'f')
+ tableinfo.relkind == 'f' || tableinfo.relkind == 'P')
headers[cols++] = gettext_noop("Stats target");
/* Column comments, if the relkind supports this feature. */
if (tableinfo.relkind == 'r' || tableinfo.relkind == 'v' ||
- tableinfo.relkind == 'm' ||
- tableinfo.relkind == 'c' || tableinfo.relkind == 'f')
+ tableinfo.relkind == 'm' || tableinfo.relkind == 'c' ||
+ tableinfo.relkind == 'f' || tableinfo.relkind == 'P')
headers[cols++] = gettext_noop("Description");
}
@@ -1732,7 +1742,7 @@ describeOneTableDetails(const char *schemaname,
/* Statistics target, if the relkind supports this feature */
if (tableinfo.relkind == 'r' || tableinfo.relkind == 'm' ||
- tableinfo.relkind == 'f')
+ tableinfo.relkind == 'f' || tableinfo.relkind == 'P')
{
printTableAddCell(&cont, PQgetvalue(res, i, firstvcol + 1),
false, false);
@@ -1740,14 +1750,33 @@ describeOneTableDetails(const char *schemaname,
/* Column comments, if the relkind supports this feature. */
if (tableinfo.relkind == 'r' || tableinfo.relkind == 'v' ||
- tableinfo.relkind == 'm' ||
- tableinfo.relkind == 'c' || tableinfo.relkind == 'f')
+ tableinfo.relkind == 'm' || tableinfo.relkind == 'c' ||
+ tableinfo.relkind == 'f' || tableinfo.relkind == 'P')
printTableAddCell(&cont, PQgetvalue(res, i, firstvcol + 2),
false, false);
}
}
/* Make footers */
+ if (pset.sversion >= 90600 && tableinfo.relkind == 'P')
+ {
+ /* Get the partition key information */
+ PGresult *result;
+ char *partkeydef;
+
+ printfPQExpBuffer(&buf,
+ "SELECT pg_catalog.pg_get_partkeydef('%s'::pg_catalog.oid);",
+ oid);
+ result = PSQLexec(buf.data);
+ if (!result || PQntuples(result) != 1)
+ goto error_return;
+
+ partkeydef = PQgetvalue(result, 0, 0);
+ printfPQExpBuffer(&tmpbuf, _("Partition Key: %s"), partkeydef);
+ printTableAddFooter(&cont, tmpbuf.data);
+ PQclear(result);
+ }
+
if (tableinfo.relkind == 'i')
{
/* Footer information about an index */
@@ -1886,7 +1915,7 @@ describeOneTableDetails(const char *schemaname,
PQclear(result);
}
else if (tableinfo.relkind == 'r' || tableinfo.relkind == 'm' ||
- tableinfo.relkind == 'f')
+ tableinfo.relkind == 'f' || tableinfo.relkind == 'P')
{
/* Footer information about a table */
PGresult *result = NULL;
@@ -2445,7 +2474,7 @@ describeOneTableDetails(const char *schemaname,
* Finish printing the footer information about a table.
*/
if (tableinfo.relkind == 'r' || tableinfo.relkind == 'm' ||
- tableinfo.relkind == 'f')
+ tableinfo.relkind == 'f' || tableinfo.relkind == 'P')
{
PGresult *result;
int tuples;
@@ -2656,7 +2685,7 @@ add_tablespace_footer(printTableContent *const cont, char relkind,
Oid tablespace, const bool newline)
{
/* relkinds for which we support tablespaces */
- if (relkind == 'r' || relkind == 'm' || relkind == 'i')
+ if (relkind == 'r' || relkind == 'm' || relkind == 'i' || relkind == 'P')
{
/*
* We ignore the database default tablespace so that users not using
@@ -2990,6 +3019,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
" WHEN 'S' THEN '%s'"
" WHEN 's' THEN '%s'"
" WHEN 'f' THEN '%s'"
+ " WHEN 'P' THEN '%s'"
" END as \"%s\",\n"
" pg_catalog.pg_get_userbyid(c.relowner) as \"%s\"",
gettext_noop("Schema"),
@@ -3001,6 +3031,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
gettext_noop("sequence"),
gettext_noop("special"),
gettext_noop("foreign table"),
+ gettext_noop("partitioned table"),
gettext_noop("Type"),
gettext_noop("Owner"));
@@ -3039,7 +3070,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
appendPQExpBufferStr(&buf, "\nWHERE c.relkind IN (");
if (showTables)
- appendPQExpBufferStr(&buf, "'r',");
+ appendPQExpBufferStr(&buf, "'r', 'P',");
if (showViews)
appendPQExpBufferStr(&buf, "'v',");
if (showMatViews)
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 3b07726..853e13e 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -427,7 +427,7 @@ static const SchemaQuery Query_for_list_of_tables = {
/* catname */
"pg_catalog.pg_class c",
/* selcondition */
- "c.relkind IN ('r')",
+ "c.relkind IN ('r', 'P')",
/* viscondition */
"pg_catalog.pg_table_is_visible(c.oid)",
/* namespace */
@@ -458,7 +458,7 @@ static const SchemaQuery Query_for_list_of_updatables = {
/* catname */
"pg_catalog.pg_class c",
/* selcondition */
- "c.relkind IN ('r', 'f', 'v')",
+ "c.relkind IN ('r', 'f', 'v', 'P')",
/* viscondition */
"pg_catalog.pg_table_is_visible(c.oid)",
/* namespace */
@@ -488,7 +488,7 @@ static const SchemaQuery Query_for_list_of_tsvmf = {
/* catname */
"pg_catalog.pg_class c",
/* selcondition */
- "c.relkind IN ('r', 'S', 'v', 'm', 'f')",
+ "c.relkind IN ('r', 'S', 'v', 'm', 'f', 'P')",
/* viscondition */
"pg_catalog.pg_table_is_visible(c.oid)",
/* namespace */
diff --git a/src/include/catalog/indexing.h b/src/include/catalog/indexing.h
index ca5eb3d..cadb741 100644
--- a/src/include/catalog/indexing.h
+++ b/src/include/catalog/indexing.h
@@ -319,6 +319,9 @@ DECLARE_UNIQUE_INDEX(pg_replication_origin_roiident_index, 6001, on pg_replicati
DECLARE_UNIQUE_INDEX(pg_replication_origin_roname_index, 6002, on pg_replication_origin using btree(roname text_pattern_ops));
#define ReplicationOriginNameIndex 6002
+DECLARE_UNIQUE_INDEX(pg_partitioned_partedrelid_index, 3351, on pg_partitioned using btree(partedrelid oid_ops));
+#define PartitionedRelidIndexId 3351
+
/* last step of initialization script: build the indexes declared above */
BUILD_INDICES
diff --git a/src/include/catalog/partition.h b/src/include/catalog/partition.h
new file mode 100644
index 0000000..2c876c4
--- /dev/null
+++ b/src/include/catalog/partition.h
@@ -0,0 +1,55 @@
+/*-------------------------------------------------------------------------
+ *
+ * partition.h
+ * Header file for structures and utility functions related to
+ * partitioning
+ *
+ * Copyright (c) 2007-2016, PostgreSQL Global Development Group
+ *
+ * src/include/utils/partition.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PARTITION_H
+#define PARTITION_H
+
+#include "fmgr.h"
+#include "utils/relcache.h"
+
+/* Type and collation information for partition key columns */
+typedef struct
+{
+ Oid *typid;
+ int32 *typmod;
+ int16 *typlen;
+ bool *typbyval;
+ char *typalign;
+ Oid *typcoll;
+} KeyTypeCollInfo;
+
+/* Partition key information for one level */
+typedef struct PartitionKeyData
+{
+ char strategy; /* partition strategy */
+ int16 partnatts; /* number of partition attributes */
+ AttrNumber *partattrs; /* partition attnums */
+ Oid *partopfamily; /* OIDs of opfamily per col */
+ FmgrInfo *partsupfunc; /* lookup info for support funcs */
+ List *partexprs; /* partition key expressions, if any */
+ KeyTypeCollInfo *tcinfo; /* type and collation info (all columns) */
+} PartitionKeyData;
+
+typedef struct PartitionKeyData *PartitionKey;
+
+extern void StorePartitionKey(Relation rel,
+ char strategy,
+ int16 partnatts,
+ AttrNumber *partattrs,
+ List *partexprs,
+ Oid *partopclass);
+extern void RemovePartitionKeyByRelId(Oid relid);
+extern void RelationBuildPartitionKey(Relation relation);
+extern void FreePartitionKey(PartitionKey key);
+extern bool relid_is_partitioned(Oid relid);
+
+#endif /* PARTITION_H */
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index fdc67c2..149f619 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -153,6 +153,7 @@ DESCR("");
#define RELKIND_RELATION 'r' /* ordinary table */
+#define RELKIND_PARTITIONED_REL 'P' /* partitioned table */
#define RELKIND_INDEX 'i' /* secondary index */
#define RELKIND_SEQUENCE 'S' /* sequence object */
#define RELKIND_TOASTVALUE 't' /* for out-of-line values */
diff --git a/src/include/catalog/pg_partitioned.h b/src/include/catalog/pg_partitioned.h
new file mode 100644
index 0000000..edc52db
--- /dev/null
+++ b/src/include/catalog/pg_partitioned.h
@@ -0,0 +1,67 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_partitioned.h
+ * definition of the system "partitioned" relation (pg_partitioned)
+ * along with the relation's initial contents.
+ *
+ *
+ * Portions Copyright (c) 1996-2015, PostgreSQL Global Development Group
+ *
+ * $PostgreSQL: pgsql/src/include/catalog/pg_partitioned.h $
+ *
+ * NOTES
+ * the genbki.sh script reads this file and generates .bki
+ * information from the DATA() statements.
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_PARTITIONED_REL_H
+#define PG_PARTITIONED_REL_H
+
+#include "catalog/genbki.h"
+
+/* ----------------
+ * pg_partitioned definition. cpp turns this into
+ * typedef struct FormData_pg_partitioned
+ * ----------------
+ */
+#define PartitionedRelationId 3350
+
+CATALOG(pg_partitioned,3350) BKI_WITHOUT_OIDS
+{
+ Oid partedrelid; /* partitioned table oid */
+ char partstrat; /* partition key strategy */
+ int16 partnatts; /* number of partition key columns */
+
+ /* variable-length fields start here, but we allow direct access to partattrs */
+ int2vector partattrs; /* attribute numbers of partition key
+ * columns */
+
+#ifdef CATALOG_VARLEN
+ oidvector partclass; /* operator class to compare keys */
+ pg_node_tree partexprs; /* expression trees for partition key members
+ * that are not simple column references; one
+ * for each zero entry in partkey[] */
+#endif
+} FormData_pg_partitioned;
+
+/* ----------------
+ * Form_pg_partitioned corresponds to a pointer to a tuple with
+ * the format of pg_partitioned relation.
+ * ----------------
+ */
+typedef FormData_pg_partitioned *Form_pg_partitioned;
+
+/* ----------------
+ * compiler constants for pg_partitioned
+ * ----------------
+ */
+#define Natts_pg_partitioned 6
+#define Anum_pg_partitioned_partedrelid 1
+#define Anum_pg_partitioned_partstrat 2
+#define Anum_pg_partitioned_partnatts 3
+#define Anum_pg_partitioned_partattrs 4
+#define Anum_pg_partitioned_partclass 5
+#define Anum_pg_partitioned_partexprs 6
+
+#endif /* PG_PARTITIONED_REL_H */
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index d94943b..24d7c5f 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -1969,6 +1969,8 @@ DATA(insert OID = 1642 ( pg_get_userbyid PGNSP PGUID 12 1 0 0 0 f f f f t f
DESCR("role name by OID (with fallback)");
DATA(insert OID = 1643 ( pg_get_indexdef PGNSP PGUID 12 1 0 0 0 f f f f t f s s 1 0 25 "26" _null_ _null_ _null_ _null_ _null_ pg_get_indexdef _null_ _null_ _null_ ));
DESCR("index description");
+DATA(insert OID = 3352 ( pg_get_partkeydef PGNSP PGUID 12 1 0 0 0 f f f f t f s s 1 0 25 "26" _null_ _null_ _null_ _null_ _null_ pg_get_partkeydef _null_ _null_ _null_ ));
+DESCR("partition key description");
DATA(insert OID = 1662 ( pg_get_triggerdef PGNSP PGUID 12 1 0 0 0 f f f f t f s s 1 0 25 "26" _null_ _null_ _null_ _null_ _null_ pg_get_triggerdef _null_ _null_ _null_ ));
DESCR("trigger description");
DATA(insert OID = 1387 ( pg_get_constraintdef PGNSP PGUID 12 1 0 0 0 f f f f t f s s 1 0 25 "26" _null_ _null_ _null_ _null_ _null_ pg_get_constraintdef _null_ _null_ _null_ ));
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index 28024f8..ceeb41f 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -42,8 +42,8 @@ extern bool CheckIndexCompatible(Oid oldId,
List *attributeList,
List *exclusionOpNames);
extern Oid GetDefaultOpClass(Oid type_id, Oid am_id);
-extern Oid GetIndexOpClass(List *opclass, Oid attrType,
- char *accessMethodName, Oid accessMethodId);
+extern Oid GetIndexOpClass(List *opclass, Oid attrType,
+ char *accessMethodName, Oid accessMethodId);
/* commands/functioncmds.c */
extern ObjectAddress CreateFunction(CreateFunctionStmt *stmt, const char *queryString);
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
index 01976a1..027665c 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -720,6 +720,7 @@ extern Datum pg_get_viewdef_wrap(PG_FUNCTION_ARGS);
extern Datum pg_get_viewdef_name(PG_FUNCTION_ARGS);
extern Datum pg_get_viewdef_name_ext(PG_FUNCTION_ARGS);
extern Datum pg_get_indexdef(PG_FUNCTION_ARGS);
+extern Datum pg_get_partkeydef(PG_FUNCTION_ARGS);
extern Datum pg_get_indexdef_ext(PG_FUNCTION_ARGS);
extern Datum pg_get_triggerdef(PG_FUNCTION_ARGS);
extern Datum pg_get_triggerdef_ext(PG_FUNCTION_ARGS);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 5d354c0..0c5d5d6 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -90,6 +90,8 @@ typedef struct RelationData
/* use "struct" here to avoid needing to include rowsecurity.h: */
struct RowSecurityDesc *rd_rsdesc; /* row security policies, or NULL */
+ struct PartitionKeyData *rd_partkey; /* partition key, or NULL */
+
/* data managed by RelationGetIndexList: */
List *rd_indexlist; /* list of OIDs of indexes on relation */
Oid rd_oidindex; /* OID of unique index on OID, if any */
@@ -499,6 +501,12 @@ typedef struct ViewOptions
RelationNeedsWAL(relation) && \
!IsCatalogRelation(relation))
+/*
+ * RelationGetPartitionKey
+ * Returns partition key for a relation.
+ */
+#define RelationGetPartitionKey(relation) ((relation)->rd_partkey)
+
/* routines in utils/cache/relcache.c */
extern void RelationIncrementReferenceCount(Relation rel);
extern void RelationDecrementReferenceCount(Relation rel);
diff --git a/src/include/utils/syscache.h b/src/include/utils/syscache.h
index 256615b..e727842 100644
--- a/src/include/utils/syscache.h
+++ b/src/include/utils/syscache.h
@@ -72,6 +72,7 @@ enum SysCacheIdentifier
OPEROID,
OPFAMILYAMNAMENSP,
OPFAMILYOID,
+ PARTEDRELID,
PROCNAMEARGSNSP,
PROCOID,
RANGETYPE,
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index b3a392f..513debf 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -314,9 +314,35 @@ ERROR: cannot use a constant expression as partition key
CREATE TABLE fail_agg_in_key (
a int
) PARTITION BY RANGE ((avg(a)));
-ERROR: aggregate functions are not allowed in partition key expressions
+ERROR: aggregate functions are not allowed in partition key expression
CREATE TABLE fail_window_fun_in_key (
a int,
b int
) PARTITION BY RANGE ((avg(a) OVER (PARTITION BY b)));
-ERROR: window functions are not allowed in partition key expressions
+ERROR: window functions are not allowed in partition key expression
+-- check relkind
+CREATE TABLE check_relkind (
+ a int
+) PARTITION BY RANGE (a);
+SELECT relkind FROM pg_class WHERE relname = 'check_relkind';
+ relkind
+---------
+ P
+(1 row)
+
+-- cannot drop column that is part of the partition key
+CREATE TABLE no_drop_alter_partcol (
+ a int
+) PARTITION BY RANGE (a);
+ALTER TABLE no_drop_alter_partcol DROP COLUMN a;
+ERROR: cannot drop column named in partition key
+ALTER TABLE no_drop_alter_partcol ALTER COLUMN a TYPE char(5);
+ERROR: cannot alter column named in partition key
+CREATE TABLE no_drop_alter_partexpr (
+ a text
+) PARTITION BY RANGE ((substring(a from 1 for 1)));
+ALTER TABLE no_drop_alter_partexpr DROP COLUMN a;
+ERROR: cannot drop column referenced in partition key expression
+ALTER TABLE no_drop_alter_partcol ALTER COLUMN a TYPE char(5);
+ERROR: cannot alter column named in partition key
+DROP TABLE check_relkind, no_drop_alter_partcol, no_drop_alter_partexpr;
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index 1c087a3..b1e473d 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -118,6 +118,7 @@ pg_namespace|t
pg_opclass|t
pg_operator|t
pg_opfamily|t
+pg_partitioned|t
pg_pltemplate|t
pg_policy|t
pg_proc|t
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index de20fff..baa426d 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -325,3 +325,24 @@ CREATE TABLE fail_window_fun_in_key (
a int,
b int
) PARTITION BY RANGE ((avg(a) OVER (PARTITION BY b)));
+
+-- check relkind
+CREATE TABLE check_relkind (
+ a int
+) PARTITION BY RANGE (a);
+SELECT relkind FROM pg_class WHERE relname = 'check_relkind';
+
+-- cannot drop column that is part of the partition key
+CREATE TABLE no_drop_alter_partcol (
+ a int
+) PARTITION BY RANGE (a);
+ALTER TABLE no_drop_alter_partcol DROP COLUMN a;
+ALTER TABLE no_drop_alter_partcol ALTER COLUMN a TYPE char(5);
+
+CREATE TABLE no_drop_alter_partexpr (
+ a text
+) PARTITION BY RANGE ((substring(a from 1 for 1)));
+ALTER TABLE no_drop_alter_partexpr DROP COLUMN a;
+ALTER TABLE no_drop_alter_partcol ALTER COLUMN a TYPE char(5);
+
+DROP TABLE check_relkind, no_drop_alter_partcol, no_drop_alter_partexpr;
--
1.7.1
[text/x-diff] 0004-Add-syntax-to-create-partitions-v6.patch (77.8K, ../../19a3733b-ab58-b993-c858-ec8fc578ddfd@lab.ntt.co.jp/5-0004-Add-syntax-to-create-partitions-v6.patch)
download | inline diff:
From 957d628045669f935df2085b6ce0bc9f91279ba4 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Thu, 3 Mar 2016 18:29:15 +0900
Subject: [PATCH 4/7] Add syntax to create partitions.
As follows:
CREATE TABLE name PARTITION OF parent
[ ( { column_name WITH OPTION column_constraint | table_constraint } [, ...] ) ]
partition_bound_spec [ PARTITION BY partition_key_spec ]
ALTER TABLE parent ATTACH PARTITION name
partition_bound_spec [ VALIDATE | NO VALIDATE ]
ALTER TABLE parent DETACH PARTITION name
partition_bound_spec refers to either list partition bound of form:
FOR VALUES IN (list-of-expressions)
or range partition bound of form:
FOR VALUES range_spec
where range_spec is:
START lower-bound [ INCLUSIVE | EXCLUSIVE ] END upper-bound [ INCLUSIVE | EXCLUSIVE ]
lower-bound and upper-bound is:
{ ( expression [,...] ) | UNBOUNDED }
In the above, each of lower-bound and upper-bound is either a single
expression for a single column key or list of expressions for a multi-
column key or empty. Note that in the absence of explicit specification,
lower-bound is inclusive and upper-bound is exclusive.
A partition is created as inheritance child of the parent with certain
differences from regular inheritance behavior. For example, when a new
partition is created, it cannot contain its own column definitions or
constraints (unlike CREATE TABLE child(...) INHERITS(parent)). When
"attaching" a table as partition of parent, it cannot contain columns
that are not contained in the parent (unlike ALTER TABLE child INHERIT
parent). No dependency is created between a partition and the parent.
One cannot create regular inheritance children of a partitioned table.
Whereas this commit enables inheritance, it does not yet store partition
bound info anywhere, IOW, the fact that a given inheritance child is a
"partition" is not persisted.
---
doc/src/sgml/ref/alter_table.sgml | 57 +++++
doc/src/sgml/ref/create_table.sgml | 77 ++++++-
src/backend/commands/createas.c | 1 +
src/backend/commands/sequence.c | 1 +
src/backend/commands/tablecmds.c | 490 +++++++++++++++++++++++++++---------
src/backend/commands/view.c | 1 +
src/backend/nodes/copyfuncs.c | 48 ++++
src/backend/nodes/equalfuncs.c | 42 +++
src/backend/nodes/outfuncs.c | 27 ++
src/backend/parser/gram.y | 167 ++++++++++++-
src/backend/parser/parse_agg.c | 7 +-
src/backend/parser/parse_expr.c | 23 ++
src/backend/parser/parse_utilcmd.c | 318 +++++++++++++++++++++++
src/include/catalog/partition.h | 1 +
src/include/nodes/nodes.h | 3 +
src/include/nodes/parsenodes.h | 43 +++-
src/include/parser/kwlist.h | 3 +
src/include/parser/parse_node.h | 3 +-
18 files changed, 1182 insertions(+), 130 deletions(-)
diff --git a/doc/src/sgml/ref/alter_table.sgml b/doc/src/sgml/ref/alter_table.sgml
index 5ca211e..97744c9 100644
--- a/doc/src/sgml/ref/alter_table.sgml
+++ b/doc/src/sgml/ref/alter_table.sgml
@@ -77,12 +77,15 @@ ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable>
NOT OF
OWNER TO { <replaceable class="PARAMETER">new_owner</replaceable> | CURRENT_USER | SESSION_USER }
REPLICA IDENTITY { DEFAULT | USING INDEX <replaceable class="PARAMETER">index_name</replaceable> | FULL | NOTHING }
+ ATTACH PARTITION <replaceable class="PARAMETER">name</replaceable> <replaceable class="PARAMETER">partition_bound_spec</replaceable> [ VALIDATE | NO VALIDATE ]
+ DETACH PARTITION <replaceable class="PARAMETER">name</replaceable>
<phrase>and <replaceable class="PARAMETER">table_constraint_using_index</replaceable> is:</phrase>
[ CONSTRAINT <replaceable class="PARAMETER">constraint_name</replaceable> ]
{ UNIQUE | PRIMARY KEY } USING INDEX <replaceable class="PARAMETER">index_name</replaceable>
[ DEFERRABLE | NOT DEFERRABLE ] [ INITIALLY DEFERRED | INITIALLY IMMEDIATE ]
+
</synopsis>
</refsynopsisdiv>
@@ -704,6 +707,51 @@ ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable>
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>ATTACH PARTITION</literal></term>
+ <listitem>
+ <para>
+ This form attaches an existing table as partition of the target table,
+ provided the target table is partitioned. The partition bound
+ specification must correspond with partition method and key of the
+ partitioned table. The table being attached must have all the columns
+ as the target table and no more. Also, it must have all the matching
+ constraints as the target table. That includes <literal>NOT NULL</>
+ and <literal>CHECK</> constraints. <literal>UNIQUE</literal>,
+ <literal>PRIMARY KEY</literal>, and <literal>FOREIGN KEY</literal>
+ constraints are not considered, but this might change in the future.
+ </para>
+
+ <para>
+ If <literal>NO VALIDATE</literal> clause is specified, existing rows in
+ source table are are not checked to see if they violate the partition
+ bound specification. This allows to quickly load a large amount of data
+ into the partitioned table. The default behavior is to perform the
+ check, as if <literal>VALIDATE</literal> clause were specified.
+ </para>
+
+ <para>
+ Note that once the table becomes a partition, it will be dropped or
+ truncated when the parent table is dropped or truncated. Dropping
+ it directly using <literal>DROP TABLE</literal> will fail; it must
+ first be detached from the parent. However, truncating a partition
+ directly works.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><literal>DETACH PARTITION</literal></term>
+ <listitem>
+ <para>
+ This form detaches specified partition of the target table, provided the
+ target table is partitioned and the specified partition exists. The
+ resulting table continues to exist as a standalone table with no ties
+ remaining with the parent partitioned table.
+ </para>
+ </listitem>
+ </varlistentry>
+
</variablelist>
</para>
@@ -936,6 +984,15 @@ ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable>
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><replaceable class="PARAMETER">partition_bound_spec</replaceable></term>
+ <listitem>
+ <para>
+ The partition bound specification for a new partition.
+ </para>
+ </listitem>
+ </varlistentry>
+
</variablelist>
</refsect1>
diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml
index 70a7579..79fb521 100644
--- a/doc/src/sgml/ref/create_table.sgml
+++ b/doc/src/sgml/ref/create_table.sgml
@@ -44,6 +44,17 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
[ ON COMMIT { PRESERVE ROWS | DELETE ROWS | DROP } ]
[ TABLESPACE <replaceable class="PARAMETER">tablespace_name</replaceable> ]
+CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name</replaceable>
+ PARTITION OF <replaceable class="PARAMETER">parent_table</replaceable> [ (
+ { <replaceable class="PARAMETER">column_name</replaceable> WITH OPTIONS [ <replaceable class="PARAMETER">column_constraint</replaceable> [ ... ] ]
+ | <replaceable>table_constraint</replaceable> }
+ [, ... ]
+) ] <replaceable class="PARAMETER">partition_bound_spec</replaceable>
+[ PARTITION BY {RANGE | LIST} ( { <replaceable class="parameter">column_name</replaceable> | ( <replaceable class="parameter">expression</replaceable> ) } [ <replaceable class="parameter">opclass</replaceable> ] [, ...] )
+[ WITH ( <replaceable class="PARAMETER">storage_parameter</replaceable> [= <replaceable class="PARAMETER">value</replaceable>] [, ... ] ) | WITH OIDS | WITHOUT OIDS ]
+[ ON COMMIT { PRESERVE ROWS | DELETE ROWS | DROP } ]
+[ TABLESPACE <replaceable class="PARAMETER">tablespace_name</replaceable> ]
+
<phrase>where <replaceable class="PARAMETER">column_constraint</replaceable> is:</phrase>
[ CONSTRAINT <replaceable class="PARAMETER">constraint_name</replaceable> ]
@@ -72,6 +83,10 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
{ INCLUDING | EXCLUDING } { DEFAULTS | CONSTRAINTS | INDEXES | STORAGE | COMMENTS | ALL }
+<phrase>and <replaceable class="PARAMETER">partition_bound_spec</replaceable> is:</phrase>
+
+FOR VALUES { <replaceable class="PARAMETER">list_spec</replaceable> | <replaceable class="PARAMETER">range_spec</replaceable> }
+
<phrase><replaceable class="PARAMETER">index_parameters</replaceable> in <literal>UNIQUE</literal>, <literal>PRIMARY KEY</literal>, and <literal>EXCLUDE</literal> constraints are:</phrase>
[ WITH ( <replaceable class="PARAMETER">storage_parameter</replaceable> [= <replaceable class="PARAMETER">value</replaceable>] [, ... ] ) ]
@@ -80,8 +95,20 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
<phrase><replaceable class="PARAMETER">exclude_element</replaceable> in an <literal>EXCLUDE</literal> constraint is:</phrase>
{ <replaceable class="parameter">column_name</replaceable> | ( <replaceable class="parameter">expression</replaceable> ) } [ <replaceable class="parameter">opclass</replaceable> ] [ ASC | DESC ] [ NULLS { FIRST | LAST } ]
-</synopsis>
+<phrase><replaceable class="PARAMETER">list_spec</replaceable> in <literal>FOR VALUES</literal> is:</phrase>
+
+IN ( <replaceable class="PARAMETER">expression</replaceable> [, ...] )
+
+<phrase><replaceable class="PARAMETER">range_spec</replaceable> in <literal>FOR VALUES</literal> is:</phrase>
+
+START <replaceable class="PARAMETER">lower-bound</replaceable> [ INCLUSIVE | EXCLUSIVE ] END <replaceable class="PARAMETER">upper-bound</replaceable> [ INCLUSIVE | EXCLUSIVE ]
+
+<phrase>where <replaceable class="PARAMETER">lower-bound</replaceable> and <replaceable class="PARAMETER">upper-bound</replaceable> are:</phrase>
+
+{ ( <replaceable class="PARAMETER">expression</replaceable> [, ...] ) | UNBOUNDED }
+
+</synopsis>
</refsynopsisdiv>
<refsect1 id="SQL-CREATETABLE-description">
@@ -232,6 +259,49 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
</varlistentry>
<varlistentry>
+ <term><literal>PARTITION OF <replaceable class="PARAMETER">parent_table</replaceable></literal></term>
+ <listitem>
+ <para>
+ Creates the table as <firstterm>partition</firstterm> of the specified
+ parent table (name optionally schema-qualified).
+ </para>
+
+ <para>
+ A partition bound specification must be present and must correspond with
+ partition method and key of the parent table. It is checked using the
+ specification that the new partition does not overlap with any existing
+ partitions of the parent.
+ </para>
+
+ <para>
+ A partition cannot have columns other than those inherited from the
+ parent. That includes the <structfield>oid</> column, which can be
+ specified using the <literal>WITH (OIDS)</literal> clause. On the other
+ hand, if parent has the <structfield>oid</> column, the partition
+ inherits the same, overriding the <literal>WITH (OIDS=FALSE)</literal>
+ clause, if any. Defaults and constraints can optionally be specified
+ for each of the inherited columns, which override those in the parent.
+ One can also specify table constraints, in addition to those inherited
+ from the parent. Note that all subsequent schema modifications to the
+ parent propagate to partition.
+ </para>
+
+ <para>
+ Any data row subsequently inserted into the parent table is mapped to
+ and stored in the partition, provided partition key of the row falls
+ within the partition bounds.
+ </para>
+
+ <para>
+ A partition is dropped or truncated when the parent table is dropped or
+ truncated. Dropping it directly using <literal>DROP TABLE</literal>
+ will fail; it must first be <firstterm>detached</> from the parent.
+ However, truncating a partition directly works.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><replaceable class="PARAMETER">column_name</replaceable></term>
<listitem>
<para>
@@ -340,6 +410,11 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
</para>
<para>
+ When specifying for a table being created as partition, one needs to
+ use column names from the parent table as part of the key.
+ </para>
+
+ <para>
Currently, there are following limitations on definition of partitioned
tables: one cannot specify any UNIQUE, PRIMARY KEY, EXCLUDE and/or
FOREIGN KEY constraints.
diff --git a/src/backend/commands/createas.c b/src/backend/commands/createas.c
index 5a853c4..b2274e1 100644
--- a/src/backend/commands/createas.c
+++ b/src/backend/commands/createas.c
@@ -342,6 +342,7 @@ intorel_startup(DestReceiver *self, int operation, TupleDesc typeinfo)
col->is_local = true;
col->is_not_null = false;
col->is_from_type = false;
+ col->is_for_partition = false;
col->storage = 0;
col->raw_default = NULL;
col->cooked_default = NULL;
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index 5dcd4cf..21d9f96 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -159,6 +159,7 @@ DefineSequence(CreateSeqStmt *seq)
coldef->is_local = true;
coldef->is_not_null = true;
coldef->is_from_type = false;
+ coldef->is_for_partition = false;
coldef->storage = 0;
coldef->raw_default = NULL;
coldef->cooked_default = NULL;
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index c0a9e6a..db1ee74 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -279,7 +279,8 @@ typedef struct
static void truncate_check_rel(Relation rel);
static List *MergeAttributes(List *schema, List *supers, char relpersistence,
- List **supOids, List **supconstr, int *supOidCount);
+ List **supOids, List **supconstr, int *supOidCount,
+ bool is_partition);
static bool MergeCheckConstraint(List *constraints, char *name, Node *expr);
static void MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel);
static void MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel);
@@ -448,6 +449,11 @@ static void ComputePartitionAttrs(Oid relid, List *partParams,
static bool find_attr_reference_walker(Node *node,
find_attr_reference_context *context);
static bool is_partition_attr(Relation rel, AttrNumber attnum, bool *is_expr);
+static void CreateInheritance(Relation child_rel, Relation parent_rel);
+static void RemoveInheritance(Relation child_rel, Relation parent_rel);
+static ObjectAddress ATExecAttachPartition(List **wqueue, Relation rel,
+ PartitionCmd *cmd);
+static ObjectAddress ATExecDetachPartition(Relation rel, RangeVar *name);
/* ----------------------------------------------------------------
@@ -590,10 +596,16 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
/*
* Look up inheritance ancestors and generate relation schema, including
* inherited attributes.
+ *
+ * The last parameter implicitly specifies that the table is being created
+ * as partition and schema consists of columns definitions corresponding
+ * to non-dropped columns of the parent constructed such that each
+ * attribute of the table is created as inherited and non-local.
*/
schema = MergeAttributes(schema, stmt->inhRelations,
stmt->relation->relpersistence,
- &inheritOids, &old_constraints, &parentOidCount);
+ &inheritOids, &old_constraints, &parentOidCount,
+ stmt->partbound != NULL);
/*
* Create a tuple descriptor from the relation schema. Note that this
@@ -1452,7 +1464,8 @@ storage_name(char c)
*/
static List *
MergeAttributes(List *schema, List *supers, char relpersistence,
- List **supOids, List **supconstr, int *supOidCount)
+ List **supOids, List **supconstr, int *supOidCount,
+ bool is_partition)
{
ListCell *entry;
List *inhSchema = NIL;
@@ -1497,8 +1510,8 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
/*
* Typed table column option that does not belong to a column from
- * the type. This works because the columns from the type come
- * first in the list.
+ * the type or the partition parent. This works because the columns
+ * from the type or the partition parent come first in the list.
*/
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_COLUMN),
@@ -1525,6 +1538,18 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
coldef->is_from_type = false;
list_delete_cell(schema, rest, prev);
}
+ else if (coldef->is_for_partition)
+ {
+ /*
+ * merge the column options into the column from the parent
+ */
+ coldef->is_not_null = restdef->is_not_null;
+ coldef->raw_default = restdef->raw_default;
+ coldef->cooked_default = restdef->cooked_default;
+ coldef->constraints = restdef->constraints;
+ coldef->is_for_partition = false; /* job is done */
+ list_delete_cell(schema, rest, prev);
+ }
else
ereport(ERROR,
(errcode(ERRCODE_DUPLICATE_COLUMN),
@@ -1563,8 +1588,12 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
*/
relation = heap_openrv(parent, ShareUpdateExclusiveLock);
- /* Cannot inherit from partitioned tables */
- if (relation->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+ /*
+ * Cannot inherit from a partitioned table unless being created as
+ * partition.
+ */
+ if (relation->rd_rel->relkind == RELKIND_PARTITIONED_REL &&
+ !is_partition)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot inherit from partitioned table \"%s\"",
@@ -1710,6 +1739,7 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
def->is_local = false;
def->is_not_null = attribute->attnotnull;
def->is_from_type = false;
+ def->is_for_partition = false;
def->storage = attribute->attstorage;
def->raw_default = NULL;
def->cooked_default = NULL;
@@ -1838,6 +1868,9 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
* If we had no inherited attributes, the result schema is just the
* explicitly declared columns. Otherwise, we need to merge the declared
* columns into the inherited schema list.
+ *
+ * Note: In case of a partition, there are only inherited attributes that
+ * we copied from the parent as so, in transformPartitionOf().
*/
if (inhSchema != NIL)
{
@@ -1867,16 +1900,20 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
/*
* Yes, try to merge the two column definitions. They must
- * have the same type, typmod, and collation.
+ * have the same type, typmod, and collation. Don't output
+ * the notices, if partition, per note above.
*/
- if (exist_attno == schema_attno)
- ereport(NOTICE,
- (errmsg("merging column \"%s\" with inherited definition",
- attributeName)));
- else
- ereport(NOTICE,
- (errmsg("moving and merging column \"%s\" with inherited definition", attributeName),
- errdetail("User-specified column moved to the position of the inherited column.")));
+ if (!is_partition)
+ {
+ if (exist_attno == schema_attno)
+ ereport(NOTICE,
+ (errmsg("merging column \"%s\" with inherited definition",
+ attributeName)));
+ else
+ ereport(NOTICE,
+ (errmsg("moving and merging column \"%s\" with inherited definition", attributeName),
+ errdetail("User-specified column moved to the position of the inherited column.")));
+ }
def = (ColumnDef *) list_nth(inhSchema, exist_attno - 1);
typenameTypeIdAndMod(NULL, def->typeName, &defTypeId, &deftypmod);
typenameTypeIdAndMod(NULL, newdef->typeName, &newTypeId, &newtypmod);
@@ -1913,8 +1950,9 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
storage_name(def->storage),
storage_name(newdef->storage))));
- /* Mark the column as locally defined */
- def->is_local = true;
+ /* Mark the column as locally defined, unless partition */
+ if (!is_partition)
+ def->is_local = true;
/* Merge of NOT NULL constraints = OR 'em together */
def->is_not_null |= newdef->is_not_null;
/* If new def has a default, override previous default */
@@ -2093,16 +2131,20 @@ StoreCatalogInheritance1(Oid relationId, Oid parentOid,
heap_freetuple(tuple);
/*
- * Store a dependency too
+ * Store a dependency too, iff a regular inheritance relationship, which
+ * it's not, if parent is a partitioned table.
*/
- parentobject.classId = RelationRelationId;
- parentobject.objectId = parentOid;
- parentobject.objectSubId = 0;
- childobject.classId = RelationRelationId;
- childobject.objectId = relationId;
- childobject.objectSubId = 0;
+ if (!relid_is_partitioned(parentOid))
+ {
+ parentobject.classId = RelationRelationId;
+ parentobject.objectId = parentOid;
+ parentobject.objectSubId = 0;
+ childobject.classId = RelationRelationId;
+ childobject.objectId = relationId;
+ childobject.objectSubId = 0;
- recordDependencyOn(&childobject, &parentobject, DEPENDENCY_NORMAL);
+ recordDependencyOn(&childobject, &parentobject, DEPENDENCY_NORMAL);
+ }
/*
* Post creation hook of this inheritance. Since object_access_hook
@@ -3108,6 +3150,11 @@ AlterTableGetLockLevel(List *cmds)
cmd_lockmode = AlterTableGetRelOptionsLockLevel((List *) cmd->def);
break;
+ case AT_AttachPartition:
+ case AT_DetachPartition:
+ cmd_lockmode = AccessExclusiveLock;
+ break;
+
default: /* oops */
elog(ERROR, "unrecognized alter table type: %d",
(int) cmd->subtype);
@@ -3425,6 +3472,12 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
/* No command-specific prep needed */
pass = AT_PASS_MISC;
break;
+ case AT_AttachPartition:
+ case AT_DetachPartition:
+ ATSimplePermissions(rel, ATT_TABLE);
+ /* No command-specific prep needed */
+ pass = AT_PASS_MISC;
+ break;
default: /* oops */
elog(ERROR, "unrecognized alter table type: %d",
(int) cmd->subtype);
@@ -3744,6 +3797,12 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
case AT_GenericOptions:
ATExecGenericOptions(rel, (List *) cmd->def);
break;
+ case AT_AttachPartition:
+ ATExecAttachPartition(wqueue, rel, (PartitionCmd *) cmd->def);
+ break;
+ case AT_DetachPartition:
+ ATExecDetachPartition(rel, ((PartitionCmd *) cmd->def)->name);
+ break;
default: /* oops */
elog(ERROR, "unrecognized alter table type: %d",
(int) cmd->subtype);
@@ -10188,12 +10247,7 @@ ATPrepAddInherit(Relation child_rel)
static ObjectAddress
ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode)
{
- Relation parent_rel,
- catalogRelation;
- SysScanDesc scan;
- ScanKeyData key;
- HeapTuple inheritsTuple;
- int32 inhseqno;
+ Relation parent_rel;
List *children;
ObjectAddress address;
@@ -10238,38 +10292,6 @@ ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode)
errmsg("cannot inherit from partitioned table \"%s\"", parent->relname)));
/*
- * Check for duplicates in the list of parents, and determine the highest
- * inhseqno already present; we'll use the next one for the new parent.
- * (Note: get RowExclusiveLock because we will write pg_inherits below.)
- *
- * Note: we do not reject the case where the child already inherits from
- * the parent indirectly; CREATE TABLE doesn't reject comparable cases.
- */
- catalogRelation = heap_open(InheritsRelationId, RowExclusiveLock);
- ScanKeyInit(&key,
- Anum_pg_inherits_inhrelid,
- BTEqualStrategyNumber, F_OIDEQ,
- ObjectIdGetDatum(RelationGetRelid(child_rel)));
- scan = systable_beginscan(catalogRelation, InheritsRelidSeqnoIndexId,
- true, NULL, 1, &key);
-
- /* inhseqno sequences start at 1 */
- inhseqno = 0;
- while (HeapTupleIsValid(inheritsTuple = systable_getnext(scan)))
- {
- Form_pg_inherits inh = (Form_pg_inherits) GETSTRUCT(inheritsTuple);
-
- if (inh->inhparent == RelationGetRelid(parent_rel))
- ereport(ERROR,
- (errcode(ERRCODE_DUPLICATE_TABLE),
- errmsg("relation \"%s\" would be inherited from more than once",
- RelationGetRelationName(parent_rel))));
- if (inh->inhseqno > inhseqno)
- inhseqno = inh->inhseqno;
- }
- systable_endscan(scan);
-
- /*
* Prevent circularity by seeing if proposed parent inherits from child.
* (In particular, this disallows making a rel inherit from itself.)
*
@@ -10302,6 +10324,69 @@ ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode)
RelationGetRelationName(child_rel),
RelationGetRelationName(parent_rel))));
+ /* OK to create inheritance */
+ CreateInheritance(child_rel, parent_rel);
+
+ ObjectAddressSet(address, RelationRelationId,
+ RelationGetRelid(parent_rel));
+
+ /* keep our lock on the parent relation until commit */
+ heap_close(parent_rel, NoLock);
+
+ return address;
+}
+
+/*
+ * CreateInheritance
+ * Catalog manipulation portion of creating inheritance between a child
+ * table and a parent table.
+ *
+ * Common to ATExecAddInherit() and ATExecAttachPartition().
+ */
+static void
+CreateInheritance(Relation child_rel, Relation parent_rel)
+{
+ Relation catalogRelation;
+ SysScanDesc scan;
+ ScanKeyData key;
+ HeapTuple inheritsTuple;
+ int32 inhseqno;
+
+ /* Note: get RowExclusiveLock because we will write pg_inherits below. */
+ catalogRelation = heap_open(InheritsRelationId, RowExclusiveLock);
+
+ /*
+ * Check for duplicates in the list of parents, and determine the highest
+ * inhseqno already present; we'll use the next one for the new parent.
+ * Also, if proposed child is a partition, it cannot already be inheriting.
+ *
+ * Note: we do not reject the case where the child already inherits from
+ * the parent indirectly; CREATE TABLE doesn't reject comparable cases.
+ */
+ ScanKeyInit(&key,
+ Anum_pg_inherits_inhrelid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(RelationGetRelid(child_rel)));
+ scan = systable_beginscan(catalogRelation, InheritsRelidSeqnoIndexId,
+ true, NULL, 1, &key);
+
+ /* inhseqno sequences start at 1 */
+ inhseqno = 0;
+ while (HeapTupleIsValid(inheritsTuple = systable_getnext(scan)))
+ {
+ Form_pg_inherits inh = (Form_pg_inherits) GETSTRUCT(inheritsTuple);
+
+ if (inh->inhparent == RelationGetRelid(parent_rel))
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_TABLE),
+ errmsg("relation \"%s\" would be inherited from more than once",
+ RelationGetRelationName(parent_rel))));
+
+ if (inh->inhseqno > inhseqno)
+ inhseqno = inh->inhseqno;
+ }
+ systable_endscan(scan);
+
/* Match up the columns and bump attinhcount as needed */
MergeAttributesIntoExisting(child_rel, parent_rel);
@@ -10316,16 +10401,8 @@ ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode)
inhseqno + 1,
catalogRelation);
- ObjectAddressSet(address, RelationRelationId,
- RelationGetRelid(parent_rel));
-
/* Now we're done with pg_inherits */
heap_close(catalogRelation, RowExclusiveLock);
-
- /* keep our lock on the parent relation until commit */
- heap_close(parent_rel, NoLock);
-
- return address;
}
/*
@@ -10376,7 +10453,7 @@ constraints_equivalent(HeapTuple a, HeapTuple b, TupleDesc tupleDesc)
* Check columns in child table match up with columns in parent, and increment
* their attinhcount.
*
- * Called by ATExecAddInherit
+ * Called by CreateInheritance
*
* Currently all parent columns must be found in child. Missing columns are an
* error. One day we might consider creating new columns like CREATE TABLE
@@ -10394,11 +10471,13 @@ MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel)
int parent_natts;
TupleDesc tupleDesc;
HeapTuple tuple;
+ bool is_attach_partition;
attrrel = heap_open(AttributeRelationId, RowExclusiveLock);
tupleDesc = RelationGetDescr(parent_rel);
parent_natts = tupleDesc->natts;
+ is_attach_partition = relid_is_partitioned(RelationGetRelid(parent_rel));
for (parent_attno = 1; parent_attno <= parent_natts; parent_attno++)
{
@@ -10421,14 +10500,16 @@ MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel)
attribute->atttypmod != childatt->atttypmod)
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
- errmsg("child table \"%s\" has different type for column \"%s\"",
+ errmsg("%s table \"%s\" has different type for column \"%s\"",
+ is_attach_partition ? "source" : "child",
RelationGetRelationName(child_rel),
attributeName)));
if (attribute->attcollation != childatt->attcollation)
ereport(ERROR,
(errcode(ERRCODE_COLLATION_MISMATCH),
- errmsg("child table \"%s\" has different collation for column \"%s\"",
+ errmsg("%s table \"%s\" has different collation for column \"%s\"",
+ is_attach_partition ? "source" : "child",
RelationGetRelationName(child_rel),
attributeName)));
@@ -10439,8 +10520,9 @@ MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel)
if (attribute->attnotnull && !childatt->attnotnull)
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
- errmsg("column \"%s\" in child table must be marked NOT NULL",
- attributeName)));
+ errmsg("column \"%s\" in %s table must be marked NOT NULL",
+ attributeName,
+ is_attach_partition ? "source" : "child")));
/*
* OK, bump the child column's inheritance count. (If we fail
@@ -10455,7 +10537,8 @@ MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel)
{
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
- errmsg("child table is missing column \"%s\"",
+ errmsg("%s table is missing column \"%s\"",
+ is_attach_partition ? "source" : "child",
attributeName)));
}
}
@@ -10469,7 +10552,7 @@ MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel)
*
* Constraints that are marked ONLY in the parent are ignored.
*
- * Called by ATExecAddInherit
+ * Called by CreateInheritance
*
* Currently all constraints in parent must be present in the child. One day we
* may consider adding new constraints like CREATE TABLE does.
@@ -10488,9 +10571,11 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
SysScanDesc parent_scan;
ScanKeyData parent_key;
HeapTuple parent_tuple;
+ bool is_attach_partition;
catalog_relation = heap_open(ConstraintRelationId, RowExclusiveLock);
tuple_desc = RelationGetDescr(catalog_relation);
+ is_attach_partition = relid_is_partitioned(RelationGetRelid(parent_rel));
/* Outer loop scans through the parent's constraint definitions */
ScanKeyInit(&parent_key,
@@ -10538,7 +10623,8 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
if (!constraints_equivalent(parent_tuple, child_tuple, tuple_desc))
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
- errmsg("child table \"%s\" has different definition for check constraint \"%s\"",
+ errmsg("%s table \"%s\" has different definition for check constraint \"%s\"",
+ is_attach_partition ? "source" : "child",
RelationGetRelationName(child_rel),
NameStr(parent_con->conname))));
@@ -10546,8 +10632,9 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
if (child_con->connoinherit)
ereport(ERROR,
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
- errmsg("constraint \"%s\" conflicts with non-inherited constraint on child table \"%s\"",
+ errmsg("constraint \"%s\" conflicts with non-inherited constraint on %s table \"%s\"",
NameStr(child_con->conname),
+ is_attach_partition ? "source" : "child",
RelationGetRelationName(child_rel))));
/*
@@ -10570,7 +10657,8 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
if (!found)
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
- errmsg("child table is missing constraint \"%s\"",
+ errmsg("%s table is missing constraint \"%s\"",
+ is_attach_partition ? "source" : "child",
NameStr(parent_con->conname))));
}
@@ -10581,6 +10669,41 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
/*
* ALTER TABLE NO INHERIT
*
+ * Return value is the address of the relation that is no longer parent.
+ */
+static ObjectAddress
+ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
+{
+ ObjectAddress address;
+ Relation parent_rel;
+
+ /*
+ * AccessShareLock on the parent is probably enough, seeing that DROP
+ * TABLE doesn't lock parent tables at all. We need some lock since we'll
+ * be inspecting the parent's schema.
+ */
+ parent_rel = heap_openrv(parent, AccessShareLock);
+
+ /*
+ * We don't bother to check ownership of the parent table --- ownership of
+ * the child is presumed enough rights.
+ */
+
+ /* Off to RemoveInheritance() where most of the work happens */
+ RemoveInheritance(rel, parent_rel);
+
+ /* keep our lock on the parent relation until commit */
+ heap_close(parent_rel, NoLock);
+
+ ObjectAddressSet(address, RelationRelationId,
+ RelationGetRelid(parent_rel));
+
+ return address;
+}
+
+/*
+ * RemoveInheritance
+ *
* Drop a parent from the child's parents. This just adjusts the attinhcount
* and attislocal of the columns and removes the pg_inherit and pg_depend
* entries.
@@ -10594,13 +10717,11 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
* coninhcount and conislocal for inherited constraints are adjusted in
* exactly the same way.
*
- * Return value is the address of the relation that is no longer parent.
+ * Common to ATExecDropInherit() and ATExecDetachPartition().
*/
-static ObjectAddress
-ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
+static void
+RemoveInheritance(Relation child_rel, Relation parent_rel)
{
- Relation parent_rel;
- Oid parent_oid;
Relation catalogRelation;
SysScanDesc scan;
ScanKeyData key[3];
@@ -10609,19 +10730,9 @@ ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
constraintTuple;
List *connames;
bool found = false;
- ObjectAddress address;
+ bool is_detach_partition;
- /*
- * AccessShareLock on the parent is probably enough, seeing that DROP
- * TABLE doesn't lock parent tables at all. We need some lock since we'll
- * be inspecting the parent's schema.
- */
- parent_rel = heap_openrv(parent, AccessShareLock);
-
- /*
- * We don't bother to check ownership of the parent table --- ownership of
- * the child is presumed enough rights.
- */
+ is_detach_partition = relid_is_partitioned(RelationGetRelid(parent_rel));
/*
* Find and destroy the pg_inherits entry linking the two, or error out if
@@ -10631,7 +10742,7 @@ ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
ScanKeyInit(&key[0],
Anum_pg_inherits_inhrelid,
BTEqualStrategyNumber, F_OIDEQ,
- ObjectIdGetDatum(RelationGetRelid(rel)));
+ ObjectIdGetDatum(RelationGetRelid(child_rel)));
scan = systable_beginscan(catalogRelation, InheritsRelidSeqnoIndexId,
true, NULL, 1, key);
@@ -10652,11 +10763,20 @@ ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
heap_close(catalogRelation, RowExclusiveLock);
if (!found)
- ereport(ERROR,
- (errcode(ERRCODE_UNDEFINED_TABLE),
- errmsg("relation \"%s\" is not a parent of relation \"%s\"",
- RelationGetRelationName(parent_rel),
- RelationGetRelationName(rel))));
+ {
+ if (!is_detach_partition)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_TABLE),
+ errmsg("relation \"%s\" is not a parent of relation \"%s\"",
+ RelationGetRelationName(parent_rel),
+ RelationGetRelationName(child_rel))));
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_TABLE),
+ errmsg("relation \"%s\" is not a partition of relation \"%s\"",
+ RelationGetRelationName(child_rel),
+ RelationGetRelationName(parent_rel))));
+ }
/*
* Search through child columns looking for ones matching parent rel
@@ -10665,7 +10785,7 @@ ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
ScanKeyInit(&key[0],
Anum_pg_attribute_attrelid,
BTEqualStrategyNumber, F_OIDEQ,
- ObjectIdGetDatum(RelationGetRelid(rel)));
+ ObjectIdGetDatum(RelationGetRelid(child_rel)));
scan = systable_beginscan(catalogRelation, AttributeRelidNumIndexId,
true, NULL, 1, key);
while (HeapTupleIsValid(attributeTuple = systable_getnext(scan)))
@@ -10727,7 +10847,7 @@ ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
ScanKeyInit(&key[0],
Anum_pg_constraint_conrelid,
BTEqualStrategyNumber, F_OIDEQ,
- ObjectIdGetDatum(RelationGetRelid(rel)));
+ ObjectIdGetDatum(RelationGetRelid(child_rel)));
scan = systable_beginscan(catalogRelation, ConstraintRelidIndexId,
true, NULL, 1, key);
@@ -10758,7 +10878,7 @@ ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
if (copy_con->coninhcount <= 0) /* shouldn't happen */
elog(ERROR, "relation %u has non-inherited constraint \"%s\"",
- RelationGetRelid(rel), NameStr(copy_con->conname));
+ RelationGetRelid(child_rel), NameStr(copy_con->conname));
copy_con->coninhcount--;
if (copy_con->coninhcount == 0)
@@ -10770,30 +10890,22 @@ ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
}
}
- parent_oid = RelationGetRelid(parent_rel);
-
systable_endscan(scan);
heap_close(catalogRelation, RowExclusiveLock);
- drop_parent_dependency(RelationGetRelid(rel),
- RelationRelationId,
- RelationGetRelid(parent_rel));
-
+ /* Remember that dependency exists iff regular inheritance. */
+ if (!is_detach_partition)
+ drop_parent_dependency(RelationGetRelid(child_rel),
+ RelationRelationId,
+ RelationGetRelid(parent_rel));
/*
* Post alter hook of this inherits. Since object_access_hook doesn't take
* multiple object identifiers, we relay oid of parent relation using
* auxiliary_id argument.
*/
InvokeObjectPostAlterHookArg(InheritsRelationId,
- RelationGetRelid(rel), 0,
+ RelationGetRelid(child_rel), 0,
RelationGetRelid(parent_rel), false);
-
- /* keep our lock on the parent relation until commit */
- heap_close(parent_rel, NoLock);
-
- ObjectAddressSet(address, RelationRelationId, parent_oid);
-
- return address;
}
/*
@@ -12426,3 +12538,137 @@ ComputePartitionAttrs(Oid relid, List *partParams, AttrNumber *partattrs,
partopclass[attn++] = opclassOid;
}
}
+
+
+/*
+ * ALTER TABLE <name> ATTACH PARTITION <partition-name> FOR VALUES
+ *
+ * Return the address of the newly attached partition.
+ */
+static ObjectAddress
+ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
+{
+ Relation attachRel,
+ inheritsRelation;
+ SysScanDesc scan;
+ ScanKeyData key;
+ HeapTuple tuple;
+ AttrNumber attno;
+ int natts;
+ TupleDesc tupleDesc;
+ ObjectAddress address;
+
+ attachRel = heap_openrv(cmd->name, AccessShareLock);
+
+ /*
+ * Must be owner of both parent and source table -- parent was checked by
+ * ATSimplePermissions call in ATPrepCmd
+ */
+ ATSimplePermissions(attachRel, ATT_TABLE);
+
+ if (attachRel->rd_rel->reloftype)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot attach a typed table as partition")));
+
+ if (attachRel->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot attach a partitioned table as partition")));
+
+ /* attachRel should not already be a inheritance child of some relation */
+ inheritsRelation = heap_open(InheritsRelationId, AccessShareLock);
+ ScanKeyInit(&key,
+ Anum_pg_inherits_inhrelid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(RelationGetRelid(attachRel)));
+ scan = systable_beginscan(inheritsRelation, InheritsRelidSeqnoIndexId,
+ true, NULL, 1, &key);
+ if (HeapTupleIsValid(systable_getnext(scan)))
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot attach table that inherits as partition")));
+ systable_endscan(scan);
+ heap_close(inheritsRelation, AccessShareLock);
+
+ /* If attachRel is temp, it must belong to this session */
+ if (attachRel->rd_rel->relpersistence == RELPERSISTENCE_TEMP &&
+ !attachRel->rd_islocaltemp)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot attach a temporary relation of another session as partition ")));
+
+ /* If parent has OIDs, attachRel must have OIDs */
+ if (rel->rd_rel->relhasoids && !attachRel->rd_rel->relhasoids)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot attach table \"%s\" without OIDs as partition of"
+ " table \"%s\" with OIDs", RelationGetRelationName(attachRel),
+ RelationGetRelationName(rel))));
+
+ /* OTOH, if parent doesn't have them, do not allow in attachRel either */
+ if (attachRel->rd_rel->relhasoids && !rel->rd_rel->relhasoids)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot attach table \"%s\" with OIDs as partition of table"
+ " \"%s\" without OIDs", RelationGetRelationName(attachRel),
+ RelationGetRelationName(rel))));
+
+ /* Check if there are any columns in attachRel that aren't in the parent */
+ tupleDesc = RelationGetDescr(attachRel);
+ natts = tupleDesc->natts;
+ for (attno = 1; attno <= natts; attno++)
+ {
+ Form_pg_attribute attribute = tupleDesc->attrs[attno - 1];
+ char *attributeName = NameStr(attribute->attname);
+
+ /* Ignore dropped */
+ if (attribute->attisdropped)
+ continue;
+
+ /* Find same column in parent (matching on column name). */
+ tuple = SearchSysCacheCopyAttName(RelationGetRelid(rel), attributeName);
+ if (!HeapTupleIsValid(tuple))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("table \"%s\" contains column \"%s\" not found in parent \"%s\"",
+ RelationGetRelationName(attachRel), attributeName,
+ RelationGetRelationName(rel)),
+ errdetail("Table being attached should contain only the columns"
+ " present in parent.")));
+ }
+
+ /* OK to create inheritance. Rest of the checks performed there */
+ CreateInheritance(attachRel, rel);
+
+ ObjectAddressSet(address, RelationRelationId, RelationGetRelid(attachRel));
+
+ /* keep our lock until commit */
+ heap_close(attachRel, NoLock);
+
+ return address;
+}
+
+/*
+ * ALTER TABLE DETACH PARTITION
+ *
+ * Return the address of the relation that is no longer a partition of rel.
+ */
+static ObjectAddress
+ATExecDetachPartition(Relation rel, RangeVar *name)
+{
+ Relation partRel;
+ ObjectAddress address;
+
+ partRel = heap_openrv(name, AccessShareLock);
+
+ /* All inheritance related checks are performed within the function */
+ RemoveInheritance(partRel, rel);
+
+ ObjectAddressSet(address, RelationRelationId, RelationGetRelid(partRel));
+
+ /* keep our lock until commit */
+ heap_close(partRel, NoLock);
+
+ return address;
+}
diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c
index e9d9ba2..c9c72d9 100644
--- a/src/backend/commands/view.c
+++ b/src/backend/commands/view.c
@@ -95,6 +95,7 @@ DefineVirtualRelation(RangeVar *relation, List *tlist, bool replace,
def->is_local = true;
def->is_not_null = false;
def->is_from_type = false;
+ def->is_for_partition = false;
def->storage = 0;
def->raw_default = NULL;
def->cooked_default = NULL;
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index dfaac9f..62218f0 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2606,6 +2606,7 @@ _copyColumnDef(const ColumnDef *from)
COPY_SCALAR_FIELD(is_local);
COPY_SCALAR_FIELD(is_not_null);
COPY_SCALAR_FIELD(is_from_type);
+ COPY_SCALAR_FIELD(is_for_partition);
COPY_SCALAR_FIELD(storage);
COPY_NODE_FIELD(raw_default);
COPY_NODE_FIELD(cooked_default);
@@ -3001,6 +3002,7 @@ CopyCreateStmtFields(const CreateStmt *from, CreateStmt *newnode)
COPY_NODE_FIELD(relation);
COPY_NODE_FIELD(tableElts);
COPY_NODE_FIELD(inhRelations);
+ COPY_NODE_FIELD(partbound);
COPY_NODE_FIELD(partby);
COPY_NODE_FIELD(ofTypename);
COPY_NODE_FIELD(constraints);
@@ -4183,6 +4185,43 @@ _copyPartitionElem(const PartitionElem *from)
return newnode;
}
+static PartitionListSpec *
+_copyPartitionListSpec(const PartitionListSpec *from)
+{
+ PartitionListSpec *newnode = makeNode(PartitionListSpec);
+
+ COPY_NODE_FIELD(values);
+ COPY_LOCATION_FIELD(location);
+
+ return newnode;
+}
+
+static PartitionRangeSpec *
+_copyPartitionRangeSpec(const PartitionRangeSpec *from)
+{
+ PartitionRangeSpec *newnode = makeNode(PartitionRangeSpec);
+
+ COPY_SCALAR_FIELD(lowerinc);
+ COPY_NODE_FIELD(lower);
+ COPY_SCALAR_FIELD(upperinc);
+ COPY_NODE_FIELD(upper);
+ COPY_LOCATION_FIELD(location);
+
+ return newnode;
+}
+
+static PartitionCmd *
+_copyPartitionCmd(const PartitionCmd *from)
+{
+ PartitionCmd *newnode = makeNode(PartitionCmd);
+
+ COPY_NODE_FIELD(name);
+ COPY_NODE_FIELD(bound);
+ COPY_SCALAR_FIELD(skip_validate);
+
+ return newnode;
+}
+
/* ****************************************************************
* pg_list.h copy functions
* ****************************************************************
@@ -5082,6 +5121,15 @@ copyObject(const void *from)
case T_PartitionElem:
retval = _copyPartitionElem(from);
break;
+ case T_PartitionListSpec:
+ retval = _copyPartitionListSpec(from);
+ break;
+ case T_PartitionRangeSpec:
+ retval = _copyPartitionRangeSpec(from);
+ break;
+ case T_PartitionCmd:
+ retval = _copyPartitionCmd(from);
+ break;
default:
elog(ERROR, "unrecognized node type: %d", (int) nodeTag(from));
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 56325fe..1a25acf 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1156,6 +1156,7 @@ _equalCreateStmt(const CreateStmt *a, const CreateStmt *b)
COMPARE_NODE_FIELD(relation);
COMPARE_NODE_FIELD(tableElts);
COMPARE_NODE_FIELD(inhRelations);
+ COMPARE_NODE_FIELD(partbound);
COMPARE_NODE_FIELD(partby);
COMPARE_NODE_FIELD(ofTypename);
COMPARE_NODE_FIELD(constraints);
@@ -2363,6 +2364,7 @@ _equalColumnDef(const ColumnDef *a, const ColumnDef *b)
COMPARE_SCALAR_FIELD(is_local);
COMPARE_SCALAR_FIELD(is_not_null);
COMPARE_SCALAR_FIELD(is_from_type);
+ COMPARE_SCALAR_FIELD(is_for_partition);
COMPARE_SCALAR_FIELD(storage);
COMPARE_NODE_FIELD(raw_default);
COMPARE_NODE_FIELD(cooked_default);
@@ -2641,6 +2643,37 @@ _equalPartitionElem(const PartitionElem *a, const PartitionElem *b)
return true;
}
+static bool
+_equalPartitionListSpec(const PartitionListSpec *a, const PartitionListSpec *b)
+{
+ COMPARE_NODE_FIELD(values);
+ COMPARE_LOCATION_FIELD(location);
+
+ return true;
+}
+
+static bool
+_equalPartitionRangeSpec(const PartitionRangeSpec *a, const PartitionRangeSpec *b)
+{
+ COMPARE_SCALAR_FIELD(lowerinc);
+ COMPARE_NODE_FIELD(lower);
+ COMPARE_SCALAR_FIELD(upperinc);
+ COMPARE_NODE_FIELD(upper);
+ COMPARE_LOCATION_FIELD(location);
+
+ return true;
+}
+
+static bool
+_equalPartitionCmd(const PartitionCmd *a, const PartitionCmd *b)
+{
+ COMPARE_NODE_FIELD(name);
+ COMPARE_NODE_FIELD(bound);
+ COMPARE_SCALAR_FIELD(skip_validate);
+
+ return true;
+}
+
/*
* Stuff from pg_list.h
*/
@@ -3397,6 +3430,15 @@ equal(const void *a, const void *b)
case T_PartitionElem:
retval = _equalPartitionElem(a, b);
break;
+ case T_PartitionListSpec:
+ retval = _equalPartitionListSpec(a, b);
+ break;
+ case T_PartitionRangeSpec:
+ retval = _equalPartitionRangeSpec(a, b);
+ break;
+ case T_PartitionCmd:
+ retval = _equalPartitionCmd(a, b);
+ break;
default:
elog(ERROR, "unrecognized node type: %d",
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index b02699a..951d705 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2359,6 +2359,7 @@ _outCreateStmtInfo(StringInfo str, const CreateStmt *node)
WRITE_NODE_FIELD(relation);
WRITE_NODE_FIELD(tableElts);
WRITE_NODE_FIELD(inhRelations);
+ WRITE_NODE_FIELD(partbound);
WRITE_NODE_FIELD(partby);
WRITE_NODE_FIELD(ofTypename);
WRITE_NODE_FIELD(constraints);
@@ -2539,6 +2540,7 @@ _outColumnDef(StringInfo str, const ColumnDef *node)
WRITE_BOOL_FIELD(is_local);
WRITE_BOOL_FIELD(is_not_null);
WRITE_BOOL_FIELD(is_from_type);
+ WRITE_BOOL_FIELD(is_for_partition);
WRITE_CHAR_FIELD(storage);
WRITE_NODE_FIELD(raw_default);
WRITE_NODE_FIELD(cooked_default);
@@ -3229,6 +3231,25 @@ _outPartitionElem(StringInfo str, const PartitionElem *node)
WRITE_LOCATION_FIELD(location);
}
+static void
+_outPartitionListSpec(StringInfo str, const PartitionListSpec *node)
+{
+ WRITE_NODE_TYPE("PARTITIONLISTVALUES");
+
+ WRITE_NODE_FIELD(values);
+}
+
+static void
+_outPartitionRangeSpec(StringInfo str, const PartitionRangeSpec *node)
+{
+ WRITE_NODE_TYPE("PARTITIONRANGE");
+
+ WRITE_BOOL_FIELD(lowerinc);
+ WRITE_NODE_FIELD(lower);
+ WRITE_BOOL_FIELD(upperinc);
+ WRITE_NODE_FIELD(upper);
+}
+
/*
* outNode -
* converts a Node into ascii string and append it to 'str'
@@ -3810,6 +3831,12 @@ outNode(StringInfo str, const void *obj)
case T_PartitionElem:
_outPartitionElem(str, obj);
break;
+ case T_PartitionListSpec:
+ _outPartitionListSpec(str, obj);
+ break;
+ case T_PartitionRangeSpec:
+ _outPartitionRangeSpec(str, obj);
+ break;
default:
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 3757eee..d7e6c05 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -229,6 +229,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
VariableSetStmt *vsetstmt;
PartitionElem *partelem;
PartitionBy *partby;
+ PartitionRangeSpec *partrange;
}
%type <node> stmt schema_stmt
@@ -544,6 +545,14 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <partby> PartitionBy OptPartitionBy
%type <partelem> part_elem
%type <list> part_params
+%type <list> OptPartitionElementList PartitionElementList
+%type <node> PartitionElement
+%type <node> ForValues
+%type <node> partvalue
+%type <list> partvalue_list
+%type <boolean> lb_inc ub_inc
+%type <list> RangeBound
+%type <boolean> opt_validate_spec
/*
* Non-keyword token types. These are hard-wired into the "flex" lexer.
@@ -569,7 +578,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
/* ordinary key words in alphabetical order */
%token <keyword> ABORT_P ABSOLUTE_P ACCESS ACTION ADD_P ADMIN AFTER
AGGREGATE ALL ALSO ALTER ALWAYS ANALYSE ANALYZE AND ANY ARRAY AS ASC
- ASSERTION ASSIGNMENT ASYMMETRIC AT ATTRIBUTE AUTHORIZATION
+ ASSERTION ASSIGNMENT ASYMMETRIC AT ATTACH ATTRIBUTE AUTHORIZATION
BACKWARD BEFORE BEGIN_P BETWEEN BIGINT BINARY BIT
BOOLEAN_P BOTH BY
@@ -585,7 +594,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
DATA_P DATABASE DAY_P DEALLOCATE DEC DECIMAL_P DECLARE DEFAULT DEFAULTS
DEFERRABLE DEFERRED DEFINER DELETE_P DELIMITER DELIMITERS DEPENDS DESC
- DICTIONARY DISABLE_P DISCARD DISTINCT DO DOCUMENT_P DOMAIN_P DOUBLE_P DROP
+ DETACH DICTIONARY DISABLE_P DISCARD DISTINCT DO DOCUMENT_P DOMAIN_P
+ DOUBLE_P DROP
EACH ELSE ENABLE_P ENCODING ENCRYPTED END_P ENUM_P ESCAPE EVENT EXCEPT
EXCLUDE EXCLUDING EXCLUSIVE EXECUTE EXISTS EXPLAIN
@@ -599,7 +609,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
HANDLER HAVING HEADER_P HOLD HOUR_P
IDENTITY_P IF_P ILIKE IMMEDIATE IMMUTABLE IMPLICIT_P IMPORT_P IN_P
- INCLUDING INCREMENT INDEX INDEXES INHERIT INHERITS INITIALLY INLINE_P
+ INCLUDING INCLUSIVE INCREMENT INDEX INDEXES INHERIT INHERITS INITIALLY INLINE_P
INNER_P INOUT INPUT_P INSENSITIVE INSERT INSTEAD INT_P INTEGER
INTERSECT INTERVAL INTO INVOKER IS ISNULL ISOLATION
@@ -2373,6 +2383,38 @@ alter_table_cmd:
n->def = (Node *)$1;
$$ = (Node *) n;
}
+ /* ALTER TABLE <name> ATTACH PARTITION <table_name> FOR VALUES */
+ | ATTACH PARTITION qualified_name ForValues opt_validate_spec
+ {
+ AlterTableCmd *n = makeNode(AlterTableCmd);
+ PartitionCmd *cmd = makeNode(PartitionCmd);
+
+ n->subtype = AT_AttachPartition;
+ cmd->name = $3;
+ cmd->bound = (Node *) $4;
+ cmd->skip_validate = $5;
+ n->def = (Node *) cmd;
+
+ $$ = (Node *) n;
+ }
+ /* ALTER TABLE <name> DETACH PARTITION <partition_name> */
+ | DETACH PARTITION qualified_name
+ {
+ AlterTableCmd *n = makeNode(AlterTableCmd);
+ PartitionCmd *cmd = makeNode(PartitionCmd);
+
+ n->subtype = AT_DetachPartition;
+ cmd->name = $3;
+ n->def = (Node *) cmd;
+
+ $$ = (Node *) n;
+ }
+ ;
+
+opt_validate_spec:
+ NO VALIDATE { $$ = true; }
+ | VALIDATE { $$ = false; }
+ | /* EMPTY */ { $$ = false; }
;
alter_column_default:
@@ -2468,6 +2510,60 @@ reloption_elem:
}
;
+ForValues:
+ /* a LIST partition */
+ FOR VALUES IN_P '(' partvalue_list ')'
+ {
+ PartitionListSpec *n = makeNode(PartitionListSpec);
+
+ n->values = $5;
+ n->location = @1;
+
+ $$ = (Node *) n;
+ }
+
+ /* a RANGE partition */
+ | FOR VALUES START RangeBound lb_inc END_P RangeBound ub_inc
+ {
+ PartitionRangeSpec *n = makeNode(PartitionRangeSpec);
+
+ n->lowerinc = $5;
+ n->lower = $4;
+ n->upperinc = $8;
+ n->upper = $7;
+ n->location = @1;
+
+ $$ = (Node *) n;
+ }
+ ;
+
+RangeBound:
+ UNBOUNDED { $$ = NIL; }
+ | '(' partvalue_list ')' { $$ = $2; }
+ ;
+
+lb_inc:
+ EXCLUSIVE { $$ = false;}
+ | INCLUSIVE { $$ = true; }
+ | /* EMPTY */ { $$ = true; }
+ ;
+
+ub_inc:
+ INCLUSIVE { $$ = true; }
+ | EXCLUSIVE { $$ = false; }
+ | /* EMPTY */ { $$ = false; }
+ ;
+
+partvalue:
+ Sconst { $$ = makeStringConst($1, @1); }
+ | NumericOnly { $$ = makeAConst($1, @1); }
+ | NULL_P { $$ = makeNullAConst(@1); }
+ ;
+
+partvalue_list:
+ partvalue { $$ = list_make1($1); }
+ | partvalue_list ',' partvalue { $$ = lappend($1, $3); }
+ ;
/*****************************************************************************
*
@@ -2885,6 +2981,44 @@ CreateStmt: CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
n->if_not_exists = true;
$$ = (Node *)n;
}
+ | CREATE OptTemp TABLE qualified_name PARTITION OF qualified_name
+ OptPartitionElementList ForValues OptPartitionBy OptWith
+ OnCommitOption OptTableSpace
+ {
+ CreateStmt *n = makeNode(CreateStmt);
+ $4->relpersistence = $2;
+ n->relation = $4;
+ n->tableElts = $8;
+ n->inhRelations = list_make1($7);
+ n->partbound = (Node *) $9;
+ n->partby = $10;
+ n->ofTypename = NULL;
+ n->constraints = NIL;
+ n->options = $11;
+ n->oncommit = $12;
+ n->tablespacename = $13;
+ n->if_not_exists = false;
+ $$ = (Node *)n;
+ }
+ | CREATE OptTemp TABLE IF_P NOT EXISTS qualified_name PARTITION OF
+ qualified_name OptPartitionElementList ForValues OptPartitionBy
+ OptWith OnCommitOption OptTableSpace
+ {
+ CreateStmt *n = makeNode(CreateStmt);
+ $7->relpersistence = $2;
+ n->relation = $7;
+ n->tableElts = $11;
+ n->inhRelations = list_make1($10);
+ n->partbound = (Node *) $12;
+ n->partby = $13;
+ n->ofTypename = NULL;
+ n->constraints = NIL;
+ n->options = $14;
+ n->oncommit = $15;
+ n->tablespacename = $16;
+ n->if_not_exists = true;
+ $$ = (Node *)n;
+ }
;
/*
@@ -2930,6 +3064,11 @@ OptTypedTableElementList:
| /*EMPTY*/ { $$ = NIL; }
;
+OptPartitionElementList:
+ '(' PartitionElementList ')' { $$ = $2; }
+ | /*EMPTY*/ { $$ = NIL; }
+ ;
+
TableElementList:
TableElement
{
@@ -2952,6 +3091,17 @@ TypedTableElementList:
}
;
+PartitionElementList:
+ PartitionElement
+ {
+ $$ = list_make1($1);
+ }
+ | PartitionElementList ',' PartitionElement
+ {
+ $$ = lappend($1, $3);
+ }
+ ;
+
TableElement:
columnDef { $$ = $1; }
| TableLikeClause { $$ = $1; }
@@ -2963,6 +3113,11 @@ TypedTableElement:
| TableConstraint { $$ = $1; }
;
+PartitionElement:
+ columnOptions { $$ = $1; }
+ | TableConstraint { $$ = $1; }
+ ;
+
columnDef: ColId Typename create_generic_options ColQualList
{
ColumnDef *n = makeNode(ColumnDef);
@@ -2972,6 +3127,7 @@ columnDef: ColId Typename create_generic_options ColQualList
n->is_local = true;
n->is_not_null = false;
n->is_from_type = false;
+ n->is_for_partition = false;
n->storage = 0;
n->raw_default = NULL;
n->cooked_default = NULL;
@@ -2993,6 +3149,7 @@ columnOptions: ColId WITH OPTIONS ColQualList
n->is_local = true;
n->is_not_null = false;
n->is_from_type = false;
+ n->is_for_partition = false;
n->storage = 0;
n->raw_default = NULL;
n->cooked_default = NULL;
@@ -11123,6 +11280,7 @@ TableFuncElement: ColId Typename opt_collate_clause
n->is_local = true;
n->is_not_null = false;
n->is_from_type = false;
+ n->is_for_partition = false;
n->storage = 0;
n->raw_default = NULL;
n->cooked_default = NULL;
@@ -13806,6 +13964,7 @@ unreserved_keyword:
| ASSERTION
| ASSIGNMENT
| AT
+ | ATTACH
| ATTRIBUTE
| BACKWARD
| BEFORE
@@ -13852,6 +14011,7 @@ unreserved_keyword:
| DELIMITER
| DELIMITERS
| DEPENDS
+ | DETACH
| DICTIONARY
| DISABLE_P
| DISCARD
@@ -13894,6 +14054,7 @@ unreserved_keyword:
| IMPLICIT_P
| IMPORT_P
| INCLUDING
+ | INCLUSIVE
| INCREMENT
| INDEX
| INDEXES
diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c
index 77ffd84..d71fdfe 100644
--- a/src/backend/parser/parse_agg.c
+++ b/src/backend/parser/parse_agg.c
@@ -493,7 +493,9 @@ check_agglevels_and_constraints(ParseState *pstate, Node *expr)
err = _("grouping operations are not allowed in partition key expression");
break;
-
+ case EXPR_KIND_PARTITION_FOR_VALUES:
+ errkind = true;
+ break;
/*
* There is intentionally no default: case here, so that the
@@ -854,6 +856,9 @@ transformWindowFuncCall(ParseState *pstate, WindowFunc *wfunc,
case EXPR_KIND_PARTITION_KEY:
err = _("window functions are not allowed in partition key expression");
break;
+ case EXPR_KIND_PARTITION_FOR_VALUES:
+ errkind = true;
+ break;
/*
* There is intentionally no default: case here, so that the
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 147c6da..f14d776 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -352,6 +352,26 @@ transformExprRecurse(ParseState *pstate, Node *expr)
result = (Node *) expr;
break;
}
+ case T_PartitionListSpec:
+ {
+ PartitionListSpec *list = (PartitionListSpec *) expr;
+
+ list->values = transformExpressionList(pstate, list->values,
+ pstate->p_expr_kind);
+ result = expr;
+ break;
+ }
+ case T_PartitionRangeSpec:
+ {
+ PartitionRangeSpec *range = (PartitionRangeSpec *) expr;
+
+ range->lower = transformExpressionList(pstate, range->lower,
+ pstate->p_expr_kind);
+ range->upper = transformExpressionList(pstate, range->upper,
+ pstate->p_expr_kind);
+ result = expr;
+ break;
+ }
default:
/* should not reach here */
@@ -1698,6 +1718,7 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
case EXPR_KIND_OFFSET:
case EXPR_KIND_RETURNING:
case EXPR_KIND_VALUES:
+ case EXPR_KIND_PARTITION_FOR_VALUES:
/* okay */
break;
case EXPR_KIND_CHECK_CONSTRAINT:
@@ -3254,6 +3275,8 @@ ParseExprKindName(ParseExprKind exprKind)
return "WHEN";
case EXPR_KIND_PARTITION_KEY:
return "partition key expression";
+ case EXPR_KIND_PARTITION_FOR_VALUES:
+ return "FOR VALUES";
/*
* There is intentionally no default: case here, so that the
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 465597d..e69cbfc 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -48,8 +48,10 @@
#include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "nodes/nodeFuncs.h"
+#include "optimizer/planner.h"
#include "parser/analyze.h"
#include "parser/parse_clause.h"
+#include "parser/parse_coerce.h"
#include "parser/parse_collate.h"
#include "parser/parse_expr.h"
#include "parser/parse_relation.h"
@@ -89,6 +91,7 @@ typedef struct
* the table */
IndexStmt *pkey; /* PRIMARY KEY index, if any */
bool ispartitioned; /* true if table is partitioned */
+ Node *partbound; /* transformed FOR VALUES */
} CreateStmtContext;
/* State shared by transformCreateSchemaStmt and its subroutines */
@@ -131,6 +134,10 @@ static void transformConstraintAttrs(CreateStmtContext *cxt,
List *constraintList);
static void transformColumnType(CreateStmtContext *cxt, ColumnDef *column);
static void setSchemaName(char *context_schema, char **stmt_schema_name);
+static void transformPartitionOf(CreateStmtContext *cxt, Node *bound);
+static void transformAttachPartition(CreateStmtContext *cxt, PartitionCmd *cmd);
+static Node *transformPartitionBound(CreateStmtContext *cxt, Relation parent,
+ Node *bound);
/*
@@ -232,6 +239,7 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
cxt.alist = NIL;
cxt.pkey = NULL;
cxt.ispartitioned = stmt->partby != NULL;
+ cxt.partbound = NULL;
/*
* Notice that we allow OIDs here only for plain tables, even though
@@ -250,6 +258,9 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
if (stmt->ofTypename)
transformOfType(&cxt, stmt->ofTypename);
+ if (stmt->partbound)
+ transformPartitionOf(&cxt, stmt->partbound);
+
if (stmt->partby)
{
int partnatts = list_length(stmt->partby->partParams);
@@ -355,6 +366,7 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
*/
stmt->tableElts = cxt.columns;
stmt->constraints = cxt.ckconstraints;
+ stmt->partbound = cxt.partbound;
result = lappend(cxt.blist, stmt);
result = list_concat(result, cxt.alist);
@@ -894,6 +906,7 @@ transformTableLikeClause(CreateStmtContext *cxt, TableLikeClause *table_like_cla
def->is_local = true;
def->is_not_null = attribute->attnotnull;
def->is_from_type = false;
+ def->is_for_partition = false;
def->storage = 0;
def->raw_default = NULL;
def->cooked_default = NULL;
@@ -1113,6 +1126,7 @@ transformOfType(CreateStmtContext *cxt, TypeName *ofTypename)
n->is_local = true;
n->is_not_null = false;
n->is_from_type = true;
+ n->is_for_partition = false;
n->storage = 0;
n->raw_default = NULL;
n->cooked_default = NULL;
@@ -2580,6 +2594,7 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
cxt.alist = NIL;
cxt.pkey = NULL;
cxt.ispartitioned = relid_is_partitioned(relid);
+ cxt.partbound = NULL;
/*
* The only subtypes that currently require parse transformation handling
@@ -2668,6 +2683,22 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
break;
}
+ case AT_AttachPartition:
+ {
+ PartitionCmd *partcmd = (PartitionCmd *) cmd->def;
+
+ transformAttachPartition(&cxt, partcmd);
+
+ /* assign transformed values */
+ partcmd->bound = cxt.partbound;
+ }
+
+ newcmds = lappend(newcmds, cmd);
+ break;
+ case AT_DetachPartition:
+ newcmds = lappend(newcmds, cmd);
+ break;
+
default:
newcmds = lappend(newcmds, cmd);
break;
@@ -3032,3 +3063,290 @@ setSchemaName(char *context_schema, char **stmt_schema_name)
"different from the one being created (%s)",
*stmt_schema_name, context_schema)));
}
+
+/*
+ * transformPartitionOf
+ * Analyze PARTITION OF ... FOR VALUES ...
+ */
+static void
+transformPartitionOf(CreateStmtContext *cxt, Node *bound)
+{
+ TupleDesc tupdesc;
+ int i;
+ Relation parentRel;
+ RangeVar *partof = linitial(cxt->inhRelations);
+
+ parentRel = heap_openrv(partof, AccessShareLock);
+
+ /* Check if the target table is partitioned at all */
+ if (parentRel->rd_rel->relkind != RELKIND_PARTITIONED_REL)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("\"%s\" is not partitioned",
+ RelationGetRelationName(parentRel))));
+ Assert(RelationGetPartitionKey(parentRel) != NULL);
+
+ /* If parent rel is temp, it must belong to this session */
+ if (parentRel->rd_rel->relpersistence == RELPERSISTENCE_TEMP &&
+ !parentRel->rd_islocaltemp)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot create as partition of temporary relation of another session")));
+
+ /*
+ * Do not allow OIDs in a partition, if not present in the parent. But
+ * force them in partition, if they are present in the parent.
+ */
+ if (cxt->hasoids && !parentRel->rd_rel->relhasoids)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot create table with OIDs as partition of table without OIDs")));
+
+ if (parentRel->rd_rel->relhasoids)
+ cxt->hasoids = true;
+
+ tupdesc = RelationGetDescr(parentRel);
+ for (i = 0; i < tupdesc->natts; i++)
+ {
+ Form_pg_attribute attribute = tupdesc->attrs[i];
+ ColumnDef *def;
+
+ if (attribute->attisdropped)
+ continue;
+
+ def = makeNode(ColumnDef);
+ def->colname = pstrdup(NameStr(attribute->attname));
+ def->typeName = makeTypeNameFromOid(attribute->atttypid,
+ attribute->atttypmod);
+ def->inhcount = 1;
+ def->is_local = false;
+ def->is_not_null = attribute->attnotnull;
+ def->is_from_type = false;
+ def->is_for_partition = true;
+ def->storage = attribute->attstorage;
+ def->raw_default = NULL;
+ def->cooked_default = NULL;
+ def->collClause = NULL;
+ def->collOid = attribute->attcollation;
+ def->constraints = NIL;
+ def->location = -1;
+
+ cxt->columns = lappend(cxt->columns, def);
+ }
+
+ /* tranform the values */
+ cxt->partbound = transformPartitionBound(cxt, parentRel, bound);
+
+ heap_close(parentRel, AccessShareLock);
+}
+
+/*
+ * transformAttachPartition
+ * Analyze ATTACH PARTITION ... FOR VALUES ...
+ */
+static void
+transformAttachPartition(CreateStmtContext *cxt, PartitionCmd *cmd)
+{
+ Relation parentRel = cxt->rel;
+
+ /* Check if the target table is partitioned at all */
+ if (parentRel->rd_rel->relkind != RELKIND_PARTITIONED_REL)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("\"%s\" is not partitioned", cxt->relation->relname)));
+ Assert(RelationGetPartitionKey(parentRel) != NULL);
+
+ /* tranform the values */
+ cxt->partbound = transformPartitionBound(cxt, parentRel, cmd->bound);
+}
+
+/*
+ * transformPartitionBound
+ *
+ * Transform partition bound per the partition key
+ */
+static Node *
+transformPartitionBound(CreateStmtContext *cxt, Relation parent, Node *bound)
+{
+ int i;
+ ListCell *cell;
+ Oid parentId = RelationGetRelid(parent);
+ PartitionKey key = RelationGetPartitionKey(parent);
+ PartitionListSpec *list, *result_list;
+ PartitionRangeSpec *range, *result_range;
+
+ switch (key->strategy)
+ {
+ case PARTITION_STRAT_LIST:
+ if (!IsA(bound, PartitionListSpec))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("invalid bound specification for a list partition"),
+ parser_errposition(cxt->pstate, exprLocation(bound))));
+
+ list = (PartitionListSpec *) transformExpr(cxt->pstate, bound,
+ EXPR_KIND_PARTITION_FOR_VALUES);
+
+ result_list = makeNode(PartitionListSpec);
+
+ foreach(cell, list->values)
+ {
+ Node *value = (Node *) lfirst(cell);
+ Node *orig_value = value;
+ Oid valuetype;
+
+ valuetype = exprType(value);
+ value = coerce_to_target_type(cxt->pstate,
+ value, valuetype,
+ key->tcinfo->typid[0],
+ key->tcinfo->typmod[0],
+ COERCION_ASSIGNMENT,
+ COERCE_IMPLICIT_CAST,
+ -1);
+ if (value == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("specified value cannot be cast to type \"%s\""
+ " of key column \"%s\"",
+ format_type_be(key->tcinfo->typid[0]),
+ get_relid_attribute_name(parentId, key->partattrs[0])),
+ parser_errposition(cxt->pstate, exprLocation(orig_value))));
+
+ /* Simplify the expression */
+ value = (Node *) expression_planner((Expr *) value);
+
+ result_list->values = lappend(result_list->values, value);
+ }
+ return (Node *) result_list;
+
+ case PARTITION_STRAT_RANGE:
+ if (!IsA(bound, PartitionRangeSpec))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("invalid bound specification for a range partition"),
+ parser_errposition(cxt->pstate, exprLocation(bound))));
+
+ range = (PartitionRangeSpec *) transformExpr(cxt->pstate, bound,
+ EXPR_KIND_PARTITION_FOR_VALUES);
+
+ if (!range->lower && !range->upper)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("both START and END cannot be UNBOUNDED"),
+ parser_errposition(cxt->pstate, range->location)));
+
+ result_range = makeNode(PartitionRangeSpec);
+ result_range->lowerinc = range->lowerinc;
+ result_range->upperinc = range->upperinc;
+
+ if (range->lower)
+ {
+ if (list_length(range->lower) > key->partnatts)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("START has more values specified than number"
+ " of columns in the partition key"),
+ parser_errposition(cxt->pstate,
+ exprLocation(list_nth(range->lower,
+ list_length(range->lower) - 1)))));
+ else if (list_length(range->lower) < key->partnatts)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("START has fewer values specified than number"
+ " of columns in the partition key"),
+ parser_errposition(cxt->pstate,
+ exprLocation(list_nth(range->lower,
+ list_length(range->lower) - 1)))));
+
+ i = 0;
+ foreach (cell, range->lower)
+ {
+ Node *value = (Node *) lfirst(cell);
+ Node *orig_value = value;
+ Oid valuetype;
+
+ valuetype = exprType(value);
+ value = coerce_to_target_type(cxt->pstate,
+ value, valuetype,
+ key->tcinfo->typid[i],
+ key->tcinfo->typmod[i],
+ COERCION_ASSIGNMENT,
+ COERCE_IMPLICIT_CAST,
+ -1);
+ if (value == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("specified value cannot be cast to type"
+ " \"%s\" of key column \"%s\"",
+ format_type_be(key->tcinfo->typid[i]),
+ get_relid_attribute_name(parentId, key->partattrs[i])),
+ parser_errposition(cxt->pstate, exprLocation(orig_value))));
+
+ /* Simplify the expression */
+ value = (Node *) expression_planner((Expr *) value);
+
+ result_range->lower = lappend(result_range->lower, value);
+ ++i;
+ }
+ }
+ else
+ result_range->lowerinc = false;
+
+ if (range->upper)
+ {
+ if (list_length(range->upper) > key->partnatts)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("END has more values specified than number"
+ " of columns in the partition key"),
+ parser_errposition(cxt->pstate,
+ exprLocation(list_nth(range->upper,
+ list_length(range->upper) - 1)))));
+ else if (list_length(range->upper) < key->partnatts)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("END has fewer values specified than number"
+ " of columns in the partition key"),
+ parser_errposition(cxt->pstate,
+ exprLocation(list_nth(range->upper,
+ list_length(range->upper) - 1)))));
+
+ i = 0;
+ foreach (cell, range->upper)
+ {
+ Node *value = (Node *) lfirst(cell);
+ Node *orig_value = value;
+ Oid valuetype;
+
+ valuetype = exprType(value);
+ value = coerce_to_target_type(cxt->pstate,
+ value, valuetype,
+ key->tcinfo->typid[i],
+ key->tcinfo->typmod[i],
+ COERCION_ASSIGNMENT,
+ COERCE_IMPLICIT_CAST,
+ -1);
+ if (value == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("specified value cannot be cast to type"
+ " \"%s\" of key column \"%s\"",
+ format_type_be(key->tcinfo->typid[i]),
+ get_relid_attribute_name(parentId, key->partattrs[i])),
+ parser_errposition(cxt->pstate, exprLocation(orig_value))));
+
+ /* Simplify the expression */
+ value = (Node *) expression_planner((Expr *) value);
+
+ result_range->upper = lappend(result_range->upper, value);
+ ++i;
+ }
+ }
+ else
+ result_range->upperinc = false;
+
+ return (Node *) result_range;
+ }
+
+ return NULL;
+}
diff --git a/src/include/catalog/partition.h b/src/include/catalog/partition.h
index 2c876c4..31dbe17 100644
--- a/src/include/catalog/partition.h
+++ b/src/include/catalog/partition.h
@@ -14,6 +14,7 @@
#define PARTITION_H
#include "fmgr.h"
+#include "parser/parse_node.h"
#include "utils/relcache.h"
/* Type and collation information for partition key columns */
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index d96f083..cc4d9a0 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -404,6 +404,7 @@ typedef enum NodeTag
T_AlterPolicyStmt,
T_CreateTransformStmt,
T_CreateAmStmt,
+ T_PartitionCmd,
/*
* TAGS FOR PARSE TREE NODES (parsenodes.h)
@@ -453,6 +454,8 @@ typedef enum NodeTag
T_RoleSpec,
T_PartitionElem,
T_PartitionBy,
+ T_PartitionListSpec,
+ T_PartitionRangeSpec,
/*
* TAGS FOR REPLICATION GRAMMAR PARSE NODES (replnodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 4f697b8..d9dff96 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -591,6 +591,7 @@ typedef struct ColumnDef
bool is_local; /* column has local (non-inherited) def'n */
bool is_not_null; /* NOT NULL constraint specified? */
bool is_from_type; /* column definition came from table type */
+ bool is_for_partition; /* column definition is for a partition */
char storage; /* attstorage setting, or 0 for default */
Node *raw_default; /* default value (untransformed parse tree) */
Node *cooked_default; /* default value (transformed expr tree) */
@@ -731,6 +732,40 @@ typedef struct PartitionBy
int location; /* token location, or -1 if unknown */
} PartitionBy;
+/*
+ * PartitionRangeSpec - range type syntax for range partition bound
+ */
+typedef struct PartitionListSpec
+{
+ NodeTag type;
+ List *values;
+ int location;
+} PartitionListSpec;
+
+/*
+ * PartitionRangeSpec - range type syntax for range partition bound
+ */
+typedef struct PartitionRangeSpec
+{
+ NodeTag type;
+ bool lowerinc;
+ List *lower;
+ bool upperinc;
+ List *upper;
+ int location; /* token location, or -1 if unknown */
+} PartitionRangeSpec;
+
+/*
+ * PartitionCmd - ALTER TABLE partition commands
+ */
+typedef struct PartitionCmd
+{
+ NodeTag type;
+ RangeVar *name;
+ Node *bound;
+ bool skip_validate;
+} PartitionCmd;
+
/****************************************************************************
* Nodes for a Query tree
****************************************************************************/
@@ -1558,7 +1593,9 @@ typedef enum AlterTableType
AT_DisableRowSecurity, /* DISABLE ROW SECURITY */
AT_ForceRowSecurity, /* FORCE ROW SECURITY */
AT_NoForceRowSecurity, /* NO FORCE ROW SECURITY */
- AT_GenericOptions /* OPTIONS (...) */
+ AT_GenericOptions, /* OPTIONS (...) */
+ AT_AttachPartition, /* ATTACH PARTITION */
+ AT_DetachPartition /* DETACH PARTITION */
} AlterTableType;
typedef struct ReplicaIdentityStmt
@@ -1783,7 +1820,9 @@ typedef struct CreateStmt
RangeVar *relation; /* relation to create */
List *tableElts; /* column definitions (list of ColumnDef) */
List *inhRelations; /* relations to inherit from (list of
- * inhRelation) */
+ * inhRelation); (ab)used also as partition
+ * parent */
+ Node *partbound; /* FOR VALUES clause */
PartitionBy *partby; /* PARTITION BY clause */
TypeName *ofTypename; /* OF typename */
List *constraints; /* constraints (list of Constraint nodes) */
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 40da67a..70c264c 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -49,6 +49,7 @@ PG_KEYWORD("assertion", ASSERTION, UNRESERVED_KEYWORD)
PG_KEYWORD("assignment", ASSIGNMENT, UNRESERVED_KEYWORD)
PG_KEYWORD("asymmetric", ASYMMETRIC, RESERVED_KEYWORD)
PG_KEYWORD("at", AT, UNRESERVED_KEYWORD)
+PG_KEYWORD("attach", ATTACH, UNRESERVED_KEYWORD)
PG_KEYWORD("attribute", ATTRIBUTE, UNRESERVED_KEYWORD)
PG_KEYWORD("authorization", AUTHORIZATION, TYPE_FUNC_NAME_KEYWORD)
PG_KEYWORD("backward", BACKWARD, UNRESERVED_KEYWORD)
@@ -127,6 +128,7 @@ PG_KEYWORD("delimiter", DELIMITER, UNRESERVED_KEYWORD)
PG_KEYWORD("delimiters", DELIMITERS, UNRESERVED_KEYWORD)
PG_KEYWORD("depends", DEPENDS, UNRESERVED_KEYWORD)
PG_KEYWORD("desc", DESC, RESERVED_KEYWORD)
+PG_KEYWORD("detach", DETACH, UNRESERVED_KEYWORD)
PG_KEYWORD("dictionary", DICTIONARY, UNRESERVED_KEYWORD)
PG_KEYWORD("disable", DISABLE_P, UNRESERVED_KEYWORD)
PG_KEYWORD("discard", DISCARD, UNRESERVED_KEYWORD)
@@ -191,6 +193,7 @@ PG_KEYWORD("implicit", IMPLICIT_P, UNRESERVED_KEYWORD)
PG_KEYWORD("import", IMPORT_P, UNRESERVED_KEYWORD)
PG_KEYWORD("in", IN_P, RESERVED_KEYWORD)
PG_KEYWORD("including", INCLUDING, UNRESERVED_KEYWORD)
+PG_KEYWORD("inclusive", INCLUSIVE, UNRESERVED_KEYWORD)
PG_KEYWORD("increment", INCREMENT, UNRESERVED_KEYWORD)
PG_KEYWORD("index", INDEX, UNRESERVED_KEYWORD)
PG_KEYWORD("indexes", INDEXES, UNRESERVED_KEYWORD)
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index a13c6fb..3d45663 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -65,7 +65,8 @@ typedef enum ParseExprKind
EXPR_KIND_EXECUTE_PARAMETER, /* parameter value in EXECUTE */
EXPR_KIND_TRIGGER_WHEN, /* WHEN condition in CREATE TRIGGER */
EXPR_KIND_POLICY, /* USING or WITH CHECK expr in policy */
- EXPR_KIND_PARTITION_KEY /* partition key expression */
+ EXPR_KIND_PARTITION_KEY, /* partition key expression */
+ EXPR_KIND_PARTITION_FOR_VALUES /* partition bound */
} ParseExprKind;
--
1.7.1
[text/x-diff] 0005-Infrastructure-for-partition-metadata-storage-and-ma-v6.patch (101.9K, ../../19a3733b-ab58-b993-c858-ec8fc578ddfd@lab.ntt.co.jp/6-0005-Infrastructure-for-partition-metadata-storage-and-ma-v6.patch)
download | inline diff:
From d4278f60a4cb750c4790e85a40d04e40d09c4637 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Fri, 18 Mar 2016 15:54:17 +0900
Subject: [PATCH 5/7] Infrastructure for partition metadata storage and manipulation.
Catalog pg_partition with:
partrelid oid
partbound pg_node_tree
Now since partition metadata is stored in a dedicated catalog, it is possible
to use that knowledge to enforce special behaviors for inheritance children
(or relations in general) that are partitions. Some such behaviors include:
* Cannot drop using DROP TABLE without first "detaching" from its parent
* Cannot add, drop new columns
* Cannot rename, change type of existing columns
It also becomes now possible to check, from the FOR VALUES specification, that
the new partition (in CREATE TABLE PARTITION OF parent or ALTER TABLE parent
ATTACH PARTITION) does not overlap with any existing partition of parent.
A partition's check expression (that is, an expression tree that can be used
a check constraint for the rows in it) can be obtained with
RelationGetPartitionCheckExpr(). ExecConstraints() now checks using that
that expression, whether a row being inserted or new row value in update case
violate the partition boundary specification. Also, constraint exclusion
based partition-pruning is done using them.
When attaching a partition, rows in the table are checked whether any violates
the partition bound specification (AND'd with the parent's check expression,
if parent itself is a partition), unless NO VALIDATE clause is specified with
the command.
This commit also invents an internal representation of partition bounds in
the form of structs PartitionListBound and PartitionRangeBound, which consist
of datums and information like number of values in a list, booleans for
whether a range bound is infinite, inclusive (vs. exclusive), lower bound
(vs. upper bound), etc. "Partition descriptor" of a partitioned table
(struct PartitionDesc) consists of an array of oids of partitions, array
of pointers to PartitionListBound structs (NULL if not list partitioned,
and 2 arrays of pointers to PartitionRangeBound structs, (each NULL,
if not range partitioned).
One more data structure that this commit invents for future use is struct
PartitionDescNode. It represents a node in partition tree where each node
of the tree corresponds to a partitioned table in the tree. The tree
structure is such that a parent node links to the first child node (which
corresponds with the first partition of the parent that is partitioned
itself), which in turn, links to its right sibling (the next partition
that is partitioned) and so on. Each node consists of some book-keeping
information including a pointer to PartitionDesc of the corresponding table
and recursive pointers just mentioned viz. downlink and next.
---
doc/src/sgml/catalogs.sgml | 56 ++
src/backend/catalog/Makefile | 2 +-
src/backend/catalog/heap.c | 6 +
src/backend/catalog/partition.c | 1300 ++++++++++++++++++++++++++++
src/backend/commands/copy.c | 2 +-
src/backend/commands/lockcmds.c | 4 +-
src/backend/commands/tablecmds.c | 205 ++++-
src/backend/executor/execMain.c | 75 ++-
src/backend/executor/nodeModifyTable.c | 4 +-
src/backend/nodes/readfuncs.c | 33 +
src/backend/optimizer/util/plancat.c | 13 +
src/backend/parser/analyze.c | 1 +
src/backend/parser/parse_clause.c | 4 +-
src/backend/parser/parse_relation.c | 3 +-
src/backend/parser/parse_utilcmd.c | 6 +
src/backend/utils/adt/ruleutils.c | 78 ++
src/backend/utils/cache/relcache.c | 290 +++++++
src/backend/utils/cache/syscache.c | 12 +
src/bin/pg_dump/common.c | 86 ++
src/bin/pg_dump/pg_dump.c | 98 ++-
src/bin/pg_dump/pg_dump.h | 12 +
src/bin/psql/describe.c | 46 +-
src/include/catalog/indexing.h | 3 +
src/include/catalog/partition.h | 85 ++
src/include/catalog/pg_partition.h | 54 ++
src/include/nodes/execnodes.h | 4 +
src/include/utils/rel.h | 10 +
src/include/utils/syscache.h | 1 +
src/test/regress/expected/sanity_check.out | 1 +
29 files changed, 2458 insertions(+), 36 deletions(-)
create mode 100644 src/include/catalog/pg_partition.h
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 1c4e789..30601c0 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -226,6 +226,11 @@
</row>
<row>
+ <entry><link linkend="catalog-pg-partition"><structname>pg_partition</structname></link></entry>
+ <entry>information about partitions</entry>
+ </row>
+
+ <row>
<entry><link linkend="catalog-pg-partitioned"><structname>pg_partitioned</structname></link></entry>
<entry>information about partitioned tables, including the partition key</entry>
</row>
@@ -4679,6 +4684,57 @@
</sect1>
+ <sect1 id="catalog-pg-partition">
+ <title><structname>pg_partition</structname></title>
+
+ <indexterm zone="catalog-pg-partition">
+ <primary>pg_partition</primary>
+ </indexterm>
+
+ <para>
+ The catalog <structname>pg_partition</structname> stores partition bounds
+ of tables that are partitions.
+ </para>
+
+ <table>
+ <title><structname>pg_partition</> Columns</title>
+
+ <tgroup cols="4">
+ <thead>
+ <row>
+ <entry>Name</entry>
+ <entry>Type</entry>
+ <entry>References</entry>
+ <entry>Description</entry>
+ </row>
+ </thead>
+
+ <tbody>
+
+ <row>
+ <entry><structfield>partrelid</structfield></entry>
+ <entry><type>oid</type></entry>
+ <entry><literal><link linkend="catalog-pg-class"><structname>pg_class</structname></link>.oid</literal></entry>
+ <entry>The OID of the <structname>pg_class</> entry for this partition</entry>
+ </row>
+
+ <row>
+ <entry><structfield>partbound</structfield></entry>
+ <entry><type>pg_node_tree</type></entry>
+ <entry></entry>
+ <entry>
+ Expression tree (in <function>nodeToString()</function> representation)
+ for partition boundary value. For list partitions, it is simply a list
+ of values. For range partitions, it consists of lower and upper bound
+ values along with their inclusivity.
+ </entry>
+ </row>
+
+ </tbody>
+ </tgroup>
+ </table>
+ </sect1>
+
<sect1 id="catalog-pg-partitioned">
<title><structname>pg_partitioned</structname></title>
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index 4dc6300..ee1be31 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -41,7 +41,7 @@ POSTGRES_BKI_SRCS = $(addprefix $(top_srcdir)/src/include/catalog/,\
pg_foreign_data_wrapper.h pg_foreign_server.h pg_user_mapping.h \
pg_foreign_table.h pg_policy.h pg_replication_origin.h \
pg_default_acl.h pg_init_privs.h pg_seclabel.h pg_shseclabel.h \
- pg_collation.h pg_range.h pg_transform.h pg_partitioned.h\
+ pg_collation.h pg_range.h pg_transform.h pg_partitioned.h pg_partition.h\
toasting.h indexing.h \
)
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 932a48b..2e99661 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -1808,7 +1808,13 @@ heap_drop_with_catalog(Oid relid)
* If a partitioned table, delete the pg_partitioned tuples.
*/
if (rel->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+ {
RemovePartitionKeyByRelId(relid);
+ RelationDropPartitions(relid);
+ }
+
+ if (relid_is_partition(relid))
+ RemovePartitionEntryByRelId(relid);
/*
* Schedule unlinking of the relation's physical files at commit.
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index 210a633..be4a79f 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -18,19 +18,26 @@
#include "access/heapam.h"
#include "access/htup_details.h"
#include "access/nbtree.h"
+#include "access/sysattr.h"
#include "catalog/dependency.h"
#include "catalog/indexing.h"
#include "catalog/objectaddress.h"
#include "catalog/partition.h"
#include "catalog/pg_collation.h"
+#include "catalog/pg_inherits.h"
+#include "catalog/pg_inherits_fn.h"
#include "catalog/pg_opclass.h"
#include "catalog/pg_partitioned.h"
+#include "catalog/pg_partition.h"
#include "catalog/pg_type.h"
#include "executor/executor.h"
#include "miscadmin.h"
+#include "nodes/makefuncs.h"
#include "nodes/nodeFuncs.h"
+#include "nodes/parsenodes.h"
#include "optimizer/clauses.h"
#include "optimizer/planmain.h"
+#include "optimizer/var.h"
#include "storage/lmgr.h"
#include "utils/array.h"
#include "utils/builtins.h"
@@ -39,12 +46,55 @@
#include "utils/fmgroids.h"
#include "utils/inval.h"
#include "utils/lsyscache.h"
+#include "utils/rel.h"
#include "utils/syscache.h"
+typedef struct translate_var_attno_mutator_context
+{
+ AttrNumber old_attno;
+ AttrNumber new_attno;
+} translate_var_attno_mutator_context;
+
static PartitionKey CopyPartitionKey(PartitionKey fromkey);
static KeyTypeCollInfo *copy_key_type_coll_info(int nkeycols,
KeyTypeCollInfo *tcinfo);
static void free_key_type_coll_info(KeyTypeCollInfo *tcinfo);
+static bool list_overlaps_existing_partition(PartitionKey key,
+ PartitionListSpec *list_spec,
+ PartitionDesc pdesc,
+ Oid *with);
+static bool partition_range_empty(PartitionKey key,
+ PartitionRangeSpec *range_spec);
+static int32 compare_range_keys(PartitionKey key, Datum *val1, Datum *val2);
+static bool range_overlaps_existing_partition(PartitionKey key,
+ PartitionRangeSpec *range_spec,
+ PartitionDesc pdesc,
+ Oid *with);
+static PartitionListBound *make_list_from_spec(PartitionKey key, List *values);
+static PartitionRangeBound *make_range_from_spec(PartitionKey key, List *val,
+ bool inclusive, bool lower);
+static bool partition_range_overlaps(PartitionKey key,
+ PartitionRangeBound *part1_lower,
+ PartitionRangeBound *part1_upper,
+ PartitionRangeBound *part2_lower,
+ PartitionRangeBound *part2_upper);
+
+static List *get_partitions(Oid relid, int lockmode);
+
+static Expr *get_check_expr_for_list(PartitionKey key, List *values);
+static Expr *get_check_expr_for_range(PartitionKey key,
+ List *lowervalues,
+ bool lowerinc,
+ List *uppervalues,
+ bool upperinc);
+static Node *translate_var_attno(Node *expr, AttrNumber attno,
+ AttrNumber new_attno);
+static Node *translate_var_attno_mutator(Node *node,
+ translate_var_attno_mutator_context *cxt);
+static Node *generate_partition_check_expr(Relation rel);
+
+static PartitionDescNode GetPartitionDescNodeRecurse(Relation rel, int offset);
+static int get_leaf_partition_count(PartitionDescNode pdnode);
/*
* StorePartitionKey
@@ -448,3 +498,1253 @@ FreePartitionKey(PartitionKey key)
free_key_type_coll_info(key->tcinfo);
pfree(key);
}
+
+/*
+ * StorePartitionBound
+ *
+ * Store partition boundary info in pg_partition after checking for its
+ * validity.
+ */
+void
+StorePartitionBound(Oid relid, Oid parentId, Node *bound)
+{
+ Relation pg_partition;
+ Relation parent;
+ PartitionKey key;
+ PartitionDesc pdesc;
+ Oid with;
+ HeapTuple tuple;
+ char *boundString;
+ Datum values[Natts_pg_partition];
+ bool nulls[Natts_pg_partition];
+ ParseState *pstate = make_parsestate(NULL);
+
+ parent = heap_open(parentId, AccessShareLock);
+ key = RelationGetPartitionKey(parent);
+ pdesc = RelationGetPartitionDesc(parent);
+
+ /*
+ * Check if 'bound' happens to overlap with any of parent's existing
+ * partitions.
+ */
+ switch (key->strategy)
+ {
+ case PARTITION_STRAT_LIST:
+ {
+ PartitionListSpec *list;
+
+ Assert(IsA(bound, PartitionListSpec));
+ list = (PartitionListSpec *) bound;
+ if (list_overlaps_existing_partition(key, list, pdesc, &with))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("new partition's list of values overlaps with that"
+ " of partition \"%s\" of \"%s\"",
+ get_rel_name(with), get_rel_name(parentId)),
+ errhint("Please specify a list of values that does not"
+ " overlap with any existing list partition's list."),
+ parser_errposition(pstate, list->location)));
+ break;
+ }
+
+ case PARTITION_STRAT_RANGE:
+ {
+ PartitionRangeSpec *range;
+
+ Assert(IsA(bound, PartitionRangeSpec));
+ range = (PartitionRangeSpec *) bound;
+ if (partition_range_empty(key, range))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("new partition's range contains no element"),
+ errhint("Please specify a non-empty range."),
+ parser_errposition(pstate, range->location)));
+
+ if (range_overlaps_existing_partition(key, range, pdesc, &with))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("new partition's range overlaps with that of"
+ " partition \"%s\" of \"%s\"",
+ get_rel_name(with), get_rel_name(parentId)),
+ errhint("Please specify a range that does not overlap"
+ " with any existing partition's range."),
+ parser_errposition(pstate, range->location)));
+ break;
+ }
+ }
+
+ pg_partition = heap_open(PartitionRelationId, RowExclusiveLock);
+
+ boundString = nodeToString(bound);
+
+ /* Build the tuple - bound can never be null */
+ MemSet(nulls, false, sizeof(nulls));
+
+ values[Anum_pg_partition_partrelid - 1] = ObjectIdGetDatum(relid);
+ values[Anum_pg_partition_partbound - 1] = CStringGetTextDatum(boundString);
+
+ tuple = heap_form_tuple(RelationGetDescr(pg_partition), values, nulls);
+
+ simple_heap_insert(pg_partition, tuple);
+
+ /* Update the indexes on pg_partition */
+ CatalogUpdateIndexes(pg_partition, tuple);
+
+ /*
+ * Invalid the parent's relcache so that the partition is now included in
+ * its partition descriptor.
+ */
+ CacheInvalidateRelcache(parent);
+
+ heap_close(parent, AccessShareLock);
+ heap_close(pg_partition, RowExclusiveLock);
+}
+
+/*
+ * Does new list partition 'bound' overlap any of existing list partitions?
+ */
+static bool
+list_overlaps_existing_partition(PartitionKey key,
+ PartitionListSpec *list_spec,
+ PartitionDesc pdesc,
+ Oid *with)
+{
+ PartitionListBound *new_list;
+ int i;
+
+ if (pdesc->nparts == 0)
+ return false;
+
+ new_list = make_list_from_spec(key, list_spec->values);
+
+ for (i = 0; i < pdesc->nparts; i++)
+ {
+ int j;
+
+ /* Only one of the partitions can contain nulls */
+ if (pdesc->lists[i]->contains_null && new_list->contains_null)
+ return true;
+
+ for (j = 0; j < pdesc->lists[i]->nvalues; j++)
+ {
+ int k;
+
+ for (k = 0; k < new_list->nvalues; k++)
+ {
+ int32 result;
+
+ result = DatumGetInt32(FunctionCall2Coll(&key->partsupfunc[0],
+ key->tcinfo->typcoll[0],
+ pdesc->lists[i]->values[j],
+ new_list->values[k]));
+ if (!result)
+ {
+ *with = pdesc->oids[i];
+ return true;
+ }
+ }
+ }
+ }
+
+ return false;
+}
+
+/*
+ * Is new partition's range empty?
+ */
+static bool
+partition_range_empty(PartitionKey key, PartitionRangeSpec *range_spec)
+{
+ PartitionRangeBound *lower,
+ *upper;
+
+ lower = make_range_from_spec(key, range_spec->lower, range_spec->lowerinc,
+ true);
+ upper = make_range_from_spec(key, range_spec->upper, range_spec->upperinc,
+ false);
+
+ /*
+ * Only either lower or upper is allowed to be unbounded, which if
+ * one of them is, the range is not empty.
+ */
+ if (lower->infinite || upper->infinite)
+ return false;
+
+ /*
+ * If upper > lower, ouright empty or if lower = upper and either of
+ * them is exclusive.
+ */
+ if (compare_range_keys(key, upper->val, lower->val) < 0 ||
+ (compare_range_keys(key, lower->val, upper->val) == 0 &&
+ (!lower->inclusive || !upper->inclusive)))
+ return true;
+
+ return false;
+}
+
+/* Compare range composite keys using logic similar to RowCompareExpr */
+static int32
+compare_range_keys(PartitionKey key, Datum *val1, Datum *val2)
+{
+ int32 result;
+ int i;
+
+ for (i = 0; i < key->partnatts; i++)
+ {
+ result = DatumGetInt32(FunctionCall2Coll(&key->partsupfunc[i],
+ key->tcinfo->typcoll[i],
+ val1[i], val2[i]));
+ if (!result)
+ continue;
+ break;
+ }
+
+ return result;
+}
+
+/*
+ * Does new range partition's range overlap any of existing range partitions?
+ */
+static bool
+range_overlaps_existing_partition(PartitionKey key,
+ PartitionRangeSpec *range_spec,
+ PartitionDesc pdesc,
+ Oid *with)
+{
+ int i;
+ PartitionRangeBound *new_lower,
+ *new_upper;
+
+ if (pdesc->nparts == 0)
+ return false;
+
+ /*
+ * Create internal representation of rangelower and rangeupper from
+ * range_spec.
+ */
+ new_lower = make_range_from_spec(key, range_spec->lower, range_spec->lowerinc,
+ true);
+ new_upper = make_range_from_spec(key, range_spec->upper, range_spec->upperinc,
+ false);
+
+ /* Check with existing partitions for overlap */
+ for (i = 0; i < pdesc->nparts; i++)
+ {
+ if (partition_range_overlaps(key, new_lower, new_upper,
+ pdesc->rangelowers[i],
+ pdesc->rangeuppers[i]))
+ {
+ *with = pdesc->oids[i];
+ return true;
+ }
+ }
+
+ return false;
+}
+
+/*
+ * Make a PartitionListBound from list of values using the information in key
+ */
+static PartitionListBound *
+make_list_from_spec(PartitionKey key, List *values)
+{
+ PartitionListBound *list;
+ ListCell *cell;
+ int i;
+
+ list = (PartitionListBound *) palloc0(sizeof(PartitionListBound));
+ list->nvalues = list_length(values);
+ list->values = (Datum *) palloc(list->nvalues * sizeof(Datum));
+
+ i = 0;
+ foreach (cell, values)
+ {
+ Const *val = lfirst(cell);
+
+ if (val->constisnull)
+ list->contains_null = true;
+ else
+ list->values[i] = datumCopy(val->constvalue,
+ key->tcinfo->typbyval[0],
+ key->tcinfo->typlen[0]);
+ i++;
+ }
+
+ return list;
+}
+
+/*
+ * Make a PartitionRangeBound from arguments using the information in key
+ */
+static PartitionRangeBound *
+make_range_from_spec(PartitionKey key, List *val, bool inclusive, bool lower)
+{
+ PartitionRangeBound *range;
+ ListCell *cell;
+
+ range = (PartitionRangeBound *) palloc0(sizeof(PartitionRangeBound));
+ range->infinite = (val == NIL);
+ range->inclusive = inclusive;
+ range->lower = lower;
+
+ if (val)
+ {
+ int i;
+
+ range->val = (Datum *) palloc0(key->partnatts * sizeof(Datum));
+
+ i = 0;
+ foreach (cell, val)
+ {
+ Const *val = lfirst(cell);
+
+ if (val->constisnull)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("cannot specify NULL in range bound")));
+ else
+ range->val[i] = datumCopy(val->constvalue,
+ key->tcinfo->typbyval[i],
+ key->tcinfo->typlen[i]);
+ i++;
+ }
+ }
+
+ return range;
+}
+
+/* Check two range partitions for overlap */
+static bool
+partition_range_overlaps(PartitionKey key,
+ PartitionRangeBound *part1_lower,
+ PartitionRangeBound *part1_upper,
+ PartitionRangeBound *part2_lower,
+ PartitionRangeBound *part2_upper)
+{
+ if (partition_range_cmp_bounds(key, part1_lower, part2_lower) >= 0 &&
+ partition_range_cmp_bounds(key, part1_lower, part2_upper) <= 0)
+ return true;
+
+ if (partition_range_cmp_bounds(key, part2_lower, part1_lower) >= 0 &&
+ partition_range_cmp_bounds(key, part2_lower, part1_upper) <= 0)
+ return true;
+
+ return false;
+}
+
+/* Returns for two range partition bounds whether, b1 <=, =, >= b2 */
+int32
+partition_range_cmp_bounds(PartitionKey key,
+ PartitionRangeBound *b1,
+ PartitionRangeBound *b2)
+{
+ int32 result;
+
+ /*
+ * First, handle cases involving infinity, which don't require invoking
+ * the comparison proc.
+ */
+ if (b1->infinite && b2->infinite)
+ {
+ /*
+ * Both are infinity, so they are equal unless one is lower and the
+ * other not.
+ */
+ if (b1->lower == b2->lower)
+ return 0;
+ else
+ return b1->lower ? -1 : 1;
+ }
+ else if (b1->infinite)
+ return b1->lower ? -1 : 1;
+ else if (b2->infinite)
+ return b2->lower ? 1 : -1;
+
+ /*
+ * Both boundaries are finite, so compare the held values.
+ */
+ result = compare_range_keys(key, b1->val, b2->val);
+
+ /*
+ * If the comparison is anything other than equal, we're done. If they
+ * compare equal though, we still have to consider whether the boundaries
+ * are inclusive or exclusive.
+ */
+ if (result == 0)
+ {
+ if (!b1->inclusive && !b2->inclusive)
+ {
+ /* both are exclusive */
+ if (b1->lower == b2->lower)
+ return 0;
+ else
+ return b1->lower ? 1 : -1;
+ }
+ else if (!b1->inclusive)
+ return b1->lower ? 1 : -1;
+ else if (!b2->inclusive)
+ return b2->lower ? -1 : 1;
+ else
+ {
+ /*
+ * Both are inclusive and the values held are equal, so they are
+ * equal regardless of whether they are upper or lower boundaries,
+ * or a mix.
+ */
+ return 0;
+ }
+ }
+
+ return result;
+}
+
+/*
+ * RemovePartitionByRelId
+ * Remove a pg_partition entry for a partition.
+ */
+void
+RemovePartitionEntryByRelId(Oid relid)
+{
+ Relation rel;
+ HeapTuple tuple;
+
+ rel = heap_open(PartitionRelationId, RowExclusiveLock);
+ tuple = SearchSysCache1(PARTRELID, ObjectIdGetDatum(relid));
+
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "cache lookup failed for partition entry of relation %u",
+ relid);
+
+ simple_heap_delete(rel, &tuple->t_self);
+
+ /* Update the indexes on pg_partitioned_rel */
+ CatalogUpdateIndexes(rel, tuple);
+
+ ReleaseSysCache(tuple);
+ heap_close(rel, RowExclusiveLock);
+}
+
+/*
+ * does relid have pg_partition entry?
+ */
+bool
+relid_is_partition(Oid relid)
+{
+ return SearchSysCacheExists1(PARTRELID, ObjectIdGetDatum(relid));
+}
+
+/*
+ * Returns inheritance parent of relid by scanning pg_inherits
+ */
+Oid
+get_partition_parent(Oid relid)
+{
+ Form_pg_inherits form;
+ Relation catalogRelation;
+ SysScanDesc scan;
+ ScanKeyData key[2];
+ HeapTuple tuple;
+
+ catalogRelation = heap_open(InheritsRelationId, AccessShareLock);
+
+ ScanKeyInit(&key[0],
+ Anum_pg_inherits_inhrelid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(relid));
+ ScanKeyInit(&key[1],
+ Anum_pg_inherits_inhseqno,
+ BTEqualStrategyNumber, F_INT4EQ,
+ Int32GetDatum(1));
+
+ scan = systable_beginscan(catalogRelation, InheritsRelidSeqnoIndexId, true,
+ NULL, 2, key);
+
+ tuple = systable_getnext(scan);
+ Assert(HeapTupleIsValid(tuple));
+
+ form = (Form_pg_inherits) GETSTRUCT(tuple);
+
+ systable_endscan(scan);
+ heap_close(catalogRelation, AccessShareLock);
+
+ return form->inhparent;
+}
+
+/*
+ * RelationDropPartitions
+ * Find and drop all partitions of relation with OID 'relid'
+ */
+void
+RelationDropPartitions(Oid relid)
+{
+ List *partitionOids;
+ ListCell *cell;
+
+ partitionOids = get_partitions(relid, AccessExclusiveLock);
+
+ foreach (cell, partitionOids)
+ {
+ Oid partitionOid = lfirst_oid(cell);
+ ObjectAddress obj;
+
+ obj.classId = RelationRelationId;
+ obj.objectId = partitionOid;
+ obj.objectSubId = 0;
+
+ performDeletion(&obj, DROP_CASCADE, 0);
+ }
+}
+
+/*
+ * get_partition_info
+ * Return information of partitions of rel
+ *
+ * Returns number of partitions in *nparts.
+ */
+PartitionInfo **
+get_partition_info(Relation rel, int *nparts)
+{
+ int i,
+ n;
+ Relation partrel;
+ List *partoids;
+ ListCell *cell;
+ PartitionInfo **parts;
+ PartitionKey key = RelationGetPartitionKey(rel);
+
+ /* Read info from pg_inherits */
+ partoids = get_partitions(RelationGetRelid(rel), NoLock);
+
+ partrel = heap_open(PartitionRelationId, AccessShareLock);
+
+ n = list_length(partoids);
+ parts = (PartitionInfo **) palloc0(n * sizeof(PartitionInfo *));
+
+ i = 0;
+ foreach(cell, partoids)
+ {
+ Oid partrelid = lfirst_oid(cell);
+ HeapTuple tuple;
+ Datum datum;
+ bool isnull;
+ Node *bound;
+
+ tuple = SearchSysCache1(PARTRELID, partrelid);
+
+ /*
+ * Happens when we have created the pg_inherits entry but not the
+ * pg_partition entry yet.
+ */
+ if (!HeapTupleIsValid(tuple))
+ continue;
+
+ parts[i] = (PartitionInfo *) palloc0(sizeof(PartitionInfo));
+ parts[i]->oid = partrelid;
+
+ datum = fastgetattr(tuple,
+ Anum_pg_partition_partbound,
+ partrel->rd_att,
+ &isnull);
+
+ bound = stringToNode(TextDatumGetCString(datum));
+
+ switch (key->strategy)
+ {
+ case PARTITION_STRAT_LIST:
+ {
+ PartitionListSpec *list_spec;
+
+ Assert(IsA(bound, PartitionListSpec));
+ list_spec = (PartitionListSpec *) bound;
+ parts[i]->list = make_list_from_spec(key, list_spec->values);
+ }
+ break;
+
+ case PARTITION_STRAT_RANGE:
+ {
+ PartitionRangeSpec *range_spec;
+
+ Assert(IsA(bound, PartitionRangeSpec));
+ range_spec = (PartitionRangeSpec *) bound;
+
+ parts[i]->rangelower = make_range_from_spec(key,
+ range_spec->lower,
+ range_spec->lowerinc,
+ true);
+ parts[i]->rangeupper = make_range_from_spec(key,
+ range_spec->upper,
+ range_spec->upperinc,
+ false);
+ }
+ break;
+ }
+
+ ReleaseSysCache(tuple);
+ i++;
+ }
+
+ heap_close(partrel, AccessShareLock);
+
+ *nparts = i;
+ if (*nparts == 0 && parts)
+ {
+ pfree(parts);
+ parts = NULL;
+ }
+
+ return parts;
+}
+
+/*
+ * Wrapper around find_inheritance_children for use by partitioning
+ * related code
+ */
+static List *
+get_partitions(Oid relid, int lockmode)
+{
+ return find_inheritance_children(relid, lockmode);
+}
+
+/*
+ * Helper routine to deep-free a PartitionInfo array
+ */
+void
+free_partition_info(PartitionInfo **p, int num)
+{
+ int i;
+
+ for (i = 0; i < num; i++)
+ {
+ if (p[i]->list)
+ {
+ pfree(p[i]->list->values);
+ pfree(p[i]->list);
+ }
+
+ if (p[i]->rangelower)
+ {
+ if (p[i]->rangelower->val)
+ pfree(p[i]->rangelower->val);
+ pfree(p[i]->rangelower);
+ }
+
+ if (p[i]->rangeupper)
+ {
+ if (p[i]->rangeupper->val)
+ pfree(p[i]->rangeupper->val);
+ pfree(p[i]->rangeupper);
+ }
+
+ pfree(p[i]);
+ }
+
+ pfree(p);
+}
+
+/*
+ * get_check_expr_from_partbound - for direct use by tablecmds.c
+ */
+Expr *
+get_check_expr_from_partbound(Relation rel, Relation parent, Node *bound)
+{
+ PartitionKey key = RelationGetPartitionKey(parent);
+ Expr *my_expr;
+ int i;
+ ListCell *partexprs_item;
+
+ if (IsA(bound, PartitionListSpec))
+ {
+ PartitionListSpec *list_spec = (PartitionListSpec *) bound;
+
+ my_expr = get_check_expr_for_list(key, list_spec->values);
+ }
+ else if (IsA(bound, PartitionRangeSpec))
+ {
+ PartitionRangeSpec *range_spec = (PartitionRangeSpec *) bound;
+
+ my_expr = get_check_expr_for_range(key,
+ range_spec->lower,
+ range_spec->lowerinc,
+ range_spec->upper,
+ range_spec->upperinc);
+ }
+
+ /*
+ * Translate vars in the generated expression with the correct attnos.
+ * Note that the vars in my_expr bear attnos dictated by key which carries
+ * attnos of the parent.
+ */
+ partexprs_item = list_head(key->partexprs);
+ for (i = 0; i < key->partnatts; i++)
+ {
+ AttrNumber attno = key->partattrs[i],
+ new_attno;
+ char *attname;
+
+ if (attno != 0)
+ {
+ attname = get_attname(RelationGetRelid(parent), attno);
+ new_attno = get_attnum(RelationGetRelid(rel), attname);
+
+ my_expr = (Expr *) translate_var_attno((Node *) my_expr,
+ attno,
+ new_attno);
+ }
+ else
+ {
+ Node *expr = (Node *) lfirst(partexprs_item);
+ Bitmapset *expr_attrs = NULL;
+ int index;
+
+ pull_varattnos(expr, 1, &expr_attrs);
+ partexprs_item = lnext(partexprs_item);
+
+ index = -1;
+ while ((index = bms_next_member(expr_attrs, index)) > 0)
+ {
+ AttrNumber attno = index + FirstLowInvalidHeapAttributeNumber;
+ attname = get_attname(RelationGetRelid(parent), attno);
+ new_attno = get_attnum(RelationGetRelid(rel), attname);
+ my_expr = (Expr *) translate_var_attno((Node *) my_expr,
+ attno,
+ new_attno);
+ }
+ }
+ }
+
+ return my_expr;
+}
+
+/*
+ * get_check_expr_for_list
+ */
+static Expr *
+get_check_expr_for_list(PartitionKey key, List *values)
+{
+ ArrayExpr *values_arr = makeNode(ArrayExpr);
+ ScalarArrayOpExpr *arrayop_expr;
+ Node *key_col;
+ Oid operoid;
+ StrategyNumber strategy = BTEqualStrategyNumber;
+
+ /* Btree equality operator of specified opfamily */
+ operoid = get_opfamily_member(key->partopfamily[0],
+ key->tcinfo->typid[0],
+ key->tcinfo->typid[0],
+ strategy);
+
+ /* lexpr */
+ if (key->partattrs[0] != 0)
+ key_col = (Node *) makeVar(1, key->partattrs[0],
+ key->tcinfo->typid[0],
+ key->tcinfo->typmod[0],
+ key->tcinfo->typcoll[0],
+ 0);
+ else
+ key_col = (Node *) linitial(key->partexprs);
+
+ /* rexpr */
+ values_arr->array_typeid = get_array_type(key->tcinfo->typid[0]);
+ values_arr->array_collid = key->tcinfo->typcoll[0];
+ values_arr->element_typeid = key->tcinfo->typid[0];
+ values_arr->elements = values;
+ values_arr->multidims = false;
+ values_arr->location = -1;
+
+ /* build the actual predicate expression node */
+ arrayop_expr = makeNode(ScalarArrayOpExpr);
+ arrayop_expr->opno = operoid;
+ arrayop_expr->opfuncid = get_opcode(operoid);
+ arrayop_expr->useOr = true;
+ arrayop_expr->inputcollid = key->tcinfo->typcoll[0];
+ arrayop_expr->args = list_make2(key_col, values_arr);
+ arrayop_expr->location = -1;
+
+ return (Expr *) arrayop_expr;
+}
+
+/*
+ * get_check_expr_for_range
+ */
+static Expr *
+get_check_expr_for_range(PartitionKey key,
+ List *lowervalues,
+ bool lowerinc,
+ List *uppervalues,
+ bool upperinc)
+{
+ List *and_args = NIL;
+ ListCell *cell1,
+ *cell2,
+ *partexprs_item;
+ int i;
+ uint16 strategy;
+
+ /*
+ * Handle the case with only one bound.
+ *
+ * In this case, consider only the first column of the key and generate
+ * appropriate opclause since only the comparison with the first column
+ * would have determined whether a rows went into such a partition.
+ * It's always true that -INF < someval and someval < +INF.
+ */
+ if (lowervalues == NIL || uppervalues == NIL)
+ {
+ List *values;
+ Const *key_val;
+ Node *key_col;
+ Oid operoid;
+ bool islower,
+ inclusive;
+
+ if (lowervalues != NIL)
+ {
+ values = lowervalues;
+ islower = true;
+ inclusive = lowerinc;
+ }
+ else
+ {
+ values = uppervalues;
+ islower = false;
+ inclusive = upperinc;
+ }
+
+ /* lexpr */
+ if (key->partattrs[0] != 0)
+ key_col = (Node *) makeVar(1, key->partattrs[0],
+ key->tcinfo->typid[0],
+ key->tcinfo->typmod[0],
+ key->tcinfo->typcoll[0],
+ 0);
+ else
+ key_col = (Node *) linitial(key->partexprs);
+
+ /* rexpr */
+ key_val = linitial(values);
+
+ if (islower)
+ strategy = inclusive ? BTGreaterEqualStrategyNumber : BTGreaterStrategyNumber;
+ else
+ strategy = inclusive ? BTLessEqualStrategyNumber : BTLessStrategyNumber;
+
+ operoid = get_opfamily_member(key->partopfamily[0],
+ key->tcinfo->typid[0],
+ key->tcinfo->typid[0],
+ strategy);
+
+ and_args = lappend(and_args,
+ make_opclause(operoid, BOOLOID,
+ false,
+ (Expr *) key_col,
+ (Expr *) key_val,
+ InvalidOid,
+ key->tcinfo->typcoll[0]));
+
+ return makeBoolExpr(AND_EXPR, and_args, -1);
+ }
+
+ /* Both bounds constrain values going into the partition. */
+ i = 0;
+ partexprs_item = list_head(key->partexprs);
+ forboth (cell1, lowervalues, cell2, uppervalues)
+ {
+ Node *key_col;
+ Const *lower_val = lfirst(cell1);
+ Const *upper_val = lfirst(cell2);
+ Expr *lower_clause,
+ *upper_clause;
+ Oid eqop,
+ lowerop,
+ upperop;
+ EState *estate;
+ MemoryContext oldcxt;
+ Expr *test_expr;
+ ExprState *test_exprstate;
+ Datum test_result;
+ bool isNull;
+
+ /* lexpr */
+ if (key->partattrs[i] != 0)
+ {
+ key_col = (Node *) makeVar(1, key->partattrs[i],
+ key->tcinfo->typid[i],
+ key->tcinfo->typmod[i],
+ key->tcinfo->typcoll[i],
+ 0);
+ }
+ else
+ {
+ key_col = (Node *) lfirst(partexprs_item);
+ partexprs_item = lnext(partexprs_item);
+ }
+
+ /*
+ * If lower_val = upper_val (= val, say), create a column = val
+ * constraint.
+ */
+ eqop = get_opfamily_member(key->partopfamily[i],
+ key->tcinfo->typid[i],
+ key->tcinfo->typid[i],
+ BTEqualStrategyNumber);
+
+ estate = CreateExecutorState();
+ oldcxt = MemoryContextSwitchTo(estate->es_query_cxt);
+ test_expr = make_opclause(eqop,
+ BOOLOID,
+ false,
+ (Expr *) lower_val,
+ (Expr *) upper_val,
+ InvalidOid,
+ key->tcinfo->typcoll[i]);
+ fix_opfuncids((Node *) test_expr);
+ test_exprstate = ExecInitExpr(test_expr, NULL);
+ test_result = ExecEvalExprSwitchContext(test_exprstate,
+ GetPerTupleExprContext(estate),
+ &isNull, NULL);
+ MemoryContextSwitchTo(oldcxt);
+ FreeExecutorState(estate);
+
+ if (DatumGetBool(test_result))
+ {
+ and_args = lappend(and_args,
+ make_opclause(eqop,
+ BOOLOID,
+ false,
+ (Expr *) key_col,
+ /* could've been upper_val */
+ (Expr *) lower_val,
+ InvalidOid,
+ key->tcinfo->typcoll[i]));
+
+ /* Go to the next column. */
+ }
+ else
+ {
+ /*
+ * For all columns but last, use inclusive strategy (<= or >=).
+ *
+ * For the last column, the bound's inclusivity determines.
+ */
+ strategy = i < key->partnatts - 1 || lowerinc
+ ? BTGreaterEqualStrategyNumber
+ : BTGreaterStrategyNumber;
+ lowerop = get_opfamily_member(key->partopfamily[i],
+ key->tcinfo->typid[i],
+ key->tcinfo->typid[i],
+ strategy);
+ strategy = i < key->partnatts - 1 || upperinc
+ ? BTLessEqualStrategyNumber
+ : BTLessStrategyNumber;
+ upperop = get_opfamily_member(key->partopfamily[i],
+ key->tcinfo->typid[i],
+ key->tcinfo->typid[i],
+ strategy);
+
+ lower_clause = make_opclause(lowerop,
+ BOOLOID,
+ false,
+ (Expr *) key_col,
+ (Expr *) lower_val,
+ InvalidOid,
+ key->tcinfo->typcoll[i]);
+ upper_clause = make_opclause(upperop,
+ BOOLOID,
+ false,
+ (Expr *) key_col,
+ (Expr *) upper_val,
+ InvalidOid,
+ key->tcinfo->typcoll[i]);
+
+ and_args = lappend(and_args,
+ makeBoolExpr(AND_EXPR,
+ list_make2(lower_clause, upper_clause), -1));
+
+ /* No need to constraint further columns. */
+ break;
+ }
+
+ i++;
+ }
+
+ return makeBoolExpr(AND_EXPR, and_args, -1);
+}
+
+/*
+ * translate_var_attno
+ * Changes Vars with a given attno in the provided expression tree to
+ * Vars with new_attno
+ */
+static Node *
+translate_var_attno(Node *expr, AttrNumber attno, AttrNumber new_attno)
+{
+ translate_var_attno_mutator_context cxt;
+
+ cxt.old_attno = attno;
+ cxt.new_attno = new_attno;
+
+ return expression_tree_mutator(expr, translate_var_attno_mutator, &cxt);
+}
+
+/*
+ * translate_var_attno_mutator
+ */
+static Node *
+translate_var_attno_mutator(Node *node,
+ translate_var_attno_mutator_context *cxt)
+{
+ if (node == NULL)
+ return NULL;
+
+ if (IsA(node, Var) && ((Var *) node)->varattno == cxt->old_attno)
+ {
+ Var *newvar = copyObject(node);
+
+ newvar->varattno = cxt->new_attno;
+
+ return (Node *) newvar;
+ }
+
+ return expression_tree_mutator(node, translate_var_attno_mutator,
+ (void *) cxt);
+}
+
+/*
+ * RelationGetPartitionConstraint
+ * Returns the equivelent CHECK constraint expression for a partition
+ */
+Node *
+RelationGetPartitionCheckExpr(Relation rel)
+{
+ /* Quick exit */
+ if (!relid_is_partition(RelationGetRelid(rel)))
+ return NULL;
+
+ /* Quick copy */
+ if (rel->rd_partcheck)
+ return copyObject(rel->rd_partcheck);
+
+ /* None of those, generate */
+ return generate_partition_check_expr(rel);
+}
+
+/*
+ * generate_partition_constraints
+ * Generate partition check constraint expression rel's pg_partition
+ * entry
+ *
+ * Result expression tree is stored CacheMemoryContext to ensure it survives
+ * as long as the relcache entry. But we should be running in a less long-lived
+ * working context. To avoid leaking cache memory if this routine fails partway
+ * routine fails partway through, we build in working memory and then copy
+ * the completed structure into cache memory.
+ */
+static Node *
+generate_partition_check_expr(Relation rel)
+{
+ HeapTuple tuple;
+ MemoryContext oldcxt;
+ Datum boundDatum;
+ bool isnull;
+ Node *bound;
+ Node *my_expr = NULL,
+ *result = NULL;
+ Oid relid = RelationGetRelid(rel);
+ Relation parent = heap_open(get_partition_parent(relid), NoLock);
+
+ /* quick copy */
+ if (rel->rd_partcheck)
+ {
+ heap_close(parent, NoLock);
+ return copyObject(rel->rd_partcheck);
+ }
+
+ tuple = SearchSysCache1(PARTRELID, relid);
+ Assert(HeapTupleIsValid(tuple));
+ boundDatum = SysCacheGetAttr(PARTRELID, tuple,
+ Anum_pg_partition_partbound,
+ &isnull);
+ Assert(!isnull);
+ bound = stringToNode(TextDatumGetCString(boundDatum));
+
+ my_expr = (Node *) get_check_expr_from_partbound(rel, parent, bound);
+
+ /*
+ * If parent is also a partition, create a AND_EXPR BoolExpr with just
+ * created expression and parent's check expression, as its args.
+ */
+ if (relid_is_partition(RelationGetRelid(parent)))
+ {
+ Node *parent_expr;
+
+ parent_expr = generate_partition_check_expr(parent);
+ result = (Node *) makeBoolExpr(AND_EXPR,
+ list_make2(parent_expr, my_expr), -1);
+ }
+ else
+ result = my_expr;
+
+ oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
+ rel->rd_partcheck = copyObject(result);
+ MemoryContextSwitchTo(oldcxt);
+
+ ReleaseSysCache(tuple);
+ heap_close(parent, NoLock);
+
+ return result;
+}
+
+/*
+ * RelationGetPartitionDescNode
+ * Recursively form partition node tree rooted at rel's node
+ */
+PartitionDescNode
+RelationGetPartitionDescNode(Relation rel)
+{
+ PartitionDescNode root;
+
+ /*
+ * Recurse to build the tree of partition descriptor nodes with
+ * rel's descriptor as the root of the tree.
+ */
+ root = GetPartitionDescNodeRecurse(rel, 0);
+ root->index = 0;
+ root->num_leaf_partitions = get_leaf_partition_count(root);
+
+ return root;
+}
+
+/*
+ * GetPartitionDescNodeRecurse
+ * Workhorse of RelationGetPartitionDescNode
+ *
+ * offset is the index (0-based) of the first leaf node in the tree, as
+ * as determined and passed by the parent or the left sibling.
+ */
+static PartitionDescNode
+GetPartitionDescNodeRecurse(Relation rel, int offset)
+{
+ PartitionDescNode root,
+ prev;
+ PartitionDesc pdesc = RelationGetPartitionDesc(rel);
+ int i;
+
+ /* First build our own node */
+ root = (PartitionDescNode) palloc0(sizeof(PartitionDescNodeData));
+ root->pdesc = pdesc;
+ root->relid = RelationGetRelid(rel);
+ root->offset = offset;
+ root->downlink = NULL;
+ root->next = NULL;
+
+ /*
+ * Recursively add nodes for partitions that are themselves partitioned,
+ * linking ourselves to the first such node via 'downlink'. Each node
+ * thus created (being on the same level) is linked to the right sibling
+ * via 'next'. For each node, determine the offset to pass and once
+ * created, update its index and num_leaf_partitions.
+ */
+ prev = NULL;
+ for (i = 0; i < pdesc->nparts; i++)
+ {
+ Oid relid = pdesc->oids[i];
+ int offset;
+ Relation rel;
+ PartitionDescNode pdnode;
+
+ if (!relid_is_partitioned(relid))
+ continue;
+
+ rel = heap_open(relid, AccessShareLock);
+
+ /* Offset for the new node */
+ if (prev)
+ /*
+ * Count leaves on the left - prev node has the account of those
+ * on its own left and then there may have been some siblings
+ * in-between that were leaves themselves.
+ */
+ offset = prev->offset + prev->num_leaf_partitions +
+ (i - prev->index - 1);
+ else
+ /*
+ * We're the first child - account of leaf nodes comes from the
+ * parent. Left siblings were all leaves themselves.
+ */
+ offset = root->offset + i;
+
+ pdnode = GetPartitionDescNodeRecurse(rel, offset);
+ pdnode->index = i;
+ pdnode->num_leaf_partitions = get_leaf_partition_count(pdnode);
+
+ heap_close(rel, AccessShareLock);
+
+ /* Found our first child; link to it. */
+ if (root->downlink == NULL)
+ root->downlink = pdnode;
+
+ /* Link new node to the left sibling, if any */
+ if (prev)
+ prev->next = pdnode;
+
+ prev = pdnode;
+ }
+
+ return root;
+}
+
+/*
+ * get_leaf_partition_count
+ * Recursively count the number of leaf partitions in the partition
+ * tree rooted at pdnode
+ */
+static int
+get_leaf_partition_count(PartitionDescNode pdnode)
+{
+ int i;
+ int result = 0;
+ PartitionDescNode node = pdnode->downlink;
+
+ for (i = 0; i < pdnode->pdesc->nparts; i++)
+ {
+ /* Indexes 0..(node->index - 1) are of leaf partitions */
+ if (node && i == node->index)
+ {
+ result += get_leaf_partition_count(node);
+ node = node->next;
+ }
+ else
+ result += 1;
+ }
+
+ return result;
+}
+
+/*
+ * get_leaf_partition_count
+ * Recursively compute the list of OIDs of leaf partitions in the
+ * partition tree rooted at pdnode
+ */
+List *
+get_leaf_partition_oids(PartitionDescNode pdnode)
+{
+ int i;
+ List *result = NIL;
+ Oid *myparts = pdnode->pdesc->oids;
+ int n_myparts = pdnode->pdesc->nparts;
+ PartitionDescNode node = pdnode->downlink;
+
+ for (i = 0; i < n_myparts; i++)
+ {
+ /* Indexes 0..(node->index - 1) are leaf partitions */
+ if (node && i == node->index)
+ {
+ result = list_concat(result, get_leaf_partition_oids(node));
+ node = node->next;
+ }
+ else
+ result = lappend_oid(result, myparts[i]);
+ }
+
+ return result;
+}
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 7623aaf..5437926 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -2464,7 +2464,7 @@ CopyFrom(CopyState cstate)
if (!skip_tuple)
{
/* Check the constraints of the tuple */
- if (cstate->rel->rd_att->constr)
+ if (cstate->rel->rd_att->constr || resultRelInfo->ri_PartitionCheck)
ExecConstraints(resultRelInfo, slot, estate);
if (useHeapMultiInsert)
diff --git a/src/backend/commands/lockcmds.c b/src/backend/commands/lockcmds.c
index 62671a8..5120c93 100644
--- a/src/backend/commands/lockcmds.c
+++ b/src/backend/commands/lockcmds.c
@@ -16,6 +16,7 @@
#include "access/heapam.h"
#include "catalog/namespace.h"
+#include "catalog/partition.h"
#include "catalog/pg_inherits_fn.h"
#include "commands/lockcmds.h"
#include "miscadmin.h"
@@ -63,7 +64,8 @@ LockTableCommand(LockStmt *lockstmt)
RangeVarCallbackForLockTable,
(void *) &lockstmt->mode);
- if (recurse)
+ /* Force inheritance recursion, if partitioned table. */
+ if (recurse || relid_is_partitioned(reloid))
LockTableRecurse(reloid, lockstmt->mode, lockstmt->nowait);
}
}
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index db1ee74..dda14c5 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -121,6 +121,19 @@ static List *on_commits = NIL;
/*
+ * For AT ATTACH PARTITION <table> FOR VALUES <partition_bound_spec>,
+ *
+ * Struct describing partition check expression corresponding to
+ * <partition_bound_spec> to validate for rows in <table> during
+ * Phase 3 scan.
+ */
+typedef struct PartitionCheck
+{
+ Expr *expr;
+ List *state;
+} PartitionCheck;
+
+/*
* State information for ALTER TABLE
*
* The pending-work queue for an ALTER TABLE is a List of AlteredTableInfo
@@ -163,6 +176,8 @@ typedef struct AlteredTableInfo
Oid newTableSpace; /* new tablespace; 0 means no change */
bool chgPersistence; /* T if SET LOGGED/UNLOGGED is used */
char newrelpersistence; /* if above is true */
+ PartitionCheck *partitionCheck; /* partition check expression for
+ * ATTACH PARTITION validation */
/* Objects to rebuild after completing ALTER TYPE operations */
List *changedConstraintOids; /* OIDs of constraints to rebuild */
List *changedConstraintDefs; /* string definitions of same */
@@ -755,6 +770,14 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
partattrs, partexprs, partopclass);
}
+ /* Process and store partition bound. */
+ if (stmt->partbound)
+ {
+ Oid parentId = linitial_oid(inheritOids);
+
+ StorePartitionBound(relationId, parentId, stmt->partbound);
+ }
+
ObjectAddressSet(address, RelationRelationId, relationId);
/*
@@ -995,6 +1018,13 @@ RangeVarCallbackForDropRelation(const RangeVar *rel, Oid relOid, Oid oldRelOid,
if (!OidIsValid(relOid))
return;
+ if (relid_is_partition(relOid))
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("\"%s\" is a partition of \"%s\"", rel->relname,
+ get_rel_name(get_partition_parent(relOid))),
+ errhint("Use ALTER TABLE DETACH PARTITION to be able to drop it.")));
+
tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relOid));
if (!HeapTupleIsValid(tuple))
return; /* concurrently dropped, so nothing to do */
@@ -1086,7 +1116,8 @@ ExecuteTruncate(TruncateStmt *stmt)
rels = lappend(rels, rel);
relids = lappend_oid(relids, myrelid);
- if (recurse)
+ /* Force inheritance recursion, if partitioned table. */
+ if (recurse || relid_is_partitioned(myrelid))
{
ListCell *child;
List *children;
@@ -1810,8 +1841,12 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
Node *expr;
bool found_whole_row;
- /* ignore if the constraint is non-inheritable */
- if (check[i].ccnoinherit)
+ /*
+ * ignore if the constraint is non-inheritable and this is not
+ * the partition case (constraints of partitioned tables are
+ * always present in their partitions)
+ */
+ if (check[i].ccnoinherit && !is_partition)
continue;
/* Adjust Vars to match new table's column numbering */
@@ -2246,6 +2281,11 @@ renameatt_check(Oid myrelid, Form_pg_class classform, bool recursing)
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot rename column of typed table")));
+ if (relid_is_partition(myrelid) && !recursing)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot rename column of a partition")));
+
/*
* Renaming the columns of sequences or toast tables doesn't actually
* break anything from the system's point of view, since internal
@@ -2472,11 +2512,13 @@ renameatt(RenameStmt *stmt)
return InvalidObjectAddress;
}
+ /* Force inheritance recursion, if partitioned table. */
attnum =
renameatt_internal(relid,
stmt->subname, /* old att name */
stmt->newname, /* new att name */
- interpretInhOption(stmt->relation->inhOpt), /* recursive? */
+ interpretInhOption(stmt->relation->inhOpt) ||
+ relid_is_partitioned(relid), /* recursive? */
false, /* recursing? */
0, /* expected inhcount */
stmt->behavior);
@@ -2624,11 +2666,13 @@ RenameConstraint(RenameStmt *stmt)
}
}
+ /* Force inheritance recursion, if partitioned table. */
return
rename_constraint_internal(relid, typid,
stmt->subname,
stmt->newname,
- stmt->relation ? interpretInhOption(stmt->relation->inhOpt) : false, /* recursive? */
+ stmt->relation ? interpretInhOption(stmt->relation->inhOpt) ||
+ relid_is_partitioned(relid) : false, /* recursive? */
false, /* recursing? */
0 /* expected inhcount */ );
@@ -2870,8 +2914,11 @@ AlterTable(Oid relid, LOCKMODE lockmode, AlterTableStmt *stmt)
CheckTableNotInUse(rel, "ALTER TABLE");
+ /* Force inheritance recursion, if partitioned table */
ATController(stmt,
- rel, stmt->cmds, interpretInhOption(stmt->relation->inhOpt),
+ rel, stmt->cmds,
+ interpretInhOption(stmt->relation->inhOpt) ||
+ relid_is_partitioned(RelationGetRelid(rel)),
lockmode);
}
@@ -3988,7 +4035,7 @@ ATRewriteTables(AlterTableStmt *parsetree, List **wqueue, LOCKMODE lockmode)
* Test the current data within the table against new constraints
* generated by ALTER TABLE commands, but don't rebuild data.
*/
- if (tab->constraints != NIL || tab->new_notnull)
+ if (tab->constraints != NIL || tab->new_notnull || tab->partitionCheck)
ATRewriteTable(tab, InvalidOid, lockmode);
/*
@@ -4132,6 +4179,21 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
}
}
+ /*
+ * Build expression execution states for partition check expression
+ * as well.
+ */
+ if (tab->partitionCheck)
+ {
+ List *expr;
+
+ needscan = true;
+ Assert(tab->partitionCheck->expr);
+ expr = list_make1(tab->partitionCheck->expr);
+ tab->partitionCheck->state = (List *)
+ ExecPrepareExpr((Expr *) expr, estate);
+ }
+
foreach(l, tab->newvals)
{
NewColumnValue *ex = lfirst(l);
@@ -4321,6 +4383,12 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
}
}
+ if (tab->partitionCheck &&
+ !ExecQual(tab->partitionCheck->state, econtext, true))
+ ereport(ERROR,
+ (errcode(ERRCODE_CHECK_VIOLATION),
+ errmsg("table contains a row violating the partition bound specification")));
+
/* Write the tuple out to the new relation */
if (newrel)
heap_insert(newrel, tuple, mycid, hi_options, bistate);
@@ -4518,7 +4586,8 @@ ATSimpleRecursion(List **wqueue, Relation rel,
*/
if (recurse &&
(rel->rd_rel->relkind == RELKIND_RELATION ||
- rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE))
+ rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE ||
+ rel->rd_rel->relkind == RELKIND_PARTITIONED_REL))
{
Oid relid = RelationGetRelid(rel);
ListCell *child;
@@ -4840,6 +4909,11 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
if (recursing)
ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
+ if (relid_is_partition(RelationGetRelid(rel)) && !recursing)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot add column to a partition")));
+
attrdesc = heap_open(AttributeRelationId, RowExclusiveLock);
/*
@@ -5295,6 +5369,7 @@ ATExecDropNotNull(Relation rel, const char *colName, LOCKMODE lockmode)
List *indexoidlist;
ListCell *indexoidscan;
ObjectAddress address;
+ Oid relid;
/*
* lookup the attribute
@@ -5362,6 +5437,28 @@ ATExecDropNotNull(Relation rel, const char *colName, LOCKMODE lockmode)
list_free(indexoidlist);
/*
+ * If rel is partition, throw error if the attribute is not null in
+ * parent
+ */
+ relid = RelationGetRelid(rel);
+ if (relid_is_partition(relid))
+ {
+ Oid parentId = get_partition_parent(relid);
+ Relation parent = heap_open(parentId, AccessShareLock);
+ TupleDesc tupDesc = RelationGetDescr(parent);
+ AttrNumber parent_attnum;
+
+ parent_attnum = get_attnum(parentId, colName);
+ if (tupDesc->attrs[parent_attnum - 1]->attnotnull)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("column \"%s\" is not null in parent",
+ colName),
+ errhint("Please drop not null in the parent instead")));
+ heap_close(parent, AccessShareLock);
+ }
+
+ /*
* Okay, actually perform the catalog change ... if needed
*/
if (((Form_pg_attribute) GETSTRUCT(tuple))->attnotnull)
@@ -5891,6 +5988,11 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
if (recursing)
ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
+ if (relid_is_partition(RelationGetRelid(rel)) && !recursing)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot drop column from a partition")));
+
/*
* get the number of the attribute
*/
@@ -5936,11 +6038,13 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
if (!is_expr)
ereport(ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
- errmsg("cannot drop column named in partition key")));
+ errmsg("cannot drop column named in%s partition key",
+ recursing ? " downstream" : "")));
else
ereport(ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
- errmsg("cannot drop column referenced in partition key expression")));
+ errmsg("cannot drop column referenced in%s partition key expression",
+ recursing ? " downstream" : "")));
}
ReleaseSysCache(tuple);
@@ -6374,8 +6478,9 @@ ATAddCheckConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
/*
* If adding a NO INHERIT constraint, no need to find our children.
+ * However, that applies only to regular inheritance parents.
*/
- if (constr->is_no_inherit)
+ if (constr->is_no_inherit && !relid_is_partitioned(RelationGetRelid(rel)))
return address;
/*
@@ -7936,9 +8041,12 @@ ATExecDropConstraint(Relation rel, const char *constrName,
/*
* Propagate to children as appropriate. Unlike most other ALTER
* routines, we have to do this one level of recursion at a time; we can't
- * use find_all_inheritors to do it in one pass.
+ * use find_all_inheritors to do it in one pass. Note that if the parent
+ * is a partitioned table, we propagate to children (partitions) despite
+ * is_no_inherit_constraint.
*/
- if (!is_no_inherit_constraint)
+ if (!is_no_inherit_constraint ||
+ relid_is_partitioned(RelationGetRelid(rel)))
children = find_inheritance_children(RelationGetRelid(rel), lockmode);
else
children = NIL;
@@ -8068,6 +8176,11 @@ ATPrepAlterColumnType(List **wqueue,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot alter column type of typed table")));
+ if (relid_is_partition(RelationGetRelid(rel)) && !recursing)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot alter column type of a partition")));
+
/* lookup the attribute so we can check inheritance status */
tuple = SearchSysCacheAttName(RelationGetRelid(rel), colName);
if (!HeapTupleIsValid(tuple))
@@ -8098,11 +8211,13 @@ ATPrepAlterColumnType(List **wqueue,
if (!is_expr)
ereport(ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
- errmsg("cannot alter column named in partition key")));
+ errmsg("cannot alter column named in%s partition key",
+ recursing ? " downstream" : "")));
else
ereport(ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
- errmsg("cannot alter column referenced in partition key expression")));
+ errmsg("cannot alter column referenced in%s partition key expression",
+ recursing ? " downstream" : "")));
}
/* Look up the target type */
@@ -10235,6 +10350,11 @@ ATPrepAddInherit(Relation child_rel)
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot change inheritance of typed table")));
+ if (relid_is_partition(RelationGetRelid(child_rel)))
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot change inheritance of a partition")));
+
if (child_rel->rd_rel->relkind == RELKIND_PARTITIONED_REL)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
@@ -10291,6 +10411,11 @@ ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode)
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot inherit from partitioned table \"%s\"", parent->relname)));
+ if (relid_is_partition(RelationGetRelid(parent_rel)))
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot inherit from a partition")));
+
/*
* Prevent circularity by seeing if proposed parent inherits from child.
* (In particular, this disallows making a rel inherit from itself.)
@@ -10677,6 +10802,11 @@ ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
ObjectAddress address;
Relation parent_rel;
+ if (relid_is_partition(RelationGetRelid(rel)))
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot change inheritance of a partition")));
+
/*
* AccessShareLock on the parent is probably enough, seeing that DROP
* TABLE doesn't lock parent tables at all. We need some lock since we'll
@@ -12566,6 +12696,12 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
*/
ATSimplePermissions(attachRel, ATT_TABLE);
+ if (relid_is_partition(RelationGetRelid(attachRel)))
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("\"%s\" is already a partition",
+ RelationGetRelationName(attachRel))));
+
if (attachRel->rd_rel->reloftype)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
@@ -12599,6 +12735,12 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
errmsg("cannot attach a temporary relation of another session as partition ")));
/* If parent has OIDs, attachRel must have OIDs */
+ if (attachRel->rd_rel->reloftype)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot attach a typed table as partition")));
+
+ /* If parent has OIDs then child must have OIDs */
if (rel->rd_rel->relhasoids && !attachRel->rd_rel->relhasoids)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
@@ -12641,6 +12783,30 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
/* OK to create inheritance. Rest of the checks performed there */
CreateInheritance(attachRel, rel);
+ /*
+ * Set up to have the rows in table to be checked for violation of the
+ * partition bound spec in phase 3 scan.
+ */
+ if (!cmd->skip_validate)
+ {
+ AlteredTableInfo *tab;
+ PartitionCheck *check;
+ Expr *my_expr,
+ *parent_expr = (Expr *) RelationGetPartitionCheckExpr(rel);
+
+ tab = ATGetQueueEntry(wqueue, attachRel);
+ check = (PartitionCheck *) palloc0(sizeof(PartitionCheck));
+ my_expr = get_check_expr_from_partbound(attachRel, rel, cmd->bound);
+ check->expr = parent_expr ? makeBoolExpr(AND_EXPR,
+ list_make2(parent_expr, my_expr), -1)
+ : my_expr;
+ tab->partitionCheck = check;
+ }
+
+ /* Store bound into the catalog */
+ StorePartitionBound(RelationGetRelid(attachRel), RelationGetRelid(rel),
+ cmd->bound);
+
ObjectAddressSet(address, RelationRelationId, RelationGetRelid(attachRel));
/* keep our lock until commit */
@@ -12665,6 +12831,15 @@ ATExecDetachPartition(Relation rel, RangeVar *name)
/* All inheritance related checks are performed within the function */
RemoveInheritance(partRel, rel);
+ /* Remove the pg_partition entry */
+ RemovePartitionEntryByRelId(RelationGetRelid(partRel));
+
+ /*
+ * Invalidate the relcache so that the partition is no longer included
+ * in our partition descriptor.
+ */
+ CacheInvalidateRelcache(rel);
+
ObjectAddressSet(address, RelationRelationId, RelationGetRelid(partRel));
/* keep our lock until commit */
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index ab6ec1a..230324e 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -42,6 +42,7 @@
#include "access/transam.h"
#include "access/xact.h"
#include "catalog/namespace.h"
+#include "catalog/partition.h"
#include "commands/matview.h"
#include "commands/trigger.h"
#include "executor/execdebug.h"
@@ -1251,6 +1252,8 @@ InitResultRelInfo(ResultRelInfo *resultRelInfo,
resultRelInfo->ri_ConstraintExprs = NULL;
resultRelInfo->ri_junkFilter = NULL;
resultRelInfo->ri_projectReturning = NULL;
+ resultRelInfo->ri_PartitionCheck = (Expr *)
+ RelationGetPartitionCheckExpr(resultRelationDesc);
}
/*
@@ -1692,6 +1695,49 @@ ExecRelCheck(ResultRelInfo *resultRelInfo,
return NULL;
}
+/*
+ * ExecPartitionCheck --- check that tuple meets the partition boundary
+ * specification.
+ *
+ * Note: This is called, *iff* resultRelInfo is the main target table.
+ */
+static bool
+ExecPartitionCheck(ResultRelInfo *resultRelInfo, TupleTableSlot *slot,
+ EState *estate)
+{
+ ExprContext *econtext;
+
+ /*
+ * If first time through, build expression state tree for the partition
+ * check expression. Keep it in the per-query memory context so they'll
+ * survive throughout the query.
+ */
+ if (resultRelInfo->ri_PartitionCheckExpr == NULL)
+ {
+ List *qual = make_ands_implicit(resultRelInfo->ri_PartitionCheck);
+ resultRelInfo->ri_PartitionCheckExpr = (List *)
+ ExecPrepareExpr((Expr *) qual, estate);
+ }
+
+ /*
+ * We will use the EState's per-tuple context for evaluating constraint
+ * expressions (creating it if it's not already there).
+ */
+ econtext = GetPerTupleExprContext(estate);
+
+ /* Arrange for econtext's scan tuple to be the tuple under test */
+ econtext->ecxt_scantuple = slot;
+
+ /*
+ * NOTE: SQL specifies that a NULL result from a constraint expression
+ * is not to be treated as a failure. Therefore, tell ExecQual to
+ * return TRUE for NULL.
+ *
+ * XXX - although, it's unlikely that NULL would result.
+ */
+ return ExecQual(resultRelInfo->ri_PartitionCheckExpr, econtext, true);
+}
+
void
ExecConstraints(ResultRelInfo *resultRelInfo,
TupleTableSlot *slot, EState *estate)
@@ -1703,9 +1749,9 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
Bitmapset *insertedCols;
Bitmapset *updatedCols;
- Assert(constr);
+ Assert(constr || resultRelInfo->ri_PartitionCheck);
- if (constr->has_not_null)
+ if (constr && constr->has_not_null)
{
int natts = tupdesc->natts;
int attrChk;
@@ -1736,7 +1782,7 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
}
}
- if (constr->num_check > 0)
+ if (constr && constr->num_check > 0)
{
const char *failed;
@@ -1760,6 +1806,29 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
errtableconstraint(rel, failed)));
}
}
+
+ if (resultRelInfo->ri_PartitionCheck)
+ {
+ if (!ExecPartitionCheck(resultRelInfo, slot, estate))
+ {
+ char *val_desc;
+
+ insertedCols = GetInsertedColumns(resultRelInfo, estate);
+ updatedCols = GetUpdatedColumns(resultRelInfo, estate);
+ modifiedCols = bms_union(insertedCols, updatedCols);
+ val_desc = ExecBuildSlotValueDescription(RelationGetRelid(rel),
+ slot,
+ tupdesc,
+ modifiedCols,
+ 64);
+ ereport(ERROR,
+ (errcode(ERRCODE_CHECK_VIOLATION),
+ errmsg("new row violates the partition boundary"
+ " specification of \"%s\"",
+ RelationGetRelationName(rel)),
+ val_desc ? errdetail("Failing row contains %s.", val_desc) : 0));
+ }
+ }
}
/*
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 7ad1140..6ecb6be 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -354,7 +354,7 @@ ExecInsert(ModifyTableState *mtstate,
/*
* Check the constraints of the tuple
*/
- if (resultRelationDesc->rd_att->constr)
+ if (resultRelationDesc->rd_att->constr || resultRelInfo->ri_PartitionCheck)
ExecConstraints(resultRelInfo, slot, estate);
if (onconflict != ONCONFLICT_NONE && resultRelInfo->ri_NumIndices > 0)
@@ -907,7 +907,7 @@ lreplace:;
/*
* Check the constraints of the tuple
*/
- if (resultRelationDesc->rd_att->constr)
+ if (resultRelationDesc->rd_att->constr || resultRelInfo->ri_PartitionCheck)
ExecConstraints(resultRelInfo, slot, estate);
/*
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 6f28047..1ea1439 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -2249,6 +2249,35 @@ _readExtensibleNode(void)
}
/*
+ * _readPartitionListSpec
+ */
+static PartitionListSpec *
+_readPartitionListSpec(void)
+{
+ READ_LOCALS(PartitionListSpec);
+
+ READ_NODE_FIELD(values);
+
+ READ_DONE();
+}
+
+/*
+ * _readPartitionRangeSpec
+ */
+static PartitionRangeSpec *
+_readPartitionRangeSpec(void)
+{
+ READ_LOCALS(PartitionRangeSpec);
+
+ READ_BOOL_FIELD(lowerinc);
+ READ_NODE_FIELD(lower);
+ READ_BOOL_FIELD(upperinc);
+ READ_NODE_FIELD(upper);
+
+ READ_DONE();
+}
+
+/*
* parseNodeString
*
* Given a character string representing a node tree, parseNodeString creates
@@ -2478,6 +2507,10 @@ parseNodeString(void)
return_value = _readAlternativeSubPlan();
else if (MATCH("EXTENSIBLENODE", 14))
return_value = _readExtensibleNode();
+ else if (MATCH("PARTITIONLISTVALUES", 19))
+ return_value = _readPartitionListSpec();
+ else if (MATCH("PARTITIONRANGE", 14))
+ return_value = _readPartitionRangeSpec();
else
{
elog(ERROR, "badly formatted node string \"%.32s\"...", token);
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index 3a585ad..af4939b 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -27,6 +27,7 @@
#include "catalog/catalog.h"
#include "catalog/dependency.h"
#include "catalog/heap.h"
+#include "catalog/partition.h"
#include "catalog/pg_am.h"
#include "foreign/fdwapi.h"
#include "miscadmin.h"
@@ -1029,6 +1030,7 @@ get_relation_constraints(PlannerInfo *root,
Index varno = rel->relid;
Relation relation;
TupleConstr *constr;
+ Node *pexpr;
/*
* We assume the relation has already been safely locked.
@@ -1108,6 +1110,17 @@ get_relation_constraints(PlannerInfo *root,
}
}
+ /* Append partition check constraint, if any */
+ pexpr = RelationGetPartitionCheckExpr(relation);
+ if (pexpr)
+ {
+ /* Fix Vars to have the desired varno */
+ if (varno != 1)
+ ChangeVarNodes(pexpr, 1, varno, 0);
+
+ result = list_concat(result, make_ands_implicit((Expr *) pexpr));
+ }
+
heap_close(relation, NoLock);
return result;
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 29c8c4e..b4fca37 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -25,6 +25,7 @@
#include "postgres.h"
#include "access/sysattr.h"
+#include "catalog/partition.h"
#include "catalog/pg_type.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
diff --git a/src/backend/parser/parse_clause.c b/src/backend/parser/parse_clause.c
index 307e383..5124dbf 100644
--- a/src/backend/parser/parse_clause.c
+++ b/src/backend/parser/parse_clause.c
@@ -202,8 +202,8 @@ setTargetTable(ParseState *pstate, RangeVar *relation,
pstate->p_target_rangetblentry = rte;
/*
- * Override specified inheritance option, if relation is a partitioned
- * table and it's a target of INSERT (ie, alsoSource == false).
+ * For UPDATE/DELETE, force inheritance recursion, if partitioned
+ * table.
*/
if (alsoSource)
rte->inh |= relid_is_partitioned(rte->relid);
diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c
index abcc1d6..b3870f6 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -1218,8 +1218,7 @@ addRangeTableEntry(ParseState *pstate,
* The initial default on access checks is always check-for-READ-access,
* which is the right thing for all except target tables.
*
- * Note: we override the specified inheritance option, if relation is a
- * partitioned table.
+ * Note: We force inheritance recursion, if partitioned table.
*/
rte->lateral = false;
rte->inh = inh || relid_is_partitioned(RelationGetRelid(rel));
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e69cbfc..a477883 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -32,6 +32,7 @@
#include "catalog/dependency.h"
#include "catalog/heap.h"
#include "catalog/index.h"
+#include "catalog/partition.h"
#include "catalog/namespace.h"
#include "catalog/partition.h"
#include "catalog/pg_am.h"
@@ -3105,6 +3106,11 @@ transformPartitionOf(CreateStmtContext *cxt, Node *bound)
if (parentRel->rd_rel->relhasoids)
cxt->hasoids = true;
+ /*
+ * Create ColumnDefs correponding to parent's attributes as "inherited"
+ * attributes. Also, set is_for_partition whose function is similar to
+ * is_from_type for MergeAttributes()'s purposes.
+ */
tupdesc = RelationGetDescr(parentRel);
for (i = 0; i < tupdesc->natts; i++)
{
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index a9e077f..dae706e 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -8224,6 +8224,84 @@ get_rule_expr(Node *node, deparse_context *context,
}
break;
+ case T_PartitionListSpec:
+ {
+ PartitionListSpec *list_spec = (PartitionListSpec *) node;
+ ListCell *cell;
+ char *sep;
+
+ appendStringInfoString(buf, "FOR VALUES");
+
+ appendStringInfoString(buf, " IN (");
+ sep = "";
+ foreach (cell, list_spec->values)
+ {
+ Const *val = lfirst(cell);
+
+ appendStringInfoString(buf, sep);
+ get_const_expr(val, context, -1);
+ sep = ", ";
+ }
+
+ appendStringInfoString(buf, ")");
+ }
+ break;
+
+ case T_PartitionRangeSpec:
+ {
+ PartitionRangeSpec *range_spec = (PartitionRangeSpec *) node;
+ ListCell *cell;
+ char *sep;
+
+ appendStringInfoString(buf, "FOR VALUES");
+
+ appendStringInfoString(buf, " START");
+ if (!range_spec->lower)
+ appendStringInfoString(buf, " UNBOUNDED");
+ else
+ {
+ appendStringInfoString(buf, " (");
+
+ sep = "";
+ foreach (cell, range_spec->lower)
+ {
+ Const *val = lfirst(cell);
+
+ appendStringInfoString(buf, sep);
+ get_const_expr(val, context, -1);
+ sep = ", ";
+ }
+ appendStringInfoString(buf, ")");
+
+ if (!range_spec->lowerinc)
+ appendStringInfoString(buf, " EXCLUSIVE");
+ }
+
+ appendStringInfoString(buf, " END");
+
+ if (!range_spec->upper)
+ appendStringInfoString(buf, " UNBOUNDED");
+ else
+ {
+ appendStringInfoString(buf, " (");
+
+ sep = "";
+ foreach (cell, range_spec->upper)
+ {
+ Const *val = lfirst(cell);
+
+ appendStringInfoString(buf, sep);
+ get_const_expr(val, context, -1);
+ sep = ", ";
+ }
+ appendStringInfoString(buf, ")");
+
+ if (range_spec->upperinc)
+ appendStringInfoString(buf, " INCLUSIVE");
+ }
+ }
+ break;
+
case T_List:
{
char *sep;
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index d66a1bf..87091e6 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -71,6 +71,7 @@
#include "storage/smgr.h"
#include "utils/array.h"
#include "utils/builtins.h"
+#include "utils/datum.h"
#include "utils/fmgroids.h"
#include "utils/inval.h"
#include "utils/lsyscache.h"
@@ -279,6 +280,19 @@ static OpClassCacheEnt *LookupOpclassInfo(Oid operatorClassOid,
StrategyNumber numSupport);
static void RelationCacheInitFileRemoveInDir(const char *tblspcpath);
static void unlink_initfile(const char *initfilename);
+static void RelationBuildPartitionDesc(Relation relation);
+static int32 range_partition_cmp_upper(const void *a, const void *b,
+ void *arg);
+static PartitionListBound *copyPartitionListBound(PartitionListBound *src,
+ PartitionKey key);
+static PartitionRangeBound *copyPartitionRangeBound(PartitionRangeBound *src,
+ PartitionKey key);
+static bool equalPartitionDescs(PartitionKey key, PartitionDesc pdesc1,
+ PartitionDesc pdesc2);
+static bool equalListBounds(PartitionKey key, PartitionListBound *lb1,
+ PartitionListBound *lb2);
+static bool equalRangeBounds(PartitionKey key, PartitionRangeBound *rb1,
+ PartitionRangeBound *rb2);
/*
@@ -928,6 +942,86 @@ equalRSDesc(RowSecurityDesc *rsdesc1, RowSecurityDesc *rsdesc2)
}
/*
+ * equalPartitionDescs
+ * Compare two partition descriptors for logical equality
+ */
+static bool
+equalPartitionDescs(PartitionKey key, PartitionDesc pdesc1,
+ PartitionDesc pdesc2)
+{
+ int i;
+
+ if (pdesc1 != NULL)
+ {
+ if (pdesc2 == NULL)
+ return false;
+ if (pdesc1->nparts != pdesc2->nparts)
+ return false;
+
+ Assert(key != NULL || pdesc1->nparts == 0);
+
+ for (i = 0; i < pdesc1->nparts; i++)
+ {
+ if (pdesc1->oids[i] != pdesc2->oids[i])
+ return false;
+
+ if (pdesc1->lists &&
+ !equalListBounds(key, pdesc1->lists[i], pdesc2->lists[i]))
+ return false;
+
+ if (pdesc1->rangelowers &&
+ (!equalRangeBounds(key, pdesc1->rangelowers[i],
+ pdesc2->rangelowers[i]) ||
+ !equalRangeBounds(key, pdesc1->rangeuppers[i],
+ pdesc2->rangeuppers[i])))
+ return false;
+ }
+ }
+ else if (pdesc2 != NULL)
+ return false;
+
+ return true;
+}
+
+/* Are two listbounds equal (contain same values)? */
+static bool
+equalListBounds(PartitionKey key, PartitionListBound *lb1,
+ PartitionListBound *lb2)
+{
+ int i,
+ j;
+
+ if (lb1->nvalues != lb2->nvalues)
+ return false;
+
+ if (lb1->contains_null != lb2->contains_null)
+ return false;
+
+ for (i = 0; i < lb1->nvalues; i++)
+ for (j = 0; j < lb2->nvalues; j++)
+ /* Non-zero result means not equal */
+ if (DatumGetInt32(FunctionCall2Coll(&key->partsupfunc[0],
+ key->tcinfo->typcoll[0],
+ lb1->values[i],
+ lb2->values[j])))
+ return false;
+
+ return true;
+}
+
+/*
+ * Are two rangebounds equal?
+ *
+ * It's clear in this case that both are either lower bounds or upper bounds.
+ */
+static bool
+equalRangeBounds(PartitionKey key, PartitionRangeBound *rb1,
+ PartitionRangeBound *rb2)
+{
+ return partition_range_cmp_bounds(key, rb1, rb2) == 0;
+}
+
+/*
* RelationBuildDesc
*
* Build a relation descriptor. The caller must hold at least
@@ -1053,9 +1147,16 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
/* if it's a partitioned table, initialize key info */
if (relation->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+ {
RelationBuildPartitionKey(relation);
+ RelationBuildPartitionDesc(relation);
+ }
else
+ {
relation->rd_partkey = NULL;
+ relation->rd_partdesc = NULL;
+ relation->rd_pdcxt = NULL;
+ }
/*
* if it's an index, initialize index-related information
@@ -1399,6 +1500,164 @@ IndexSupportInitialize(oidvector *indclass,
}
/*
+ * RelationBuildPartitionDesc
+ * Form relation's partition descriptor from information in pg_partition
+ * and store it in the relcache
+ *
+ * Not flushed from the cache unless changed.
+ */
+static void
+RelationBuildPartitionDesc(Relation relation)
+{
+ int i,
+ nparts;
+ PartitionKey key = RelationGetPartitionKey(relation);
+ PartitionDesc result;
+ PartitionInfo **parts;
+ MemoryContext oldcxt;
+
+ /* Our pg_partitioned entry not up yet. */
+ if (key == NULL)
+ return;
+
+ /* Read partitions from the catalog */
+ parts = get_partition_info(relation, &nparts);
+
+ relation->rd_pdcxt = AllocSetContextCreate(CacheMemoryContext,
+ RelationGetRelationName(relation),
+ ALLOCSET_DEFAULT_MINSIZE,
+ ALLOCSET_DEFAULT_INITSIZE,
+ ALLOCSET_DEFAULT_MAXSIZE);
+
+ oldcxt = MemoryContextSwitchTo(relation->rd_pdcxt);
+ result = (PartitionDescData *) palloc0(sizeof(PartitionDescData));
+
+ result->nparts = nparts;
+ if (parts)
+ {
+ result->oids = (Oid *) palloc0(nparts * sizeof(Oid));
+
+ switch (key->strategy)
+ {
+ case PARTITION_STRAT_LIST:
+ result->lists = (PartitionListBound **) palloc0(nparts *
+ sizeof(PartitionListBound *));
+ break;
+
+ case PARTITION_STRAT_RANGE:
+ result->rangelowers = (PartitionRangeBound **) palloc0(nparts *
+ sizeof(PartitionRangeBound *));
+ result->rangeuppers = (PartitionRangeBound **) palloc0(nparts *
+ sizeof(PartitionRangeBound *));
+
+ /* Sort on range->upper */
+ qsort_arg(parts, nparts, sizeof(PartitionInfo *),
+ range_partition_cmp_upper, key);
+ break;
+ }
+
+ for (i = 0; i < nparts; i++)
+ {
+ result->oids[i] = parts[i]->oid;
+
+ if (parts[i]->list)
+ result->lists[i] = copyPartitionListBound(parts[i]->list, key);
+ else
+ {
+ result->rangelowers[i] = copyPartitionRangeBound(parts[i]->rangelower, key);
+ result->rangeuppers[i] = copyPartitionRangeBound(parts[i]->rangeupper, key);
+ }
+ }
+
+ /* Clean up */
+ free_partition_info(parts, nparts);
+ }
+
+ MemoryContextSwitchTo(oldcxt);
+ relation->rd_partdesc = result;
+}
+
+/*
+ * range_partition_cmp_upper
+ * Compare two range partition PartitionInfos using their rangeupper
+ * bounds
+ *
+ * Used as a callback to qsort_arg for sorting range partitions.
+ */
+static int32
+range_partition_cmp_upper(const void *a, const void *b, void *arg)
+{
+ int32 result;
+ int i;
+ PartitionKey key = (PartitionKey) arg;
+ PartitionRangeBound *b1 = (*(const PartitionInfo **) a)->rangeupper,
+ *b2 = (*(const PartitionInfo **) b)->rangeupper;
+
+ if (b1->infinite || b2->infinite)
+ return b1->infinite ? 1 : -1;
+
+ for (i = 0; i < key->partnatts; i++)
+ {
+ result = DatumGetInt32(FunctionCall2Coll(&key->partsupfunc[i],
+ key->tcinfo->typcoll[i],
+ b1->val[i], b2->val[i]));
+ if (!result)
+ continue;
+
+ break;
+ }
+
+ return result;
+}
+
+/*
+ * Helper routine to copy a list partition bound struct
+ */
+static PartitionListBound *
+copyPartitionListBound(PartitionListBound *src, PartitionKey key)
+{
+ int i;
+ PartitionListBound *result;
+
+ result = (PartitionListBound *) palloc0(sizeof(PartitionListBound));
+ result->nvalues = src->nvalues;
+ result->contains_null = src->contains_null;
+ result->values = (Datum *) palloc0(src->nvalues * sizeof(Datum));
+
+ for (i = 0; i < src->nvalues; i++)
+ result->values[i] = datumCopy(src->values[i],
+ key->tcinfo->typbyval[0],
+ key->tcinfo->typlen[0]);
+ return result;
+}
+
+/*
+ * Helper routine to copy a range partition bound struct
+ */
+static PartitionRangeBound *
+copyPartitionRangeBound(PartitionRangeBound *src, PartitionKey key)
+{
+ int i;
+ PartitionRangeBound *result;
+
+ result = (PartitionRangeBound *) palloc0(sizeof(PartitionRangeBound));
+ result->infinite = src->infinite;
+ result->inclusive = src->inclusive;
+ result->lower = src->lower;
+
+ if (src->val)
+ {
+ result->val = (Datum *) palloc0(key->partnatts * sizeof(Datum));
+ for (i = 0; i < key->partnatts; i++)
+ result->val[i] = datumCopy(src->val[i],
+ key->tcinfo->typbyval[i],
+ key->tcinfo->typlen[i]);
+ }
+
+ return result;
+}
+
+/*
* LookupOpclassInfo
*
* This routine maintains a per-opclass cache of the information needed
@@ -2054,6 +2313,10 @@ RelationDestroyRelation(Relation relation, bool remember_tupdesc)
if (relation->rd_rsdesc)
MemoryContextDelete(relation->rd_rsdesc->rscxt);
FreePartitionKey(relation->rd_partkey);
+ if (relation->rd_pdcxt)
+ MemoryContextDelete(relation->rd_pdcxt);
+ if (relation->rd_partcheck)
+ pfree(relation->rd_partcheck);
if (relation->rd_fdwroutine)
pfree(relation->rd_fdwroutine);
pfree(relation);
@@ -2217,6 +2480,7 @@ RelationClearRelation(Relation relation, bool rebuild)
bool keep_tupdesc;
bool keep_rules;
bool keep_policies;
+ bool keep_partdesc;
/* Build temporary entry, but don't link it into hashtable */
newrel = RelationBuildDesc(save_relid, false);
@@ -2247,6 +2511,9 @@ RelationClearRelation(Relation relation, bool rebuild)
keep_tupdesc = equalTupleDescs(relation->rd_att, newrel->rd_att);
keep_rules = equalRuleLocks(relation->rd_rules, newrel->rd_rules);
keep_policies = equalRSDesc(relation->rd_rsdesc, newrel->rd_rsdesc);
+ keep_partdesc = equalPartitionDescs(relation->rd_partkey,
+ relation->rd_partdesc,
+ newrel->rd_partdesc);
/*
* Perform swapping of the relcache entry contents. Within this
@@ -2302,6 +2569,13 @@ RelationClearRelation(Relation relation, bool rebuild)
/* pgstat_info must be preserved */
SWAPFIELD(struct PgStat_TableStatus *, pgstat_info);
+ /* preserve old partdesc if no logical change */
+ if (keep_partdesc)
+ {
+ SWAPFIELD(PartitionDesc, rd_partdesc);
+ SWAPFIELD(MemoryContext, rd_pdcxt);
+ }
+
#undef SWAPFIELD
/* And now we can throw away the temporary entry */
@@ -3528,6 +3802,20 @@ RelationCacheInitializePhase3(void)
restart = true;
}
+ /*
+ * Reload partition key and descriptor for a partitioned table.
+ */
+ if (relation->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+ {
+ RelationBuildPartitionKey(relation);
+ Assert(relation->rd_partkey != NULL);
+
+ RelationBuildPartitionDesc(relation);
+ Assert(relation->rd_partdesc != NULL);
+
+ restart = true;
+ }
+
/* Release hold on the relation */
RelationDecrementReferenceCount(relation);
@@ -4910,6 +5198,8 @@ load_relcache_init_file(bool shared)
rel->trigdesc = NULL;
rel->rd_rsdesc = NULL;
rel->rd_partkey = NULL;
+ rel->rd_partdesc = NULL;
+ rel->rd_partcheck = NULL;
rel->rd_indexprs = NIL;
rel->rd_indpred = NIL;
rel->rd_exclops = NULL;
diff --git a/src/backend/utils/cache/syscache.c b/src/backend/utils/cache/syscache.c
index a6563fe..4a5783a 100644
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -49,6 +49,7 @@
#include "catalog/pg_operator.h"
#include "catalog/pg_opfamily.h"
#include "catalog/pg_partitioned.h"
+#include "catalog/pg_partition.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_range.h"
#include "catalog/pg_rewrite.h"
@@ -569,6 +570,17 @@ static const struct cachedesc cacheinfo[] = {
},
8
},
+ {PartitionRelationId, /* PARTRELID */
+ PartitionRelidIndexId,
+ 1,
+ {
+ Anum_pg_partition_partrelid,
+ 0,
+ 0,
+ 0
+ },
+ 128
+ },
{PartitionedRelationId, /* PARTEDRELID */
PartitionedRelidIndexId,
1,
diff --git a/src/bin/pg_dump/common.c b/src/bin/pg_dump/common.c
index 1cbb987..c8e56bd 100644
--- a/src/bin/pg_dump/common.c
+++ b/src/bin/pg_dump/common.c
@@ -68,6 +68,8 @@ static int numextmembers;
static void flagInhTables(TableInfo *tbinfo, int numTables,
InhInfo *inhinfo, int numInherits);
+static void flagPartitions(TableInfo *tblinfo, int numTables,
+ PartInfo *partinfo, int numPartitions);
static void flagInhAttrs(DumpOptions *dopt, TableInfo *tblinfo, int numTables);
static DumpableObject **buildIndexArray(void *objArray, int numObjs,
Size objSize);
@@ -75,6 +77,8 @@ static int DOCatalogIdCompare(const void *p1, const void *p2);
static int ExtensionMemberIdCompare(const void *p1, const void *p2);
static void findParentsByOid(TableInfo *self,
InhInfo *inhinfo, int numInherits);
+static void findPartitionParentByOid(TableInfo *self, PartInfo *partinfo,
+ int numPartitions);
static int strInArray(const char *pattern, char **arr, int arr_size);
@@ -93,8 +97,10 @@ getSchemaData(Archive *fout, int *numTablesPtr)
NamespaceInfo *nspinfo;
ExtensionInfo *extinfo;
InhInfo *inhinfo;
+ PartInfo *partinfo;
int numAggregates;
int numInherits;
+ int numPartitions;
int numRules;
int numProcLangs;
int numCasts;
@@ -232,6 +238,10 @@ getSchemaData(Archive *fout, int *numTablesPtr)
inhinfo = getInherits(fout, &numInherits);
if (g_verbose)
+ write_msg(NULL, "reading partition information\n");
+ partinfo = getPartitions(fout, &numPartitions);
+
+ if (g_verbose)
write_msg(NULL, "reading event triggers\n");
getEventTriggers(fout, &numEventTriggers);
@@ -245,6 +255,11 @@ getSchemaData(Archive *fout, int *numTablesPtr)
write_msg(NULL, "finding inheritance relationships\n");
flagInhTables(tblinfo, numTables, inhinfo, numInherits);
+ /* Link tables to partition parents, mark parents as interesting */
+ if (g_verbose)
+ write_msg(NULL, "finding partition relationships\n");
+ flagPartitions(tblinfo, numTables, partinfo, numPartitions);
+
if (g_verbose)
write_msg(NULL, "reading column info for interesting tables\n");
getTableAttrs(fout, tblinfo, numTables);
@@ -319,6 +334,43 @@ flagInhTables(TableInfo *tblinfo, int numTables,
}
}
+/* flagPartitions -
+ * Fill in parent link fields of every target table that is partition,
+ * and mark parents of partitions as interesting
+ *
+ * modifies tblinfo
+ */
+static void
+flagPartitions(TableInfo *tblinfo, int numTables,
+ PartInfo *partinfo, int numPartitions)
+{
+ int i;
+
+ for (i = 0; i < numTables; i++)
+ {
+ /* Some kinds are never partitions */
+ if (tblinfo[i].relkind == RELKIND_SEQUENCE ||
+ tblinfo[i].relkind == RELKIND_VIEW ||
+ tblinfo[i].relkind == RELKIND_MATVIEW)
+ continue;
+
+ /* Don't bother computing anything for non-target tables, either */
+ if (!tblinfo[i].dobj.dump)
+ continue;
+
+ /* Find the parent TableInfo and save */
+ findPartitionParentByOid(&tblinfo[i], partinfo, numPartitions);
+
+ /* Mark the parent as interesting for getTableAttrs */
+ if (tblinfo[i].partitionOf)
+ {
+ tblinfo[i].partitionOf->interesting = true;
+ addObjectDependency(&tblinfo[i].dobj,
+ tblinfo[i].partitionOf->dobj.dumpId);
+ }
+ }
+}
+
/* flagInhAttrs -
* for each dumpable table in tblinfo, flag its inherited attributes
*
@@ -920,6 +972,40 @@ findParentsByOid(TableInfo *self,
}
/*
+ * findPartitionParentByOid
+ * find a partition's parent in tblinfo[]
+ */
+static void
+findPartitionParentByOid(TableInfo *self, PartInfo *partinfo,
+ int numPartitions)
+{
+ Oid oid = self->dobj.catId.oid;
+ int i;
+
+ for (i = 0; i < numPartitions; i++)
+ {
+ if (partinfo[i].partrelid == oid)
+ {
+ TableInfo *parent;
+
+ parent = findTableByOid(partinfo[i].partparent);
+ if (parent == NULL)
+ {
+ write_msg(NULL, "failed sanity check, parent OID %u of table \"%s\" (OID %u) not found\n",
+ partinfo[i].partparent,
+ self->dobj.name,
+ oid);
+ exit_nicely(1);
+ }
+ self->partitionOf = parent;
+
+ /* While we're at it, also save the partdef */
+ self->partitiondef = partinfo[i].partdef;
+ }
+ }
+}
+
+/*
* parseOidArray
* parse a string of numbers delimited by spaces into a character array
*
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index e11af8b..6404efb 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -6108,6 +6108,63 @@ getInherits(Archive *fout, int *numInherits)
}
/*
+ * getPartitions
+ * read all the partition inheritance and partition bound information
+ * from the system catalogs return them in the PartInfo* structure
+ *
+ * numPartitions is set to the number of pairs read in
+ */
+PartInfo *
+getPartitions(Archive *fout, int *numPartitions)
+{
+ PGresult *res;
+ int ntups;
+ int i;
+ PQExpBuffer query = createPQExpBuffer();
+ PartInfo *partinfo;
+
+ int i_partrelid;
+ int i_partparent;
+ int i_partbound;
+
+ /* Make sure we are in proper schema */
+ selectSourceSchema(fout, "pg_catalog");
+
+ /* find all the inheritance information */
+
+ appendPQExpBufferStr(query,
+ "SELECT partrelid, inhparent AS partparent, "
+ " pg_get_expr(partbound, partrelid) AS partbound "
+ "FROM pg_partition, pg_inherits "
+ "WHERE partrelid = inhrelid");
+
+ res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
+
+ ntups = PQntuples(res);
+
+ *numPartitions = ntups;
+
+ partinfo = (PartInfo *) pg_malloc(ntups * sizeof(PartInfo));
+
+ i_partrelid = PQfnumber(res, "partrelid");
+ i_partparent = PQfnumber(res, "partparent");
+ i_partbound = PQfnumber(res, "partbound");
+
+ for (i = 0; i < ntups; i++)
+ {
+ partinfo[i].partrelid = atooid(PQgetvalue(res, i, i_partrelid));
+ partinfo[i].partparent = atooid(PQgetvalue(res, i, i_partparent));
+ partinfo[i].partdef = pg_strdup(PQgetvalue(res, i, i_partbound));
+ }
+
+ PQclear(res);
+
+ destroyPQExpBuffer(query);
+
+ return partinfo;
+}
+
+/*
* getIndexes
* get information about every index on a dumpable table
*
@@ -15303,6 +15360,17 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
if (tbinfo->reloftype && !dopt->binary_upgrade)
appendPQExpBuffer(q, " OF %s", tbinfo->reloftype);
+ if (tbinfo->partitionOf && !dopt->binary_upgrade)
+ {
+ TableInfo *parentRel = tbinfo->partitionOf;
+
+ appendPQExpBuffer(q, " PARTITION OF ");
+ if (parentRel->dobj.namespace != tbinfo->dobj.namespace)
+ appendPQExpBuffer(q, "%s.",
+ fmtId(parentRel->dobj.namespace->dobj.name));
+ appendPQExpBufferStr(q, fmtId(parentRel->dobj.name));
+ }
+
if (tbinfo->relkind != RELKIND_MATVIEW)
{
/* Dump the attributes */
@@ -15331,8 +15399,11 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
(!tbinfo->inhNotNull[j] ||
dopt->binary_upgrade));
- /* Skip column if fully defined by reloftype */
- if (tbinfo->reloftype &&
+ /*
+ * Skip column if fully defined by reloftype or the
+ * partition parent.
+ */
+ if ((tbinfo->reloftype || tbinfo->partitionOf) &&
!has_default && !has_notnull && !dopt->binary_upgrade)
continue;
@@ -15361,7 +15432,8 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
}
/* Attribute type */
- if (tbinfo->reloftype && !dopt->binary_upgrade)
+ if ((tbinfo->reloftype || tbinfo->partitionOf) &&
+ !dopt->binary_upgrade)
{
appendPQExpBufferStr(q, " WITH OPTIONS");
}
@@ -15426,15 +15498,22 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
if (actual_atts)
appendPQExpBufferStr(q, "\n)");
- else if (!(tbinfo->reloftype && !dopt->binary_upgrade))
+ else if (!((tbinfo->reloftype || tbinfo->partitionOf) &&
+ !dopt->binary_upgrade))
{
/*
* We must have a parenthesized attribute list, even though
- * empty, when not using the OF TYPE syntax.
+ * empty, when not using the OF TYPE or PARTITION OF syntax.
*/
appendPQExpBufferStr(q, " (\n)");
}
+ if (tbinfo->partitiondef && !dopt->binary_upgrade)
+ {
+ appendPQExpBufferStr(q, "\n");
+ appendPQExpBufferStr(q, tbinfo->partitiondef);
+ }
+
if (numParents > 0 && !dopt->binary_upgrade)
{
appendPQExpBufferStr(q, "\nINHERITS (");
@@ -15604,6 +15683,15 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
tbinfo->reloftype);
}
+ if (tbinfo->partitionOf)
+ {
+ appendPQExpBufferStr(q, "\n-- For binary upgrade, set up partitions this way.\n");
+ appendPQExpBuffer(q, "ALTER TABLE ONLY %s ATTACH PARTITION %s %s;\n",
+ fmtId(tbinfo->partitionOf->dobj.name),
+ tbinfo->dobj.name,
+ tbinfo->partitiondef);
+ }
+
appendPQExpBufferStr(q, "\n-- For binary upgrade, set heap's relfrozenxid and relminmxid\n");
appendPQExpBuffer(q, "UPDATE pg_catalog.pg_class\n"
"SET relfrozenxid = '%u', relminmxid = '%u'\n"
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 0292859..760067a 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -320,6 +320,8 @@ typedef struct _tableInfo
struct _tableDataInfo *dataObj; /* TableDataInfo, if dumping its data */
int numTriggers; /* number of triggers for table */
struct _triggerInfo *triggers; /* array of TriggerInfo structs */
+ struct _tableInfo *partitionOf; /* TableInfo for the partition parent */
+ char *partitiondef; /* partition key definition */
} TableInfo;
typedef struct _attrDefInfo
@@ -460,6 +462,15 @@ typedef struct _inhInfo
Oid inhparent; /* OID of its parent */
} InhInfo;
+/* PartInfo isn't a DumpableObject, just temporary state */
+typedef struct _partInfo
+{
+ Oid partrelid; /* OID of a partition */
+ Oid partparent; /* OID of its parent */
+ char *partdef; /* partition bound definition */
+} PartInfo;
+
+
typedef struct _prsInfo
{
DumpableObject dobj;
@@ -626,6 +637,7 @@ extern ConvInfo *getConversions(Archive *fout, int *numConversions);
extern TableInfo *getTables(Archive *fout, int *numTables);
extern void getOwnedSeqs(Archive *fout, TableInfo tblinfo[], int numTables);
extern InhInfo *getInherits(Archive *fout, int *numInherits);
+extern PartInfo *getPartitions(Archive *fout, int *numPartitions);
extern void getIndexes(Archive *fout, TableInfo tblinfo[], int numTables);
extern void getConstraints(Archive *fout, TableInfo tblinfo[], int numTables);
extern RuleInfo *getRules(Archive *fout, int *numRules);
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 71d6cdc..28972c2 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -1758,6 +1758,34 @@ describeOneTableDetails(const char *schemaname,
}
/* Make footers */
+ if (pset.sversion >= 90600)
+ {
+ /* Get the partition information */
+ PGresult *result;
+ char *parent_name;
+ char *partdef;
+
+ printfPQExpBuffer(&buf,
+ "SELECT inhparent::pg_catalog.regclass, pg_get_expr(partbound, partrelid)"
+ " FROM pg_catalog.pg_partition p"
+ " JOIN pg_catalog.pg_inherits i"
+ " ON p.partrelid = i.inhrelid"
+ " WHERE p.partrelid = '%s';", oid);
+ result = PSQLexec(buf.data);
+ if (!result)
+ goto error_return;
+
+ if (PQntuples(result) > 0)
+ {
+ parent_name = PQgetvalue(result, 0, 0);
+ partdef = PQgetvalue(result, 0, 1);
+ printfPQExpBuffer(&tmpbuf, _("Partition Of: %s %s"), parent_name,
+ partdef);
+ printTableAddFooter(&cont, tmpbuf.data);
+ PQclear(result);
+ }
+ }
+
if (pset.sversion >= 90600 && tableinfo.relkind == 'P')
{
/* Get the partition key information */
@@ -2519,8 +2547,12 @@ describeOneTableDetails(const char *schemaname,
PQclear(result);
}
- /* print inherited tables */
- printfPQExpBuffer(&buf, "SELECT c.oid::pg_catalog.regclass FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i WHERE c.oid=i.inhparent AND i.inhrelid = '%s' ORDER BY inhseqno;", oid);
+ /* print inherited tables (exclude, if parent is a partitioned table) */
+ printfPQExpBuffer(&buf,
+ "SELECT c.oid::pg_catalog.regclass"
+ " FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i"
+ " WHERE c.oid=i.inhparent AND i.inhrelid = '%s'"
+ " AND c.relkind != 'P' ORDER BY inhseqno;", oid);
result = PSQLexec(buf.data);
if (!result)
@@ -2549,9 +2581,15 @@ describeOneTableDetails(const char *schemaname,
PQclear(result);
}
- /* print child tables */
+ /* print child tables (exclude, if parent is a partitioned table) */
if (pset.sversion >= 80300)
- printfPQExpBuffer(&buf, "SELECT c.oid::pg_catalog.regclass FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i WHERE c.oid=i.inhrelid AND i.inhparent = '%s' ORDER BY c.oid::pg_catalog.regclass::pg_catalog.text;", oid);
+ printfPQExpBuffer(&buf,
+ "SELECT c.oid::pg_catalog.regclass"
+ " FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i"
+ " WHERE c.oid=i.inhrelid AND"
+ " i.inhparent = '%s' AND"
+ " EXISTS (SELECT 1 FROM pg_class c WHERE c.oid = '%s' AND c.relkind != 'P')"
+ " ORDER BY c.oid::pg_catalog.regclass::pg_catalog.text;", oid, oid);
else
printfPQExpBuffer(&buf, "SELECT c.oid::pg_catalog.regclass FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i WHERE c.oid=i.inhrelid AND i.inhparent = '%s' ORDER BY c.relname;", oid);
diff --git a/src/include/catalog/indexing.h b/src/include/catalog/indexing.h
index cadb741..73786c0 100644
--- a/src/include/catalog/indexing.h
+++ b/src/include/catalog/indexing.h
@@ -322,6 +322,9 @@ DECLARE_UNIQUE_INDEX(pg_replication_origin_roname_index, 6002, on pg_replication
DECLARE_UNIQUE_INDEX(pg_partitioned_partedrelid_index, 3351, on pg_partitioned using btree(partedrelid oid_ops));
#define PartitionedRelidIndexId 3351
+DECLARE_UNIQUE_INDEX(pg_partition_partrelid_index, 3354, on pg_partition using btree(partrelid oid_ops));
+#define PartitionRelidIndexId 3354
+
/* last step of initialization script: build the indexes declared above */
BUILD_INDICES
diff --git a/src/include/catalog/partition.h b/src/include/catalog/partition.h
index 31dbe17..7d4bf2f 100644
--- a/src/include/catalog/partition.h
+++ b/src/include/catalog/partition.h
@@ -42,6 +42,76 @@ typedef struct PartitionKeyData
typedef struct PartitionKeyData *PartitionKey;
+/* Internal representation of a list partition bound */
+typedef struct PartitionListBound
+{
+ bool contains_null;
+ int nvalues; /* number of values in the following array */
+ Datum *values; /* values contained in the list */
+} PartitionListBound;
+
+/* Internal representation of a range partition bound */
+typedef struct
+{
+ Datum *val; /* composite bound value, if any */
+ bool infinite; /* bound is +/- infinity */
+ bool inclusive; /* bound is inclusive (vs exclusive) */
+ bool lower; /* this is the lower (vs upper) bound */
+} PartitionRangeBound;
+
+/*
+ * Information about a single partition
+ */
+typedef struct PartitionInfo
+{
+ Oid oid; /* partition OID */
+ PartitionListBound *list; /* list partition bound */
+ PartitionRangeBound *rangelower; /* range partition lower bound */
+ PartitionRangeBound *rangeupper; /* range partition upper bound */
+} PartitionInfo;
+
+/*
+ * Information about partitions of a partitioned table.
+ */
+typedef struct PartitionDescData
+{
+ int nparts; /* number of partitions */
+ Oid *oids; /* OIDs of the partitions */
+ PartitionListBound **lists; /* list bounds */
+ PartitionRangeBound **rangelowers; /* range lower bounds */
+ PartitionRangeBound **rangeuppers; /* range upper bounds */
+} PartitionDescData;
+
+typedef struct PartitionDescData *PartitionDesc;
+
+/*
+ * Partition tree node
+ *
+ * pkinfo PartitionKey executor state
+ * pdesc info about immediate partitions (see
+ PartitionDescData)
+ * index index within 0..(parent_pdesc->nparts - 1)
+ * num_leaf_partitions number of leaf partitions in the partition
+ * tree rooted at this node
+ * offset index (0-based) of the first leaf partition
+ * in the partition tree rooted at this node
+ * downlink link to the leftmost child node
+ * next link to the right sibling node on a given level
+ */
+typedef struct PartitionDescNodeData
+{
+ PartitionDesc pdesc;
+ Oid relid;
+ int index;
+ int offset;
+ int num_leaf_partitions;
+
+ struct PartitionDescNodeData *downlink;
+ struct PartitionDescNodeData *next;
+} PartitionDescNodeData;
+
+typedef struct PartitionDescNodeData *PartitionDescNode;
+
extern void StorePartitionKey(Relation rel,
char strategy,
int16 partnatts,
@@ -53,4 +123,19 @@ extern void RelationBuildPartitionKey(Relation relation);
extern void FreePartitionKey(PartitionKey key);
extern bool relid_is_partitioned(Oid relid);
+extern void StorePartitionBound(Oid relid, Oid parentId, Node *bound);
+extern void RemovePartitionEntryByRelId(Oid relid);
+extern bool relid_is_partition(Oid relid);
+extern Oid get_partition_parent(Oid relid);
+extern void RelationDropPartitions(Oid relid);
+extern PartitionInfo **get_partition_info(Relation rel, int *nparts);
+extern void free_partition_info(PartitionInfo **p, int num);
+extern int32 partition_range_cmp_bounds(PartitionKey key,
+ PartitionRangeBound *b1,
+ PartitionRangeBound *b2);
+extern Node *RelationGetPartitionCheckExpr(Relation rel);
+extern Expr *get_check_expr_from_partbound(Relation rel, Relation parent,
+ Node *bound);
+extern List *get_leaf_partition_oids(PartitionDescNode pdnode);
+extern PartitionDescNode RelationGetPartitionDescNode(Relation rel);
#endif /* PARTITION_H */
diff --git a/src/include/catalog/pg_partition.h b/src/include/catalog/pg_partition.h
new file mode 100644
index 0000000..d31a6b7
--- /dev/null
+++ b/src/include/catalog/pg_partition.h
@@ -0,0 +1,54 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_partition.h
+ * definition of the system "partition" relation (pg_partition)
+ * along with the relation's initial contents.
+ *
+ *
+ * Portions Copyright (c) 1996-2015, PostgreSQL Global Development Group
+ *
+ * $PostgreSQL: pgsql/src/include/catalog/pg_partition.h $
+ *
+ * NOTES
+ * the genbki.sh script reads this file and generates .bki
+ * information from the DATA() statements.
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_PARTITION_H
+#define PG_PARTITION_H
+
+#include "catalog/genbki.h"
+
+/* ----------------
+ * pg_partitioned_rel definition. cpp turns this into
+ * typedef struct FormData_pg_partitioned_rel
+ * ----------------
+ */
+#define PartitionRelationId 3353
+
+CATALOG(pg_partition,3353) BKI_WITHOUT_OIDS
+{
+ Oid partrelid; /* partition relation OID */
+
+#ifdef CATALOG_VARLEN /* variable-length fields start here */
+ pg_node_tree partbound; /* node representation of partition bound */
+#endif
+} FormData_pg_partition;
+
+/* ----------------
+ * Form_pg_partition corresponds to a pointer to a tuple with
+ * the format of pg_partition relation.
+ * ----------------
+ */
+typedef FormData_pg_partition *Form_pg_partition;
+
+/* ----------------
+ * compiler constants for pg_partition
+ * ----------------
+ */
+#define Natts_pg_partition 2
+#define Anum_pg_partition_partrelid 1
+#define Anum_pg_partition_partbound 2
+
+#endif /* PG_PARTITION_H */
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index ee4e189..74288e2 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -319,6 +319,8 @@ typedef struct JunkFilter
* projectReturning for computing a RETURNING list
* onConflictSetProj for computing ON CONFLICT DO UPDATE SET
* onConflictSetWhere list of ON CONFLICT DO UPDATE exprs (qual)
+ * PartitionCheck partition check expression
+ * PartitionCheckExpr partition check expression state
* ----------------
*/
typedef struct ResultRelInfo
@@ -343,6 +345,8 @@ typedef struct ResultRelInfo
ProjectionInfo *ri_projectReturning;
ProjectionInfo *ri_onConflictSetProj;
List *ri_onConflictSetWhere;
+ Expr *ri_PartitionCheck;
+ List *ri_PartitionCheckExpr;
} ResultRelInfo;
/* ----------------
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 0c5d5d6..e46d68d 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -91,6 +91,10 @@ typedef struct RelationData
struct RowSecurityDesc *rd_rsdesc; /* row security policies, or NULL */
struct PartitionKeyData *rd_partkey; /* partition key, or NULL */
+ MemoryContext rd_pdcxt; /* private context for partdesc */
+ struct PartitionDescData *rd_partdesc; /* partitions, or NULL */
+ Node *rd_partcheck; /* partition CHECK constraint expression
+ * tree */
/* data managed by RelationGetIndexList: */
List *rd_indexlist; /* list of OIDs of indexes on relation */
@@ -507,6 +511,12 @@ typedef struct ViewOptions
*/
#define RelationGetPartitionKey(relation) ((relation)->rd_partkey)
+/*
+ * RelationGetPartitionDesc
+ * Returns partition descriptor for a relation.
+ */
+#define RelationGetPartitionDesc(relation) ((relation)->rd_partdesc)
+
/* routines in utils/cache/relcache.c */
extern void RelationIncrementReferenceCount(Relation rel);
extern void RelationDecrementReferenceCount(Relation rel);
diff --git a/src/include/utils/syscache.h b/src/include/utils/syscache.h
index e727842..4e493e0 100644
--- a/src/include/utils/syscache.h
+++ b/src/include/utils/syscache.h
@@ -72,6 +72,7 @@ enum SysCacheIdentifier
OPEROID,
OPFAMILYAMNAMENSP,
OPFAMILYOID,
+ PARTRELID,
PARTEDRELID,
PROCNAMEARGSNSP,
PROCOID,
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index b1e473d..57bde9d 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -118,6 +118,7 @@ pg_namespace|t
pg_opclass|t
pg_operator|t
pg_opfamily|t
+pg_partition|t
pg_partitioned|t
pg_pltemplate|t
pg_policy|t
--
1.7.1
[text/x-diff] 0006-Introduce-tuple-routing-for-partitioned-tables-v6.patch (29.3K, ../../19a3733b-ab58-b993-c858-ec8fc578ddfd@lab.ntt.co.jp/7-0006-Introduce-tuple-routing-for-partitioned-tables-v6.patch)
download | inline diff:
From 5b0136c7a9f7351711341327799569bdcd21b3b9 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Fri, 18 Mar 2016 19:48:49 +0900
Subject: [PATCH 6/7] Introduce tuple routing for partitioned tables.
A tuple inserted into a partitioned table using insert or copy is now
routed to the appropriate leaf partition, recursively.
State required for the same is built in advance by BeginCopy() and
ExecInitModifyTable(). It consists of N ResultRelInfos and pointers
to TupleConversionMap structs, where N is the number of leaf partitions
in the multi-level partition tree. One needs only to get a pointer to the
root PartitionDescNode node for the tree. A tuple conversion map is only
allocated if the tuple descriptor of a leaf partition differs from that
of the root table. Partition check expression is not initialized for
leaf partition result relations, as they are redundant in this case.
Whether or not to build one is now determined by new parameter
load_partition_check of InitResultRelInfo().
CopyFrom() or ExecInsert() passes the root node (set up initially, as
just mentioned) along with the tuple slot to get_partition_for_tuple()
which traverses the tree to determine the appropriate leaf partition for
the tuple. It returns the index (0..N) of the leaf partition. Caller
then switches the result relation (ResultRelInfo) appropriately and
performs the tuple conversion, if required, before following up with
the row triggers, constraints, actual heap and index insertion.
* Following requires further consideration:
Both CopyFrom() and ExecInsert() switch to the partition before invoking
BR triggers and back after AR trigger are processed. That ignores parent's
triggers completely. Note that those triggers are not required to be present
in each partition. OTOH, constraints are fine (both check and not null),
since parent's constraints are present in partitions.
---
src/backend/catalog/partition.c | 324 ++++++++++++++++++++++++++++++++
src/backend/commands/copy.c | 113 +++++++++++-
src/backend/commands/tablecmds.c | 1 +
src/backend/executor/execMain.c | 44 +++++-
src/backend/executor/nodeModifyTable.c | 96 ++++++++++
src/backend/parser/analyze.c | 8 +
src/include/catalog/partition.h | 24 +++
src/include/executor/executor.h | 6 +
src/include/nodes/execnodes.h | 10 +
9 files changed, 621 insertions(+), 5 deletions(-)
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index be4a79f..2b56a9f 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -96,6 +96,21 @@ static Node *generate_partition_check_expr(Relation rel);
static PartitionDescNode GetPartitionDescNodeRecurse(Relation rel, int offset);
static int get_leaf_partition_count(PartitionDescNode pdnode);
+static PartitionKeyExecInfo *BuildPartitionKeyExecInfo(Relation rel);
+static void FormPartitionKeyDatum(PartitionKeyExecInfo *pkinfo,
+ TupleTableSlot *slot,
+ EState *estate,
+ Datum *values,
+ bool *isnull);
+static int list_partition_for_tuple(PartitionKey key, PartitionDesc pdesc,
+ Datum value, bool isnull);
+static int range_partition_for_tuple(PartitionKey key, PartitionDesc pdesc,
+ Datum *values);
+static int range_partition_bsearch(PartitionKey key, PartitionDesc pdesc,
+ Datum *values);
+static bool rightof(PartitionKey key, Datum *values, PartitionRangeBound *bound);
+static bool leftof(PartitionKey key, Datum *values, PartitionRangeBound *bound);
+
/*
* StorePartitionKey
* Store the partition keys of rel into pg_partitioned catalog
@@ -1631,6 +1646,7 @@ GetPartitionDescNodeRecurse(Relation rel, int offset)
/* First build our own node */
root = (PartitionDescNode) palloc0(sizeof(PartitionDescNodeData));
+ root->pkinfo = BuildPartitionKeyExecInfo(rel);
root->pdesc = pdesc;
root->relid = RelationGetRelid(rel);
root->offset = offset;
@@ -1748,3 +1764,311 @@ get_leaf_partition_oids(PartitionDescNode pdnode)
return result;
}
+
+/* ----------------
+ * BuildPartitionKeyExecInfo
+ * Construct a list of PartitionKeyExecInfo records for an open
+ * relation
+ *
+ * PartitionKeyExecInfo stores the information about the partition key
+ * that's needed when inserting tuples into a partitioned table; especially,
+ * partition key expression state if there are any expression columns in
+ * the partition key. Normally we build a PartitionKeyExecInfo for a
+ * partitioned table just once per command, and then use it for (potentially)
+ * many tuples.
+ * ----------------
+ */
+static PartitionKeyExecInfo *
+BuildPartitionKeyExecInfo(Relation rel)
+{
+ PartitionKeyExecInfo *pkinfo;
+
+ pkinfo = (PartitionKeyExecInfo *) palloc0(sizeof(PartitionKeyExecInfo));
+ pkinfo->pi_Key = CopyPartitionKey(rel->rd_partkey);
+ pkinfo->pi_ExpressionState = NIL;
+
+ return pkinfo;
+}
+
+/*
+ * get_partition_for_tuple
+ * Recursively finds the "leaf" partition for tuple (slot)
+ *
+ * Returns -1 if no partition is found and sets *failed_at to the OID of
+ * the partitioned table whose partition was not found.
+ */
+int
+get_partition_for_tuple(PartitionDescNode pdnode,
+ TupleTableSlot *slot,
+ EState *estate,
+ Oid *failed_at)
+{
+ PartitionKeyExecInfo *pkinfo = pdnode->pkinfo;
+ PartitionDescNode node;
+ Datum values[PARTITION_MAX_KEYS];
+ bool isnull[PARTITION_MAX_KEYS];
+ int i;
+ int index;
+
+ /* Guard against stack overflow due to overly deep partition tree */
+ check_stack_depth();
+
+ if (pdnode->pdesc->nparts == 0)
+ {
+ *failed_at = pdnode->relid;
+ return -1;
+ }
+
+ /* Extract partition key from tuple */
+ Assert(GetPerTupleExprContext(estate)->ecxt_scantuple == slot);
+ FormPartitionKeyDatum(pkinfo, slot, estate, values, isnull);
+
+ /* Disallow nulls, if range partition key */
+ for (i = 0; i < pkinfo->pi_Key->partnatts; i++)
+ if (isnull[i] && pkinfo->pi_Key->strategy == PARTITION_STRAT_RANGE)
+ ereport(ERROR,
+ (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+ errmsg("range partition key contains null")));
+
+ switch (pkinfo->pi_Key->strategy)
+ {
+ case PARTITION_STRAT_LIST:
+ index = list_partition_for_tuple(pkinfo->pi_Key, pdnode->pdesc,
+ values[0], isnull[0]);
+ break;
+
+ case PARTITION_STRAT_RANGE:
+ index = range_partition_for_tuple(pkinfo->pi_Key, pdnode->pdesc,
+ values);
+ break;
+ }
+
+ /* Don't recurse, if no partition exists for the key at this level... */
+ if (index < 0)
+ {
+ *failed_at = pdnode->relid;
+ return index;
+ }
+
+ /* ... or, if the index'th partition is a leaf partition. */
+ if (!relid_is_partitioned(pdnode->pdesc->oids[index]))
+ {
+ PartitionDescNode prev;
+
+ /* Patiently determine own leaf partition index */
+ prev = node = pdnode->downlink;
+ if (node && node->index < index)
+ {
+ while (node)
+ {
+ if (node->index > index)
+ {
+ node = prev;
+ break;
+ }
+
+ prev = node;
+ node = node->next;
+ }
+
+ if (!node)
+ node = prev;
+
+ /*
+ * Account for siblings on left that are partition trees and those
+ * that are leaf partitions themsleves.
+ */
+ return node->offset + node->num_leaf_partitions +
+ (index - node->index - 1);
+ }
+ else
+ /* Only leaf partitions in pdnode->offset..index */
+ return pdnode->offset + index;
+
+ }
+
+ /* Recurse, by locating the index'th partition's node */
+ node = pdnode->downlink;
+ while (node->next != NULL && node->index != index)
+ node = node->next;
+ Assert (node != NULL);
+
+ return get_partition_for_tuple(node, slot, estate, failed_at);
+}
+
+/*
+ * FormPartitionKeyDatum
+ * Construct values[] and isnull[] arrays for partition key columns
+ */
+static void
+FormPartitionKeyDatum(PartitionKeyExecInfo *pkinfo,
+ TupleTableSlot *slot,
+ EState *estate,
+ Datum *values,
+ bool *isnull)
+{
+ ListCell *partexpr_item;
+ int i;
+
+ if (pkinfo->pi_Key->partexprs != NIL && pkinfo->pi_ExpressionState == NIL)
+ {
+ /* First time through, set up expression evaluation state */
+ pkinfo->pi_ExpressionState = (List *)
+ ExecPrepareExpr((Expr *) pkinfo->pi_Key->partexprs,
+ estate);
+ /* Check caller has set up context correctly */
+ Assert(GetPerTupleExprContext(estate)->ecxt_scantuple == slot);
+ }
+
+ partexpr_item = list_head(pkinfo->pi_ExpressionState);
+ for (i = 0; i < pkinfo->pi_Key->partnatts; i++)
+ {
+ AttrNumber keycol = pkinfo->pi_Key->partattrs[i];
+ Datum pkDatum;
+ bool isNull;
+
+ if (keycol != 0)
+ {
+ /* Plain column; get the value directly from the heap tuple */
+ pkDatum = slot_getattr(slot, keycol, &isNull);
+ }
+ else
+ {
+ /* Expression; need to evaluate it */
+ if (partexpr_item == NULL)
+ elog(ERROR, "wrong number of partition key expressions");
+ pkDatum = ExecEvalExprSwitchContext((ExprState *) lfirst(partexpr_item),
+ GetPerTupleExprContext(estate),
+ &isNull,
+ NULL);
+ partexpr_item = lnext(partexpr_item);
+ }
+ values[i] = pkDatum;
+ isnull[i] = isNull;
+ }
+
+ if (partexpr_item != NULL)
+ elog(ERROR, "wrong number of partition key expressions");
+}
+
+/*
+ * list_partition_for_tuple
+ * Find the list partition for a tuple
+ *
+ * Returns -1 if none found.
+ */
+static int
+list_partition_for_tuple(PartitionKey key, PartitionDesc pdesc,
+ Datum value, bool isnull)
+{
+ int i;
+
+ Assert(pdesc->nparts > 0);
+
+ for (i = 0; i < pdesc->nparts; i++)
+ {
+ int j;
+
+ if (isnull)
+ {
+ if (pdesc->lists[i]->contains_null)
+ return i;
+
+ continue;
+ }
+
+ for (j = 0; j < pdesc->lists[i]->nvalues; j++)
+ {
+ int32 cmpval;
+
+ cmpval = DatumGetInt32(FunctionCall2Coll(&key->partsupfunc[0],
+ key->tcinfo->typcoll[0],
+ pdesc->lists[i]->values[j],
+ value));
+ if (!cmpval)
+ return i;
+ }
+ }
+
+ return -1;
+}
+
+/*
+ * range_partition_for_tuple
+ * Search the range partition for a range key ('values')
+ *
+ * Returns -1 if none found.
+ */
+static int
+range_partition_for_tuple(PartitionKey key, PartitionDesc pdesc, Datum *values)
+{
+ Assert(pdesc->nparts > 0);
+
+ return range_partition_bsearch(key, pdesc, values);
+}
+
+/*
+ * range_partition_bsearch
+ * Workhorse of range_partition_for_tuple
+ */
+static int
+range_partition_bsearch(PartitionKey key, PartitionDesc pdesc,
+ Datum *values)
+{
+ int low, high;
+
+ /* Good ol' bsearch */
+ low = 0;
+ high = pdesc->nparts - 1;
+ while (low <= high)
+ {
+ int idx = (low + high) / 2;
+
+ if (pdesc->rangeuppers[idx]->infinite)
+ {
+ if (rightof(key, values, pdesc->rangelowers[idx]))
+ return idx;
+
+ break;
+ }
+ else if (leftof(key, values, pdesc->rangeuppers[idx]))
+ {
+ if (pdesc->rangelowers[idx]->infinite)
+ return idx;
+
+ if (rightof(key, values, pdesc->rangelowers[idx]))
+ return idx;
+
+ high = idx - 1;
+ continue;
+ }
+
+ low = idx + 1;
+ }
+
+ return -1;
+}
+
+/* Does range key lie to the right of partition bound */
+static bool
+rightof(PartitionKey key, Datum *values, PartitionRangeBound *bound)
+{
+ int32 cmpval = compare_range_keys(key, values, bound->val);
+
+ if (!cmpval)
+ return bound->lower ? bound->inclusive : !bound->inclusive;
+
+ return cmpval > 0;
+}
+
+/* Does range key lie to the left of partition bound */
+static bool
+leftof(PartitionKey key, Datum *values, PartitionRangeBound *bound)
+{
+ int32 cmpval = compare_range_keys(key, values, bound->val);
+
+ if (!cmpval)
+ return !bound->lower ? bound->inclusive : !bound->inclusive;
+
+ return cmpval < 0;
+}
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 5437926..7fc661e 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -161,6 +161,10 @@ typedef struct CopyStateData
ExprState **defexprs; /* array of default att expressions */
bool volatile_defexprs; /* is any of defexprs volatile? */
List *range_table;
+ PartitionDescNode pdnode; /* partition descriptor node tree */
+ ResultRelInfo *partitions;
+ TupleConversionMap **partition_tupconv_maps;
+ int num_partitions;
/*
* These variables are used to reduce overhead in textual COPY FROM.
@@ -1364,6 +1368,52 @@ BeginCopy(bool is_from,
(errcode(ERRCODE_UNDEFINED_COLUMN),
errmsg("table \"%s\" does not have OIDs",
RelationGetRelationName(cstate->rel))));
+
+ /* Initialize state for CopyFrom tuple routing */
+ if (is_from && rel->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+ {
+ List *leaf_part_oids;
+ ListCell *cell;
+ int i;
+ int num_leaf_parts;
+ ResultRelInfo *leaf_rel_rri;
+
+ cstate->pdnode = RelationGetPartitionDescNode(rel);
+ leaf_part_oids = get_leaf_partition_oids(cstate->pdnode);
+ num_leaf_parts = list_length(leaf_part_oids);
+
+ cstate->num_partitions = num_leaf_parts;
+ cstate->partitions = (ResultRelInfo *)
+ palloc0(num_leaf_parts * sizeof(ResultRelInfo));
+ cstate->partition_tupconv_maps = (TupleConversionMap **)
+ palloc0(num_leaf_parts * sizeof(TupleConversionMap *));
+
+ leaf_rel_rri = cstate->partitions;
+ i = 0;
+ foreach(cell, leaf_part_oids)
+ {
+ Relation leaf_rel;
+
+ leaf_rel = heap_open(lfirst_oid(cell), RowExclusiveLock);
+ InitResultRelInfo(leaf_rel_rri,
+ leaf_rel,
+ 1, /* dummy */
+ false, /* no need for partition check */
+ 0);
+
+ /* Open partition indices */
+ ExecOpenIndices(leaf_rel_rri, false);
+
+ if (!equalTupleDescs(tupDesc, RelationGetDescr(leaf_rel)))
+ cstate->partition_tupconv_maps[i] =
+ convert_tuples_by_name(tupDesc,
+ RelationGetDescr(leaf_rel),
+ gettext_noop("could not convert row type"));
+
+ leaf_rel_rri++;
+ i++;
+ }
+ }
}
else
{
@@ -1659,6 +1709,8 @@ ClosePipeToProgram(CopyState cstate)
static void
EndCopy(CopyState cstate)
{
+ int i;
+
if (cstate->is_program)
{
ClosePipeToProgram(cstate);
@@ -1672,6 +1724,18 @@ EndCopy(CopyState cstate)
cstate->filename)));
}
+ /* Close all partitions and indices thereof */
+ for (i = 0; i < cstate->num_partitions; i++)
+ {
+ ResultRelInfo *resultRelInfo = cstate->partitions + i;
+
+ ExecCloseIndices(resultRelInfo);
+ heap_close(resultRelInfo->ri_RelationDesc, NoLock);
+
+ if (cstate->partition_tupconv_maps[i])
+ pfree(cstate->partition_tupconv_maps[i]);
+ }
+
MemoryContextDelete(cstate->copycontext);
pfree(cstate);
}
@@ -2216,6 +2280,7 @@ CopyFrom(CopyState cstate)
Datum *values;
bool *nulls;
ResultRelInfo *resultRelInfo;
+ ResultRelInfo *saved_resultRelInfo = NULL;
EState *estate = CreateExecutorState(); /* for ExecConstraints() */
ExprContext *econtext;
TupleTableSlot *myslot;
@@ -2236,7 +2301,8 @@ CopyFrom(CopyState cstate)
Assert(cstate->rel);
- if (cstate->rel->rd_rel->relkind != RELKIND_RELATION)
+ if (cstate->rel->rd_rel->relkind != RELKIND_RELATION &&
+ cstate->rel->rd_rel->relkind != RELKIND_PARTITIONED_REL)
{
if (cstate->rel->rd_rel->relkind == RELKIND_VIEW)
ereport(ERROR,
@@ -2344,6 +2410,7 @@ CopyFrom(CopyState cstate)
InitResultRelInfo(resultRelInfo,
cstate->rel,
1, /* dummy rangetable index */
+ true, /* do load partition check expression */
0);
ExecOpenIndices(resultRelInfo, false);
@@ -2371,6 +2438,7 @@ CopyFrom(CopyState cstate)
if ((resultRelInfo->ri_TrigDesc != NULL &&
(resultRelInfo->ri_TrigDesc->trig_insert_before_row ||
resultRelInfo->ri_TrigDesc->trig_insert_instead_row)) ||
+ cstate->pdnode != NULL ||
cstate->volatile_defexprs)
{
useHeapMultiInsert = false;
@@ -2395,7 +2463,11 @@ CopyFrom(CopyState cstate)
values = (Datum *) palloc(tupDesc->natts * sizeof(Datum));
nulls = (bool *) palloc(tupDesc->natts * sizeof(bool));
- bistate = GetBulkInsertState();
+ if (useHeapMultiInsert)
+ bistate = GetBulkInsertState();
+ else
+ bistate = NULL;
+
econtext = GetPerTupleExprContext(estate);
/* Set up callback to identify error line number */
@@ -2447,6 +2519,31 @@ CopyFrom(CopyState cstate)
slot = myslot;
ExecStoreTuple(tuple, slot, InvalidBuffer, false);
+ /* Determine the partition */
+ saved_resultRelInfo = resultRelInfo;
+ if (cstate->pdnode)
+ {
+ int i_leaf_partition;
+ TupleConversionMap *map;
+
+ econtext->ecxt_scantuple = slot;
+ i_leaf_partition = ExecFindPartition(resultRelInfo,
+ cstate->pdnode,
+ slot,
+ estate);
+ Assert(i_leaf_partition >= 0 &&
+ i_leaf_partition < cstate->num_partitions);
+
+ resultRelInfo = cstate->partitions + i_leaf_partition;
+ estate->es_result_relation_info = resultRelInfo;
+
+ map = cstate->partition_tupconv_maps[i_leaf_partition];
+ if (map)
+ tuple = do_convert_tuple(tuple, map);
+
+ tuple->t_tableOid = RelationGetRelid(resultRelInfo->ri_RelationDesc);
+ }
+
skip_tuple = false;
/* BEFORE ROW INSERT Triggers */
@@ -2497,7 +2594,8 @@ CopyFrom(CopyState cstate)
List *recheckIndexes = NIL;
/* OK, store the tuple and create index entries for it */
- heap_insert(cstate->rel, tuple, mycid, hi_options, bistate);
+ heap_insert(resultRelInfo->ri_RelationDesc,
+ tuple, mycid, hi_options, bistate);
if (resultRelInfo->ri_NumIndices > 0)
recheckIndexes = ExecInsertIndexTuples(slot, &(tuple->t_self),
@@ -2517,6 +2615,12 @@ CopyFrom(CopyState cstate)
* tuples inserted by an INSERT command.
*/
processed++;
+
+ if (saved_resultRelInfo)
+ {
+ resultRelInfo = saved_resultRelInfo;
+ estate->es_result_relation_info = resultRelInfo;
+ }
}
}
@@ -2530,7 +2634,8 @@ CopyFrom(CopyState cstate)
/* Done, clean up */
error_context_stack = errcallback.previous;
- FreeBulkInsertState(bistate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
MemoryContextSwitchTo(oldcontext);
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index dda14c5..2ee27bd 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -1237,6 +1237,7 @@ ExecuteTruncate(TruncateStmt *stmt)
InitResultRelInfo(resultRelInfo,
rel,
0, /* dummy rangetable index */
+ false,
0);
resultRelInfo++;
}
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 230324e..2338a2d 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -826,6 +826,7 @@ InitPlan(QueryDesc *queryDesc, int eflags)
InitResultRelInfo(resultRelInfo,
resultRelation,
resultRelationIndex,
+ true,
estate->es_instrument);
resultRelInfo++;
}
@@ -1215,6 +1216,7 @@ void
InitResultRelInfo(ResultRelInfo *resultRelInfo,
Relation resultRelationDesc,
Index resultRelationIndex,
+ bool load_partition_check,
int instrument_options)
{
MemSet(resultRelInfo, 0, sizeof(ResultRelInfo));
@@ -1252,7 +1254,8 @@ InitResultRelInfo(ResultRelInfo *resultRelInfo,
resultRelInfo->ri_ConstraintExprs = NULL;
resultRelInfo->ri_junkFilter = NULL;
resultRelInfo->ri_projectReturning = NULL;
- resultRelInfo->ri_PartitionCheck = (Expr *)
+ if (load_partition_check)
+ resultRelInfo->ri_PartitionCheck = (Expr *)
RelationGetPartitionCheckExpr(resultRelationDesc);
}
@@ -1316,6 +1319,7 @@ ExecGetTriggerResultRel(EState *estate, Oid relid)
InitResultRelInfo(rInfo,
rel,
0, /* dummy rangetable index */
+ true,
estate->es_instrument);
estate->es_trig_target_relations =
lappend(estate->es_trig_target_relations, rInfo);
@@ -2996,3 +3000,41 @@ EvalPlanQualEnd(EPQState *epqstate)
epqstate->planstate = NULL;
epqstate->origslot = NULL;
}
+
+int
+ExecFindPartition(ResultRelInfo *resultRelInfo, PartitionDescNode pdnode,
+ TupleTableSlot *slot, EState *estate)
+{
+ int i_leaf_partition;
+ Oid failed_at;
+
+ i_leaf_partition = get_partition_for_tuple(pdnode, slot, estate,
+ &failed_at);
+
+ if (i_leaf_partition < 0)
+ {
+ Relation rel = resultRelInfo->ri_RelationDesc;
+ char *val_desc;
+ Bitmapset *insertedCols,
+ *updatedCols,
+ *modifiedCols;
+ TupleDesc tupDesc = RelationGetDescr(rel);
+
+ insertedCols = GetInsertedColumns(resultRelInfo, estate);
+ updatedCols = GetUpdatedColumns(resultRelInfo, estate);
+ modifiedCols = bms_union(insertedCols, updatedCols);
+ val_desc = ExecBuildSlotValueDescription(RelationGetRelid(rel),
+ slot,
+ tupDesc,
+ modifiedCols,
+ 64);
+ Assert(OidIsValid(failed_at));
+ ereport(ERROR,
+ (errcode(ERRCODE_CHECK_VIOLATION),
+ errmsg("no partition of relation \"%s\" found for row",
+ get_rel_name(failed_at)),
+ val_desc ? errdetail("Failing row contains %s.", val_desc) : 0));
+ }
+
+ return i_leaf_partition;
+}
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 6ecb6be..581112d 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -243,6 +243,7 @@ ExecInsert(ModifyTableState *mtstate,
{
HeapTuple tuple;
ResultRelInfo *resultRelInfo;
+ ResultRelInfo *saved_resultRelInfo = NULL;
Relation resultRelationDesc;
Oid newId;
List *recheckIndexes = NIL;
@@ -257,6 +258,31 @@ ExecInsert(ModifyTableState *mtstate,
* get information on the (current) result relation
*/
resultRelInfo = estate->es_result_relation_info;
+
+ saved_resultRelInfo = resultRelInfo;
+
+ if (mtstate->mt_partition_node)
+ {
+ int i_leaf_partition;
+ ExprContext *econtext = GetPerTupleExprContext(estate);
+ TupleConversionMap *map;
+
+ econtext->ecxt_scantuple = slot;
+ i_leaf_partition = ExecFindPartition(resultRelInfo,
+ mtstate->mt_partition_node,
+ slot,
+ estate);
+ Assert(i_leaf_partition >= 0 &&
+ i_leaf_partition < mtstate->mt_num_partitions);
+
+ resultRelInfo = mtstate->mt_partitions + i_leaf_partition;
+ estate->es_result_relation_info = resultRelInfo;
+
+ map = mtstate->mt_partition_tupconv_maps[i_leaf_partition];
+ if (map)
+ tuple = do_convert_tuple(tuple, map);
+ }
+
resultRelationDesc = resultRelInfo->ri_RelationDesc;
/*
@@ -496,6 +522,12 @@ ExecInsert(ModifyTableState *mtstate,
list_free(recheckIndexes);
+ if (saved_resultRelInfo)
+ {
+ resultRelInfo = saved_resultRelInfo;
+ estate->es_result_relation_info = resultRelInfo;
+ }
+
/*
* Check any WITH CHECK OPTION constraints from parent views. We are
* required to do this after testing all constraints and uniqueness
@@ -1550,6 +1582,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
Plan *subplan;
ListCell *l;
int i;
+ Relation rel;
/* check for unsupported flags */
Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));
@@ -1640,6 +1673,57 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
estate->es_result_relation_info = saved_resultRelInfo;
+ /* Build state for INSERT tuple routing */
+ rel = mtstate->resultRelInfo->ri_RelationDesc;
+ if (operation == CMD_INSERT && rel->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+ {
+ int i,
+ num_leaf_parts;
+ List *leaf_part_oids;
+ ListCell *cell;
+ ResultRelInfo *leaf_rel_rri;
+
+ mtstate->mt_partition_node = RelationGetPartitionDescNode(rel);
+ leaf_part_oids = get_leaf_partition_oids(mtstate->mt_partition_node);
+ num_leaf_parts = list_length(leaf_part_oids);
+
+ mtstate->mt_num_partitions = num_leaf_parts;
+ mtstate->mt_partitions = (ResultRelInfo *)
+ palloc0(num_leaf_parts * sizeof(ResultRelInfo));
+ mtstate->mt_partition_tupconv_maps = (TupleConversionMap **)
+ palloc0(num_leaf_parts * sizeof(TupleConversionMap *));
+
+ leaf_rel_rri = mtstate->mt_partitions;
+ i = 0;
+ foreach(cell, leaf_part_oids)
+ {
+ Relation leaf_rel;
+
+ leaf_rel = heap_open(lfirst_oid(cell), RowExclusiveLock);
+ InitResultRelInfo(leaf_rel_rri,
+ leaf_rel,
+ 1, /* dummy */
+ false, /* no need for partition checks */
+ eflags);
+
+ /* Open partition indices (note: ON CONFLICT unsupported)*/
+ if (leaf_rel_rri->ri_RelationDesc->rd_rel->relhasindex &&
+ operation != CMD_DELETE &&
+ leaf_rel_rri->ri_IndexRelationDescs == NULL)
+ ExecOpenIndices(leaf_rel_rri, false);
+
+ if (!equalTupleDescs(RelationGetDescr(rel),
+ RelationGetDescr(leaf_rel)))
+ mtstate->mt_partition_tupconv_maps[i] =
+ convert_tuples_by_name(RelationGetDescr(rel),
+ RelationGetDescr(leaf_rel),
+ gettext_noop("could not convert row type"));
+
+ leaf_rel_rri++;
+ i++;
+ }
+ }
+
/*
* Initialize any WITH CHECK OPTION constraints if needed.
*/
@@ -1957,6 +2041,18 @@ ExecEndModifyTable(ModifyTableState *node)
resultRelInfo);
}
+ /* Close all partitions and indices thereof */
+ for (i = 0; i < node->mt_num_partitions; i++)
+ {
+ ResultRelInfo *resultRelInfo = node->mt_partitions + i;
+
+ ExecCloseIndices(resultRelInfo);
+ heap_close(resultRelInfo->ri_RelationDesc, NoLock);
+
+ if (node->mt_partition_tupconv_maps[i])
+ pfree(node->mt_partition_tupconv_maps[i]);
+ }
+
/*
* Free the exprcontext
*/
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index b4fca37..6996666 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -777,8 +777,16 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
/* Process ON CONFLICT, if any. */
if (stmt->onConflictClause)
+ {
+ /* Bail out if target relation is partitioned table */
+ if (relid_is_partitioned(pstate->p_target_rangetblentry->relid))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("ON CONFLICT clause is not supported with partitioned tables")));
+
qry->onConflict = transformOnConflictClause(pstate,
stmt->onConflictClause);
+ }
/*
* If we have a RETURNING clause, we need to add the target relation to
diff --git a/src/include/catalog/partition.h b/src/include/catalog/partition.h
index 7d4bf2f..c987d63 100644
--- a/src/include/catalog/partition.h
+++ b/src/include/catalog/partition.h
@@ -14,6 +14,8 @@
#define PARTITION_H
#include "fmgr.h"
+#include "executor/tuptable.h"
+#include "nodes/execnodes.h"
#include "parser/parse_node.h"
#include "utils/relcache.h"
@@ -85,6 +87,22 @@ typedef struct PartitionDescData
typedef struct PartitionDescData *PartitionDesc;
/*
+ * PartitionKeyExecInfo
+ *
+ * This struct holds the information needed to extract partition
+ * column values from a heap tuple.
+ *
+ * Key copy of the rd_partkey of rel
+ * ExpressionState exec state for expressions, or NIL if none
+ */
+typedef struct PartitionKeyExecInfo
+{
+ NodeTag type;
+ PartitionKey pi_Key;
+ List *pi_ExpressionState; /* list of ExprState */
+} PartitionKeyExecInfo;
+
+/*
* Partition tree node
*
* pkinfo PartitionKey executor state
@@ -100,6 +118,7 @@ typedef struct PartitionDescData *PartitionDesc;
*/
typedef struct PartitionDescNodeData
{
+ PartitionKeyExecInfo *pkinfo;
PartitionDesc pdesc;
Oid relid;
int index;
@@ -138,4 +157,9 @@ extern Expr *get_check_expr_from_partbound(Relation rel, Relation parent,
Node *bound);
extern List *get_leaf_partition_oids(PartitionDescNode pdnode);
extern PartitionDescNode RelationGetPartitionDescNode(Relation rel);
+
+extern int get_partition_for_tuple(PartitionDescNode pdnode,
+ TupleTableSlot *slot,
+ EState *estate,
+ Oid *failed_at);
#endif /* PARTITION_H */
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index 39521ed..c3b0f6d 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -14,6 +14,7 @@
#ifndef EXECUTOR_H
#define EXECUTOR_H
+#include "catalog/partition.h"
#include "executor/execdesc.h"
#include "nodes/parsenodes.h"
@@ -188,6 +189,7 @@ extern void CheckValidResultRel(Relation resultRel, CmdType operation);
extern void InitResultRelInfo(ResultRelInfo *resultRelInfo,
Relation resultRelationDesc,
Index resultRelationIndex,
+ bool load_partition_check,
int instrument_options);
extern ResultRelInfo *ExecGetTriggerResultRel(EState *estate, Oid relid);
extern bool ExecContextForcesOids(PlanState *planstate, bool *hasoids);
@@ -211,6 +213,10 @@ extern void EvalPlanQualSetPlan(EPQState *epqstate,
extern void EvalPlanQualSetTuple(EPQState *epqstate, Index rti,
HeapTuple tuple);
extern HeapTuple EvalPlanQualGetTuple(EPQState *epqstate, Index rti);
+extern int ExecFindPartition(ResultRelInfo *resultRelInfo,
+ PartitionDescNode pdnode,
+ TupleTableSlot *slot,
+ EState *estate);
#define EvalPlanQualSetSlot(epqstate, slot) ((epqstate)->origslot = (slot))
extern void EvalPlanQualFetchRowMarks(EPQState *epqstate);
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 74288e2..f2f535e 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -16,6 +16,7 @@
#include "access/genam.h"
#include "access/heapam.h"
+#include "access/tupconvert.h"
#include "executor/instrument.h"
#include "lib/pairingheap.h"
#include "nodes/params.h"
@@ -1140,6 +1141,15 @@ typedef struct ModifyTableState
* tlist */
TupleTableSlot *mt_conflproj; /* CONFLICT ... SET ... projection
* target */
+ struct PartitionDescNodeData *mt_partition_node;
+ /* Partition descriptor node tree */
+ ResultRelInfo *mt_partitions; /* Per leaf partition target
+ * relations */
+ TupleConversionMap **mt_partition_tupconv_maps;
+ /* Per leaf partition
+ * tuple conversion map */
+ int mt_num_partitions; /* Number of leaf partition target
+ * relations in the above array */
} ModifyTableState;
/* ----------------
--
1.7.1
^ permalink raw reply [nested|flat] 161+ messages in thread
* Re: Declarative partitioning
@ 2016-06-21 07:49 Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
1 sibling, 1 reply; 161+ messages in thread
From: Ashutosh Bapat @ 2016-06-21 07:49 UTC (permalink / raw)
To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; +Cc: Ildar Musin <i.musin@postgrespro.ru>; Tom Lane <tgl@sss.pgh.pa.us>; pgsql-hackers
Hi Amit,
I tried creating partitioned table by range on an expression like
CREATE TABLE pt1_e (a int, b int, c varchar) PARTITION BY RANGE(a +
b);
ERROR: syntax error at or near
"+"
LINE 1: ...E pt1_e (a int, b int, c varchar) PARTITION BY RANGE(a + b);
But when I try to create partitioned table by range on expression chr(a) it
works.
On Thu, Jun 9, 2016 at 7:20 AM, Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
wrote:
> On 2016/06/08 22:22, Ashutosh Bapat wrote:
> > On Mon, May 23, 2016 at 3:35 PM, Amit Langote wrote
> >>
> >> [...]
> >>
> >> I made a mistake in the last version of the patch which caused a
> relcache
> >> field to be pfree'd unexpectedly. Attached updated patches.
> >
> > 0003-... patch does not apply cleanly. It has some conflicts in
> pg_dump.c.
> > I have tried fixing the conflict in attached patch.
>
> Thanks. See attached rebased patches.
>
> Regards,
> Amit
>
--
Best Wishes,
Ashutosh Bapat
EnterpriseDB Corporation
The Postgres Database Company
^ permalink raw reply [nested|flat] 161+ messages in thread
* Re: Declarative partitioning
@ 2016-06-21 08:13 Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
parent: Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
0 siblings, 0 replies; 161+ messages in thread
From: Amit Langote @ 2016-06-21 08:13 UTC (permalink / raw)
To: Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; +Cc: Ildar Musin <i.musin@postgrespro.ru>; Tom Lane <tgl@sss.pgh.pa.us>; pgsql-hackers
On 2016/06/21 16:49, Ashutosh Bapat wrote:
> Hi Amit,
> I tried creating partitioned table by range on an expression like
> CREATE TABLE pt1_e (a int, b int, c varchar) PARTITION BY RANGE(a +
> b);
> ERROR: syntax error at or near
> "+"
>
> LINE 1: ...E pt1_e (a int, b int, c varchar) PARTITION BY RANGE(a + b);
>
> But when I try to create partitioned table by range on expression chr(a) it
> works.
The expression needs to be parenthesized (function call expressions don't
need though). So something like the following would work:
CREATE TABLE pt1_e (a int, b int, c varchar) PARTITION BY RANGE(add(a, b));
Thanks,
Amit
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 161+ messages in thread
* Re: Declarative partitioning
@ 2016-06-24 14:08 Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
1 sibling, 1 reply; 161+ messages in thread
From: Ashutosh Bapat @ 2016-06-24 14:08 UTC (permalink / raw)
To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; +Cc: Ildar Musin <i.musin@postgrespro.ru>; Tom Lane <tgl@sss.pgh.pa.us>; pgsql-hackers
Hi Amit,
I tried creating 2-level partitioned table and tried to create simple table
using CTAS from the partitioned table. It gives a cache lookup error.
Here's the test
CREATE TABLE pt1_l (a int, b varchar, c int) PARTITION BY RANGE(a);
CREATE TABLE pt1_l_p1 PARTITION OF pt1_l FOR VALUES START (1) END (250)
INCLUSIVE PARTITION BY RANGE(b);
CREATE TABLE pt1_l_p2 PARTITION OF pt1_l FOR VALUES START (251) END (500)
INCLUSIVE PARTITION BY RANGE(((a+c)/2));
CREATE TABLE pt1_l_p3 PARTITION OF pt1_l FOR VALUES START (501) END (600)
INCLUSIVE PARTITION BY RANGE(c);
CREATE TABLE pt1_l_p1_p1 PARTITION OF pt1_l_p1 FOR VALUES START ('000001')
END ('000125') INCLUSIVE;
CREATE TABLE pt1_l_p1_p2 PARTITION OF pt1_l_p1 FOR VALUES START ('000126')
END ('000250') INCLUSIVE;
CREATE TABLE pt1_l_p2_p1 PARTITION OF pt1_l_p2 FOR VALUES START (251) END
(375) INCLUSIVE;
CREATE TABLE pt1_l_p2_p2 PARTITION OF pt1_l_p2 FOR VALUES START (376) END
(500) INCLUSIVE;
CREATE TABLE pt1_l_p3_p1 PARTITION OF pt1_l_p3 FOR VALUES START (501) END
(550) INCLUSIVE;
CREATE TABLE pt1_l_p3_p2 PARTITION OF pt1_l_p3 FOR VALUES START (551) END
(600) INCLUSIVE;
INSERT INTO pt1_l SELECT i, to_char(i, 'FM000000'), i FROM
generate_series(1, 600, 2) i;
CREATE TABLE upt1_l AS SELECT * FROM pt1_l;
The last statement gives error "ERROR: cache lookup failed for function
0". Let me know if this problem is reproducible.
On Thu, Jun 9, 2016 at 7:20 AM, Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
wrote:
> On 2016/06/08 22:22, Ashutosh Bapat wrote:
> > On Mon, May 23, 2016 at 3:35 PM, Amit Langote wrote
> >>
> >> [...]
> >>
> >> I made a mistake in the last version of the patch which caused a
> relcache
> >> field to be pfree'd unexpectedly. Attached updated patches.
> >
> > 0003-... patch does not apply cleanly. It has some conflicts in
> pg_dump.c.
> > I have tried fixing the conflict in attached patch.
>
> Thanks. See attached rebased patches.
>
> Regards,
> Amit
>
--
Best Wishes,
Ashutosh Bapat
EnterpriseDB Corporation
The Postgres Database Company
^ permalink raw reply [nested|flat] 161+ messages in thread
* Re: Declarative partitioning
@ 2016-06-27 10:26 Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
parent: Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
0 siblings, 1 reply; 161+ messages in thread
From: Amit Langote @ 2016-06-27 10:26 UTC (permalink / raw)
To: Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; +Cc: Ildar Musin <i.musin@postgrespro.ru>; Tom Lane <tgl@sss.pgh.pa.us>; pgsql-hackers
Hi Ashutosh,
On 2016/06/24 23:08, Ashutosh Bapat wrote:
> Hi Amit,
> I tried creating 2-level partitioned table and tried to create simple table
> using CTAS from the partitioned table. It gives a cache lookup error.
> Here's the test
> CREATE TABLE pt1_l (a int, b varchar, c int) PARTITION BY RANGE(a);
> CREATE TABLE pt1_l_p1 PARTITION OF pt1_l FOR VALUES START (1) END (250)
> INCLUSIVE PARTITION BY RANGE(b);
> CREATE TABLE pt1_l_p2 PARTITION OF pt1_l FOR VALUES START (251) END (500)
> INCLUSIVE PARTITION BY RANGE(((a+c)/2));
> CREATE TABLE pt1_l_p3 PARTITION OF pt1_l FOR VALUES START (501) END (600)
> INCLUSIVE PARTITION BY RANGE(c);
> CREATE TABLE pt1_l_p1_p1 PARTITION OF pt1_l_p1 FOR VALUES START ('000001')
> END ('000125') INCLUSIVE;
> CREATE TABLE pt1_l_p1_p2 PARTITION OF pt1_l_p1 FOR VALUES START ('000126')
> END ('000250') INCLUSIVE;
> CREATE TABLE pt1_l_p2_p1 PARTITION OF pt1_l_p2 FOR VALUES START (251) END
> (375) INCLUSIVE;
> CREATE TABLE pt1_l_p2_p2 PARTITION OF pt1_l_p2 FOR VALUES START (376) END
> (500) INCLUSIVE;
> CREATE TABLE pt1_l_p3_p1 PARTITION OF pt1_l_p3 FOR VALUES START (501) END
> (550) INCLUSIVE;
> CREATE TABLE pt1_l_p3_p2 PARTITION OF pt1_l_p3 FOR VALUES START (551) END
> (600) INCLUSIVE;
> INSERT INTO pt1_l SELECT i, to_char(i, 'FM000000'), i FROM
> generate_series(1, 600, 2) i;
> CREATE TABLE upt1_l AS SELECT * FROM pt1_l;
>
> The last statement gives error "ERROR: cache lookup failed for function
> 0". Let me know if this problem is reproducible.
Thanks for the test case. I can reproduce the error. It has to do with
partition key column (b) being of type varchar whereas the default
operator family for the type being text_ops and there not being an
=(varchar, varchar) operator in text_ops.
When creating a plan for SELECT * FROM pt1_l, which is an Append plan,
partition check quals are generated internally to be used for constraint
exclusion - such as, b >= '000001' AND b < '000125'. Individual OpExpr's
are generated by using information from PartitionKey of the parent
(PartitionKey) and pg_partition entry of the partition. When choosing the
operator to use for =, >=, <, etc., opfamily and typid of corresponding
columns are referred. As mentioned above, in this case, they happened to
be text_ops and varchar, respectively, for column b. There doesn't exist
an operator =(varchar, varchar) in text_ops, so InvalidOid is returned by
get_opfamily_member which goes unchecked until the error in question occurs.
I have tried to fix this in attached updated patch by using operator class
input type for operator resolution in cases where column type didn't help.
Thanks,
Amit
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Attachments:
[text/x-diff] 0001-Add-syntax-to-specify-partition-key-v7.patch (40.1K, ../../81371428-bb4b-1e33-5ad6-8c5c51b52cb7@lab.ntt.co.jp/2-0001-Add-syntax-to-specify-partition-key-v7.patch)
download | inline diff:
From e35fa0f159f7249436fc7245426f42e035eee924 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Mon, 22 Feb 2016 14:06:33 +0900
Subject: [PATCH 1/6] Add syntax to specify partition key.
This adds the following clause to CREATE TABLE:
PARTITION BY method (col [USING opclass] [, ...])
where 'method' is either RANGE or LIST. 'col' could be either a simple
column reference or a parenthesized expression. 'opclass' is name
of btree operator class to use with the column. If method is LIST,
only one column is allowed, whereas, for RANGE method up to 32 columns
can be specified. If this clause is specified, the following constraints
viz. PARTITION KEY, UNIQUE, FOREIGN KEY, EXCLUSION are not allowed
because they cannot be supported on partitioned tables.
This adds 2 new parser nodes viz. PartitionElem and PartitionBy. The former
is similar to IndexElem and the latter consists of details of partition
key such as the method and list of PartitionElems. The new member partby
of CreateStmt is a pointer to the PartitionBy struct created when PARTITION
BY clause is present.
---
doc/src/sgml/ref/create_table.sgml | 35 +++++
src/backend/commands/indexcmds.c | 4 +-
src/backend/commands/tablecmds.c | 212 ++++++++++++++++++++++++++++
src/backend/nodes/copyfuncs.c | 33 +++++
src/backend/nodes/equalfuncs.c | 28 ++++
src/backend/nodes/outfuncs.c | 26 ++++
src/backend/parser/gram.y | 110 ++++++++++++---
src/backend/parser/parse_agg.c | 11 ++
src/backend/parser/parse_expr.c | 5 +
src/backend/parser/parse_utilcmd.c | 63 ++++++++
src/include/commands/defrem.h | 2 +
src/include/nodes/nodes.h | 2 +
src/include/nodes/parsenodes.h | 35 +++++
src/include/parser/kwlist.h | 1 +
src/include/parser/parse_node.h | 3 +-
src/include/pg_config_manual.h | 5 +
src/test/regress/expected/create_table.out | 67 +++++++++
src/test/regress/sql/create_table.sql | 56 ++++++++
18 files changed, 676 insertions(+), 22 deletions(-)
diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml
index 06e6392..c023260 100644
--- a/doc/src/sgml/ref/create_table.sgml
+++ b/doc/src/sgml/ref/create_table.sgml
@@ -28,6 +28,7 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
[, ... ]
] )
[ INHERITS ( <replaceable>parent_table</replaceable> [, ... ] ) ]
+[ PARTITION BY {RANGE | LIST} ( { <replaceable class="parameter">column_name</replaceable> | ( <replaceable class="parameter">expression</replaceable> ) } [ <replaceable class="parameter">opclass</replaceable> ] [, ...] )
[ WITH ( <replaceable class="PARAMETER">storage_parameter</replaceable> [= <replaceable class="PARAMETER">value</replaceable>] [, ... ] ) | WITH OIDS | WITHOUT OIDS ]
[ ON COMMIT { PRESERVE ROWS | DELETE ROWS | DROP } ]
[ TABLESPACE <replaceable class="PARAMETER">tablespace_name</replaceable> ]
@@ -38,6 +39,7 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
| <replaceable>table_constraint</replaceable> }
[, ... ]
) ]
+[ PARTITION BY {RANGE | LIST} ( { <replaceable class="parameter">column_name</replaceable> | ( <replaceable class="parameter">expression</replaceable> ) } [ <replaceable class="parameter">opclass</replaceable> ] [, ...] )
[ WITH ( <replaceable class="PARAMETER">storage_parameter</replaceable> [= <replaceable class="PARAMETER">value</replaceable>] [, ... ] ) | WITH OIDS | WITHOUT OIDS ]
[ ON COMMIT { PRESERVE ROWS | DELETE ROWS | DROP } ]
[ TABLESPACE <replaceable class="PARAMETER">tablespace_name</replaceable> ]
@@ -314,6 +316,39 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
</varlistentry>
<varlistentry>
+ <term><literal>PARTITION BY {RANGE | LIST} ( { <replaceable class="parameter">column_name</replaceable> | ( <replaceable class="parameter">expression</replaceable> ) } [ <replaceable class="parameter">opclass</replaceable> ] [, ...] ) </literal></term>
+ <listitem>
+ <para>
+ The optional <literal>PARTITION BY</> clause specifies a method of
+ partitioning the table and the corresponding partition key. Table
+ thus created is called <firstterm>partitioned</firstterm> table. Key
+ consists of an ordered list of column names and/or expressions when
+ using the <literal>RANGE</> method, whereas only a single column or
+ expression can be specified when using the <literal>LIST</> method.
+ The type of a key column or an expresion must have an associated
+ btree operator class or one must be specified along with the column
+ or the expression.
+ </para>
+
+ <para>
+ A partitioned table is divided into sub-tables (called partitions), which
+ in turn, are created using separate <literal>CREATE TABLE</> commands.
+ The table itself is empty. A data row inserted into the table is mapped
+ to and stored in one of the partitions (if one exists) based on the
+ values of columns and/or expressions in the partition key and partition
+ rules associated with the partitions.
+ </para>
+
+ <para>
+ Currently, there are following limitations on definition of partitioned
+ tables: one cannot specify any UNIQUE, PRIMARY KEY, EXCLUDE and/or
+ FOREIGN KEY constraints.
+ </para>
+
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><literal>LIKE <replaceable>source_table</replaceable> [ <replaceable>like_option</replaceable> ... ]</literal></term>
<listitem>
<para>
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index d14d540..4ef6b80 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -69,8 +69,6 @@ static void ComputeIndexAttrs(IndexInfo *indexInfo,
char *accessMethodName, Oid accessMethodId,
bool amcanorder,
bool isconstraint);
-static Oid GetIndexOpClass(List *opclass, Oid attrType,
- char *accessMethodName, Oid accessMethodId);
static char *ChooseIndexName(const char *tabname, Oid namespaceId,
List *colnames, List *exclusionOpNames,
bool primary, bool isconstraint);
@@ -1256,7 +1254,7 @@ ComputeIndexAttrs(IndexInfo *indexInfo,
/*
* Resolve possibly-defaulted operator class specification
*/
-static Oid
+Oid
GetIndexOpClass(List *opclass, Oid attrType,
char *accessMethodName, Oid accessMethodId)
{
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 86e9814..7c82f39 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -433,6 +433,11 @@ static void RangeVarCallbackForDropRelation(const RangeVar *rel, Oid relOid,
Oid oldRelOid, void *arg);
static void RangeVarCallbackForAlterRelation(const RangeVar *rv, Oid relid,
Oid oldrelid, void *arg);
+static PartitionBy *transformPartitionBy(Relation rel, PartitionBy *partitionby);
+static void ComputePartitionAttrs(Oid relid, List *partParams,
+ AttrNumber *partattrs,
+ List **partexprs,
+ Oid *partoplass);
/* ----------------------------------------------------------------
@@ -710,6 +715,18 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
AddRelationNewConstraints(rel, rawDefaults, stmt->constraints,
true, true, false);
+ /* Process partition key, if any */
+ if (stmt->partby)
+ {
+ AttrNumber partattrs[PARTITION_MAX_KEYS];
+ Oid partopclass[PARTITION_MAX_KEYS];
+ List *partexprs = NIL;
+
+ stmt->partby = transformPartitionBy(rel, stmt->partby);
+ ComputePartitionAttrs(relationId, stmt->partby->partParams,
+ partattrs, &partexprs, partopclass);
+ }
+
ObjectAddressSet(address, RelationRelationId, relationId);
/*
@@ -12059,3 +12076,198 @@ RangeVarCallbackForAlterRelation(const RangeVar *rv, Oid relid, Oid oldrelid,
ReleaseSysCache(tuple);
}
+
+/*
+ * transformPartitionBy
+ * Transform any expressions present in the partition key
+ */
+static PartitionBy *
+transformPartitionBy(Relation rel, PartitionBy *partitionby)
+{
+ PartitionBy *partby;
+ ParseState *pstate;
+ RangeTblEntry *rte;
+ ListCell *l;
+
+ partby = (PartitionBy *) makeNode(PartitionBy);
+
+ partby->strategy = partitionby->strategy;
+ partby->location = partitionby->location;
+ partby->partParams = NIL;
+
+ /*
+ * Create a dummy ParseState and insert the target relation as its sole
+ * rangetable entry. We need a ParseState for transformExpr.
+ */
+ pstate = make_parsestate(NULL);
+ rte = addRangeTableEntryForRelation(pstate, rel, NULL, false, true);
+ addRTEtoQuery(pstate, rte, true, true, true);
+
+ /* take care of any partition expressions */
+ foreach(l, partitionby->partParams)
+ {
+ ListCell *column;
+ PartitionElem *pelem = (PartitionElem *) lfirst(l);
+
+ /* Check for PARTITION BY ... ON (foo, foo) */
+ foreach(column, partby->partParams)
+ {
+ PartitionElem *pparam = (PartitionElem *) lfirst(column);
+
+ if (pelem->name && pparam->name &&
+ !strcmp(pelem->name, pparam->name))
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_COLUMN),
+ errmsg("column \"%s\" appears twice in partition key", pelem->name),
+ parser_errposition(pstate, pelem->location)));
+ }
+
+ if (pelem->expr)
+ {
+ /* Now do parse transformation of the expression */
+ pelem->expr = transformExpr(pstate, pelem->expr,
+ EXPR_KIND_PARTITION_KEY);
+
+ /* we have to fix its collations too */
+ assign_expr_collations(pstate, pelem->expr);
+
+ /*
+ * transformExpr() should have already rejected subqueries,
+ * aggregates, and window functions, based on the EXPR_KIND_ for
+ * a partition key expression.
+ *
+ * Also reject expressions returning sets; this is for consistency
+ * DefineRelation() will make more checks.
+ */
+ if (expression_returns_set(pelem->expr))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("partition key expression cannot return a set"),
+ parser_errposition(pstate, pelem->location)));
+ }
+
+ partby->partParams = lappend(partby->partParams, pelem);
+ }
+
+ return partby;
+}
+
+/*
+ * ComputePartitionAttrs
+ * Compute per-partition-column information from partParams
+ */
+static void
+ComputePartitionAttrs(Oid relid, List *partParams, AttrNumber *partattrs,
+ List **partexprs, Oid *partopclass)
+{
+ int attn;
+ ListCell *lc;
+
+ attn = 0;
+ foreach(lc, partParams)
+ {
+ PartitionElem *pelem = (PartitionElem *) lfirst(lc);
+ Oid atttype;
+ Oid opclassOid;
+
+ if (pelem->name != NULL)
+ {
+ HeapTuple atttuple;
+ Form_pg_attribute attform;
+
+ atttuple = SearchSysCacheAttName(relid, pelem->name);
+ if (!HeapTupleIsValid(atttuple))
+ {
+ /* difference in error message spellings is historical */
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_COLUMN),
+ errmsg("column \"%s\" named in partition key does not exist",
+ pelem->name)));
+ }
+ attform = (Form_pg_attribute) GETSTRUCT(atttuple);
+
+ if (attform->attnum <= 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_COLUMN),
+ errmsg("cannot use system column \"%s\" in partition key",
+ pelem->name)));
+
+ partattrs[attn] = attform->attnum;
+ atttype = attform->atttypid;
+ ReleaseSysCache(atttuple);
+ }
+ else
+ {
+ /* Partition key expression */
+ Node *expr = pelem->expr;
+
+ Assert(expr != NULL);
+ atttype = exprType(expr);
+
+ if (IsA(expr, CollateExpr))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("cannot use COLLATE in partition key expression")));
+
+ if (IsA(expr, Var) &&
+ ((Var *) expr)->varattno != InvalidAttrNumber)
+ {
+ /*
+ * User wrote "(column)" or "(column COLLATE something)".
+ * Treat it like simple attribute anyway.
+ */
+ partattrs[attn] = ((Var *) expr)->varattno;
+ }
+ else
+ {
+ partattrs[attn] = 0; /* marks expression */
+ *partexprs = lappend(*partexprs, expr);
+
+ /*
+ * transformExpr() should have already rejected subqueries,
+ * aggregates, and window functions, based on the EXPR_KIND_
+ * for a partition key expression.
+ */
+
+ /*
+ * An expression using mutable functions is probably wrong even
+ * even to use in a partition key
+ */
+ expr = (Node *) expression_planner((Expr *) expr);
+
+ if (IsA(expr, Const))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("cannot use a constant expression as partition key")));
+
+ if (contain_mutable_functions(expr))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("functions in partition key expression must be marked IMMUTABLE")));
+ }
+ }
+
+ /*
+ * Identify the opclass to use. At the moment, we use "btree" operators
+ * that seems enough for list and range partitioning.
+ */
+ if (!pelem->opclass)
+ {
+ opclassOid = GetDefaultOpClass(atttype, BTREE_AM_OID);
+
+ if (!OidIsValid(opclassOid))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("data type %s has no default btree operator class",
+ format_type_be(atttype)),
+ errhint("You must specify an existing btree operator class or define one for the type.")));
+ }
+ else
+ opclassOid = GetIndexOpClass(pelem->opclass,
+ atttype,
+ "btree",
+ BTREE_AM_OID);
+
+ partopclass[attn++] = opclassOid;
+ }
+}
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index d278657..ecf9cbf 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3000,6 +3000,7 @@ CopyCreateStmtFields(const CreateStmt *from, CreateStmt *newnode)
COPY_NODE_FIELD(relation);
COPY_NODE_FIELD(tableElts);
COPY_NODE_FIELD(inhRelations);
+ COPY_NODE_FIELD(partby);
COPY_NODE_FIELD(ofTypename);
COPY_NODE_FIELD(constraints);
COPY_NODE_FIELD(options);
@@ -4155,6 +4156,32 @@ _copyAlterPolicyStmt(const AlterPolicyStmt *from)
return newnode;
}
+static PartitionBy *
+_copyPartitionBy(const PartitionBy *from)
+{
+
+ PartitionBy *newnode = makeNode(PartitionBy);
+
+ COPY_SCALAR_FIELD(strategy);
+ COPY_NODE_FIELD(partParams);
+ COPY_LOCATION_FIELD(location);
+
+ return newnode;
+}
+
+static PartitionElem *
+_copyPartitionElem(const PartitionElem *from)
+{
+ PartitionElem *newnode = makeNode(PartitionElem);
+
+ COPY_STRING_FIELD(name);
+ COPY_NODE_FIELD(expr);
+ COPY_NODE_FIELD(opclass);
+ COPY_LOCATION_FIELD(location);
+
+ return newnode;
+}
+
/* ****************************************************************
* pg_list.h copy functions
* ****************************************************************
@@ -5066,6 +5093,12 @@ copyObject(const void *from)
case T_RoleSpec:
retval = _copyRoleSpec(from);
break;
+ case T_PartitionBy:
+ retval = _copyPartitionBy(from);
+ break;
+ case T_PartitionElem:
+ retval = _copyPartitionElem(from);
+ break;
/*
* MISCELLANEOUS NODES
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 1eb6799..9554c2f 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1156,6 +1156,7 @@ _equalCreateStmt(const CreateStmt *a, const CreateStmt *b)
COMPARE_NODE_FIELD(relation);
COMPARE_NODE_FIELD(tableElts);
COMPARE_NODE_FIELD(inhRelations);
+ COMPARE_NODE_FIELD(partby);
COMPARE_NODE_FIELD(ofTypename);
COMPARE_NODE_FIELD(constraints);
COMPARE_NODE_FIELD(options);
@@ -2619,6 +2620,27 @@ _equalRoleSpec(const RoleSpec *a, const RoleSpec *b)
return true;
}
+static bool
+_equalPartitionBy(const PartitionBy *a, const PartitionBy *b)
+{
+ COMPARE_SCALAR_FIELD(strategy);
+ COMPARE_NODE_FIELD(partParams);
+ COMPARE_LOCATION_FIELD(location);
+
+ return true;
+}
+
+static bool
+_equalPartitionElem(const PartitionElem *a, const PartitionElem *b)
+{
+ COMPARE_STRING_FIELD(name);
+ COMPARE_NODE_FIELD(expr);
+ COMPARE_NODE_FIELD(opclass);
+ COMPARE_LOCATION_FIELD(location);
+
+ return true;
+}
+
/*
* Stuff from pg_list.h
*/
@@ -3369,6 +3391,12 @@ equal(const void *a, const void *b)
case T_RoleSpec:
retval = _equalRoleSpec(a, b);
break;
+ case T_PartitionBy:
+ retval = _equalPartitionBy(a, b);
+ break;
+ case T_PartitionElem:
+ retval = _equalPartitionElem(a, b);
+ break;
default:
elog(ERROR, "unrecognized node type: %d",
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 9186f04..0b7cec1 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2392,6 +2392,7 @@ _outCreateStmtInfo(StringInfo str, const CreateStmt *node)
WRITE_NODE_FIELD(relation);
WRITE_NODE_FIELD(tableElts);
WRITE_NODE_FIELD(inhRelations);
+ WRITE_NODE_FIELD(partby);
WRITE_NODE_FIELD(ofTypename);
WRITE_NODE_FIELD(constraints);
WRITE_NODE_FIELD(options);
@@ -3261,6 +3262,26 @@ _outForeignKeyCacheInfo(StringInfo str, const ForeignKeyCacheInfo *node)
appendStringInfo(str, " %u", node->conpfeqop[i]);
}
+static void
+_outPartitionBy(StringInfo str, const PartitionBy *node)
+{
+ WRITE_NODE_TYPE("PARTITIONBY");
+
+ WRITE_CHAR_FIELD(strategy);
+ WRITE_NODE_FIELD(partParams);
+ WRITE_LOCATION_FIELD(location);
+}
+
+static void
+_outPartitionElem(StringInfo str, const PartitionElem *node)
+{
+ WRITE_NODE_TYPE("PARTITIONELEM");
+
+ WRITE_STRING_FIELD(name);
+ WRITE_NODE_FIELD(expr);
+ WRITE_NODE_FIELD(opclass);
+ WRITE_LOCATION_FIELD(location);
+}
/*
* outNode -
@@ -3842,6 +3863,11 @@ outNode(StringInfo str, const void *obj)
break;
case T_ForeignKeyCacheInfo:
_outForeignKeyCacheInfo(str, obj);
+ case T_PartitionBy:
+ _outPartitionBy(str, obj);
+ break;
+ case T_PartitionElem:
+ _outPartitionElem(str, obj);
break;
default:
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index edf4516..bf29205 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -227,6 +227,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
struct ImportQual *importqual;
InsertStmt *istmt;
VariableSetStmt *vsetstmt;
+ PartitionElem *partelem;
+ PartitionBy *partby;
}
%type <node> stmt schema_stmt
@@ -539,6 +541,9 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
opt_frame_clause frame_extent frame_bound
%type <str> opt_existing_window_name
%type <boolean> opt_if_not_exists
+%type <partby> PartitionBy OptPartitionBy
+%type <partelem> part_elem
+%type <list> part_params
/*
* Non-keyword token types. These are hard-wired into the "flex" lexer.
@@ -603,7 +608,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
KEY
LABEL LANGUAGE LARGE_P LAST_P LATERAL_P
- LEADING LEAKPROOF LEAST LEFT LEVEL LIKE LIMIT LISTEN LOAD LOCAL
+ LEADING LEAKPROOF LEAST LEFT LEVEL LIKE LIMIT LIST LISTEN LOAD LOCAL
LOCALTIME LOCALTIMESTAMP LOCATION LOCK_P LOCKED LOGGED
MAPPING MATCH MATERIALIZED MAXVALUE METHOD MINUTE_P MINVALUE MODE MONTH_P MOVE
@@ -2808,69 +2813,75 @@ copy_generic_opt_arg_list_item:
*****************************************************************************/
CreateStmt: CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
- OptInherit OptWith OnCommitOption OptTableSpace
+ OptInherit OptPartitionBy OptWith OnCommitOption OptTableSpace
{
CreateStmt *n = makeNode(CreateStmt);
$4->relpersistence = $2;
n->relation = $4;
n->tableElts = $6;
n->inhRelations = $8;
+ n->partby = $9;
n->ofTypename = NULL;
n->constraints = NIL;
- n->options = $9;
- n->oncommit = $10;
- n->tablespacename = $11;
+ n->options = $10;
+ n->oncommit = $11;
+ n->tablespacename = $12;
n->if_not_exists = false;
$$ = (Node *)n;
}
| CREATE OptTemp TABLE IF_P NOT EXISTS qualified_name '('
- OptTableElementList ')' OptInherit OptWith OnCommitOption
- OptTableSpace
+ OptTableElementList ')' OptInherit OptPartitionBy OptWith
+ OnCommitOption OptTableSpace
{
CreateStmt *n = makeNode(CreateStmt);
$7->relpersistence = $2;
n->relation = $7;
n->tableElts = $9;
n->inhRelations = $11;
+ n->partby = $12;
n->ofTypename = NULL;
n->constraints = NIL;
- n->options = $12;
- n->oncommit = $13;
- n->tablespacename = $14;
+ n->options = $13;
+ n->oncommit = $14;
+ n->tablespacename = $15;
n->if_not_exists = true;
$$ = (Node *)n;
}
| CREATE OptTemp TABLE qualified_name OF any_name
- OptTypedTableElementList OptWith OnCommitOption OptTableSpace
+ OptTypedTableElementList OptPartitionBy OptWith OnCommitOption
+ OptTableSpace
{
CreateStmt *n = makeNode(CreateStmt);
$4->relpersistence = $2;
n->relation = $4;
n->tableElts = $7;
n->inhRelations = NIL;
+ n->partby = $8;
n->ofTypename = makeTypeNameFromNameList($6);
n->ofTypename->location = @6;
n->constraints = NIL;
- n->options = $8;
- n->oncommit = $9;
- n->tablespacename = $10;
+ n->options = $9;
+ n->oncommit = $10;
+ n->tablespacename = $11;
n->if_not_exists = false;
$$ = (Node *)n;
}
| CREATE OptTemp TABLE IF_P NOT EXISTS qualified_name OF any_name
- OptTypedTableElementList OptWith OnCommitOption OptTableSpace
+ OptTypedTableElementList OptPartitionBy OptWith OnCommitOption
+ OptTableSpace
{
CreateStmt *n = makeNode(CreateStmt);
$7->relpersistence = $2;
n->relation = $7;
n->tableElts = $10;
n->inhRelations = NIL;
+ n->partby = $11;
n->ofTypename = makeTypeNameFromNameList($9);
n->ofTypename->location = @9;
n->constraints = NIL;
- n->options = $11;
- n->oncommit = $12;
- n->tablespacename = $13;
+ n->options = $12;
+ n->oncommit = $13;
+ n->tablespacename = $14;
n->if_not_exists = true;
$$ = (Node *)n;
}
@@ -3415,6 +3426,68 @@ OptInherit: INHERITS '(' qualified_name_list ')' { $$ = $3; }
| /*EMPTY*/ { $$ = NIL; }
;
+/* Optional partition key definition */
+OptPartitionBy: PartitionBy { $$ = $1; }
+ | /*EMPTY*/ { $$ = NULL; }
+ ;
+
+PartitionBy: PARTITION BY RANGE '(' part_params ')'
+ {
+ PartitionBy *n = makeNode(PartitionBy);
+
+ n->strategy = PARTITION_STRAT_RANGE;
+ n->partParams = $5;
+ n->location = @1;
+
+ $$ = n;
+ }
+ | PARTITION BY LIST '(' part_params ')'
+ {
+ PartitionBy *n = makeNode(PartitionBy);
+
+ n->strategy = PARTITION_STRAT_LIST;
+ n->partParams = $5;
+ n->location = @1;
+
+ $$ = n;
+ }
+ ;
+
+part_params: part_elem { $$ = list_make1($1); }
+ | part_params ',' part_elem { $$ = lappend($1, $3); }
+ ;
+
+part_elem: ColId opt_class
+ {
+ PartitionElem *n = makeNode(PartitionElem);
+
+ n->name = $1;
+ n->expr = NULL;
+ n->opclass = $2;
+ n->location = @1;
+ $$ = n;
+ }
+ | func_expr_windowless opt_class
+ {
+ PartitionElem *n = makeNode(PartitionElem);
+
+ n->name = NULL;
+ n->expr = $1;
+ n->opclass = $2;
+ n->location = @1;
+ $$ = n;
+ }
+ | '(' a_expr ')' opt_class
+ {
+ PartitionElem *n = makeNode(PartitionElem);
+
+ n->name = NULL;
+ n->expr = $2;
+ n->opclass = $4;
+ n->location = @1;
+ $$ = n;
+ }
+ ;
/* WITH (options) is preferred, WITH OIDS and WITHOUT OIDS are legacy forms */
OptWith:
WITH reloptions { $$ = $2; }
@@ -13850,6 +13923,7 @@ unreserved_keyword:
| LAST_P
| LEAKPROOF
| LEVEL
+ | LIST
| LISTEN
| LOAD
| LOCAL
diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c
index 481a4dd..6c50a56 100644
--- a/src/backend/parser/parse_agg.c
+++ b/src/backend/parser/parse_agg.c
@@ -501,6 +501,14 @@ check_agglevels_and_constraints(ParseState *pstate, Node *expr)
err = _("grouping operations are not allowed in trigger WHEN conditions");
break;
+ case EXPR_KIND_PARTITION_KEY:
+ if (isAgg)
+ err = _("aggregate functions are not allowed in partition key expressions");
+ else
+ err = _("grouping operations are not allowed in partition key expressions");
+
+ break;
+
/*
* There is intentionally no default: case here, so that the
@@ -858,6 +866,9 @@ transformWindowFuncCall(ParseState *pstate, WindowFunc *wfunc,
case EXPR_KIND_TRIGGER_WHEN:
err = _("window functions are not allowed in trigger WHEN conditions");
break;
+ case EXPR_KIND_PARTITION_KEY:
+ err = _("window functions are not allowed in partition key expressions");
+ break;
/*
* There is intentionally no default: case here, so that the
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 8b28516..b53b7f1 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -1723,6 +1723,9 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
case EXPR_KIND_TRIGGER_WHEN:
err = _("cannot use subquery in trigger WHEN condition");
break;
+ case EXPR_KIND_PARTITION_KEY:
+ err = _("cannot use subquery in partition key expressions");
+ break;
/*
* There is intentionally no default: case here, so that the
@@ -3249,6 +3252,8 @@ ParseExprKindName(ParseExprKind exprKind)
return "EXECUTE";
case EXPR_KIND_TRIGGER_WHEN:
return "WHEN";
+ case EXPR_KIND_PARTITION_KEY:
+ return "partition key expression";
/*
* There is intentionally no default: case here, so that the
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 6313087..4591215 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -87,6 +87,7 @@ typedef struct
List *alist; /* "after list" of things to do after creating
* the table */
IndexStmt *pkey; /* PRIMARY KEY index, if any */
+ bool ispartitioned; /* true if table is partitioned */
} CreateStmtContext;
/* State shared by transformCreateSchemaStmt and its subroutines */
@@ -229,6 +230,7 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
cxt.blist = NIL;
cxt.alist = NIL;
cxt.pkey = NULL;
+ cxt.ispartitioned = stmt->partby != NULL;
/*
* Notice that we allow OIDs here only for plain tables, even though
@@ -247,6 +249,24 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
if (stmt->ofTypename)
transformOfType(&cxt, stmt->ofTypename);
+ if (stmt->partby)
+ {
+ int partnatts = list_length(stmt->partby->partParams);
+
+ if (partnatts > PARTITION_MAX_KEYS)
+ ereport(ERROR,
+ (errcode(ERRCODE_TOO_MANY_COLUMNS),
+ errmsg("cannot use more than %d columns in partition key",
+ PARTITION_MAX_KEYS)));
+
+ if (stmt->partby->strategy == PARTITION_STRAT_LIST &&
+ partnatts > 1)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("cannot use more than one column in partition key"),
+ errdetail("Only one column allowed with list partitioning.")));
+ }
+
/*
* Run through each primary element in the table creation clause. Separate
* column defs from constraints, and do preliminary analysis. We have to
@@ -582,6 +602,12 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
errmsg("primary key constraints are not supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
+ if (cxt->ispartitioned)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("primary key constraints are not supported on partitioned tables"),
+ parser_errposition(cxt->pstate,
+ constraint->location)));
/* FALL THRU */
case CONSTR_UNIQUE:
@@ -591,6 +617,12 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
errmsg("unique constraints are not supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
+ if (cxt->ispartitioned)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("unique constraints are not supported on partitioned tables"),
+ parser_errposition(cxt->pstate,
+ constraint->location)));
if (constraint->keys == NIL)
constraint->keys = list_make1(makeString(column->colname));
cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
@@ -608,6 +640,12 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
errmsg("foreign key constraints are not supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
+ if (cxt->ispartitioned)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("foreign key constraints are not supported on partitioned tables"),
+ parser_errposition(cxt->pstate,
+ constraint->location)));
/*
* Fill in the current attribute's name and throw it into the
@@ -673,6 +711,12 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
errmsg("primary key constraints are not supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
+ if (cxt->ispartitioned)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("primary key constraints are not supported on partitioned tables"),
+ parser_errposition(cxt->pstate,
+ constraint->location)));
cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
break;
@@ -683,6 +727,12 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
errmsg("unique constraints are not supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
+ if (cxt->ispartitioned)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("unique constraints are not supported on partitioned tables"),
+ parser_errposition(cxt->pstate,
+ constraint->location)));
cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
break;
@@ -693,6 +743,12 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
errmsg("exclusion constraints are not supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
+ if (cxt->ispartitioned)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("exclusion constraints are not supported on partitioned tables"),
+ parser_errposition(cxt->pstate,
+ constraint->location)));
cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
break;
@@ -707,6 +763,12 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
errmsg("foreign key constraints are not supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
+ if (cxt->ispartitioned)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("foreign key constraints are not supported on partitioned tables"),
+ parser_errposition(cxt->pstate,
+ constraint->location)));
cxt->fkconstraints = lappend(cxt->fkconstraints, constraint);
break;
@@ -2515,6 +2577,7 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
cxt.blist = NIL;
cxt.alist = NIL;
cxt.pkey = NULL;
+ cxt.ispartitioned = false;
/*
* The only subtypes that currently require parse transformation handling
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index b064eb4..28024f8 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -42,6 +42,8 @@ extern bool CheckIndexCompatible(Oid oldId,
List *attributeList,
List *exclusionOpNames);
extern Oid GetDefaultOpClass(Oid type_id, Oid am_id);
+extern Oid GetIndexOpClass(List *opclass, Oid attrType,
+ char *accessMethodName, Oid accessMethodId);
/* commands/functioncmds.c */
extern ObjectAddress CreateFunction(CreateFunctionStmt *stmt, const char *queryString);
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 6b850e4..c316af6 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -452,6 +452,8 @@ typedef enum NodeTag
T_OnConflictClause,
T_CommonTableExpr,
T_RoleSpec,
+ T_PartitionElem,
+ T_PartitionBy,
/*
* TAGS FOR REPLICATION GRAMMAR PARSE NODES (replnodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index d36d9c6..fe81f83 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -696,6 +696,40 @@ typedef struct XmlSerialize
int location; /* token location, or -1 if unknown */
} XmlSerialize;
+/* Partitioning related definitions */
+
+/*
+ * PartitionElem - a partition key column
+ *
+ * 'name' Name of the table column included in the key
+ * 'expr' Expression node tree of expressional key column
+ * 'opclass' Operator class name associated with the column
+ */
+typedef struct PartitionElem
+{
+ NodeTag type;
+ char *name; /* name of column to partition on, or NULL */
+ Node *expr; /* expression to partition on, or NULL */
+ List *opclass; /* name of desired opclass; NIL = default */
+ int location; /* token location, or -1 if unknown */
+} PartitionElem;
+
+/*
+ * PartitionBy - partition key definition including the strategy
+ *
+ * 'strategy' partition strategy to use (one of the below defined)
+ * 'partParams' List of PartitionElems, one for each key column
+ */
+#define PARTITION_STRAT_LIST 'l'
+#define PARTITION_STRAT_RANGE 'r'
+
+typedef struct PartitionBy
+{
+ NodeTag type;
+ char strategy;
+ List *partParams;
+ int location; /* token location, or -1 if unknown */
+} PartitionBy;
/****************************************************************************
* Nodes for a Query tree
@@ -1750,6 +1784,7 @@ typedef struct CreateStmt
List *tableElts; /* column definitions (list of ColumnDef) */
List *inhRelations; /* relations to inherit from (list of
* inhRelation) */
+ PartitionBy *partby; /* PARTITION BY clause */
TypeName *ofTypename; /* OF typename */
List *constraints; /* constraints (list of Constraint nodes) */
List *options; /* options from WITH clause */
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 17ffef5..40da67a 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -227,6 +227,7 @@ PG_KEYWORD("left", LEFT, TYPE_FUNC_NAME_KEYWORD)
PG_KEYWORD("level", LEVEL, UNRESERVED_KEYWORD)
PG_KEYWORD("like", LIKE, TYPE_FUNC_NAME_KEYWORD)
PG_KEYWORD("limit", LIMIT, RESERVED_KEYWORD)
+PG_KEYWORD("list", LIST, UNRESERVED_KEYWORD)
PG_KEYWORD("listen", LISTEN, UNRESERVED_KEYWORD)
PG_KEYWORD("load", LOAD, UNRESERVED_KEYWORD)
PG_KEYWORD("local", LOCAL, UNRESERVED_KEYWORD)
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index e3e359c..a13c6fb 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -64,7 +64,8 @@ typedef enum ParseExprKind
EXPR_KIND_ALTER_COL_TRANSFORM, /* transform expr in ALTER COLUMN TYPE */
EXPR_KIND_EXECUTE_PARAMETER, /* parameter value in EXECUTE */
EXPR_KIND_TRIGGER_WHEN, /* WHEN condition in CREATE TRIGGER */
- EXPR_KIND_POLICY /* USING or WITH CHECK expr in policy */
+ EXPR_KIND_POLICY, /* USING or WITH CHECK expr in policy */
+ EXPR_KIND_PARTITION_KEY /* partition key expression */
} ParseExprKind;
diff --git a/src/include/pg_config_manual.h b/src/include/pg_config_manual.h
index a2b2b61..01c6c09 100644
--- a/src/include/pg_config_manual.h
+++ b/src/include/pg_config_manual.h
@@ -46,6 +46,11 @@
#define INDEX_MAX_KEYS 32
/*
+ * Maximum number of columns in a partition key
+ */
+#define PARTITION_MAX_KEYS 32
+
+/*
* Set the upper and lower bounds of sequence values.
*/
#define SEQ_MAXVALUE PG_INT64_MAX
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 41ceb87..b3a392f 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -253,3 +253,70 @@ DROP TABLE as_select1;
-- check that the oid column is added before the primary key is checked
CREATE TABLE oid_pk (f1 INT, PRIMARY KEY(oid)) WITH OIDS;
DROP TABLE oid_pk;
+--
+-- partitioned tables
+--
+-- cannot use system columns in partition key
+CREATE TABLE fail_oid_pk (
+ a int
+) PARTITION BY RANGE (oid) WITH OIDS; -- fail
+ERROR: cannot use system column "oid" in partition key
+-- cannot use more than 1 column as partition key for list partitioned table
+CREATE TABLE two_col_tab (
+ a1 int,
+ a2 int
+) PARTITION BY LIST (a1, a2); -- fail
+ERROR: cannot use more than one column in partition key
+DETAIL: Only one column allowed with list partitioning.
+-- Partitioning requires to use columns with data type having a suitable
+-- btree operator class
+CREATE TABLE point_tab (
+ a point
+) PARTITION BY LIST (a); -- fail
+ERROR: data type point has no default btree operator class
+HINT: You must specify an existing btree operator class or define one for the type.
+CREATE TABLE point_tab (
+ a point
+) PARTITION BY LIST (a point_ops); -- fail
+ERROR: operator class "point_ops" does not exist for access method "btree"
+CREATE TABLE point_tab (
+ a point
+) PARTITION BY RANGE (a); -- fail
+ERROR: data type point has no default btree operator class
+HINT: You must specify an existing btree operator class or define one for the type.
+CREATE TABLE point_tab (
+ a point
+) PARTITION BY RANGE (a point_ops); -- fail
+ERROR: operator class "point_ops" does not exist for access method "btree"
+-- fail to create a partitioned table with PRIMARY KEY constraint
+CREATE TABLE pk_fail (
+ a int PRIMARY KEY
+) PARTITION BY RANGE (a);
+ERROR: primary key constraints are not supported on partitioned tables
+LINE 2: a int PRIMARY KEY
+ ^
+-- fail to create a partitioned table with foreign key constraint
+CREATE TABLE pk_rel(
+ a int PRIMARY KEY
+);
+CREATE TABLE fk_fail (
+ a int REFERENCES pk_rel(a)
+) PARTITION BY RANGE (a);
+ERROR: foreign key constraints are not supported on partitioned tables
+LINE 2: a int REFERENCES pk_rel(a)
+ ^
+DROP TABLE pk_rel;
+-- fail to create a partitioned table with prohibited expressions in key
+CREATE TABLE fail_const_key (
+ a int
+) PARTITION BY RANGE (('a'));
+ERROR: cannot use a constant expression as partition key
+CREATE TABLE fail_agg_in_key (
+ a int
+) PARTITION BY RANGE ((avg(a)));
+ERROR: aggregate functions are not allowed in partition key expressions
+CREATE TABLE fail_window_fun_in_key (
+ a int,
+ b int
+) PARTITION BY RANGE ((avg(a) OVER (PARTITION BY b)));
+ERROR: window functions are not allowed in partition key expressions
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 78bdc8b..de20fff 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -269,3 +269,59 @@ DROP TABLE as_select1;
-- check that the oid column is added before the primary key is checked
CREATE TABLE oid_pk (f1 INT, PRIMARY KEY(oid)) WITH OIDS;
DROP TABLE oid_pk;
+
+--
+-- partitioned tables
+--
+
+-- cannot use system columns in partition key
+CREATE TABLE fail_oid_pk (
+ a int
+) PARTITION BY RANGE (oid) WITH OIDS; -- fail
+
+-- cannot use more than 1 column as partition key for list partitioned table
+CREATE TABLE two_col_tab (
+ a1 int,
+ a2 int
+) PARTITION BY LIST (a1, a2); -- fail
+
+-- Partitioning requires to use columns with data type having a suitable
+-- btree operator class
+CREATE TABLE point_tab (
+ a point
+) PARTITION BY LIST (a); -- fail
+CREATE TABLE point_tab (
+ a point
+) PARTITION BY LIST (a point_ops); -- fail
+CREATE TABLE point_tab (
+ a point
+) PARTITION BY RANGE (a); -- fail
+CREATE TABLE point_tab (
+ a point
+) PARTITION BY RANGE (a point_ops); -- fail
+
+-- fail to create a partitioned table with PRIMARY KEY constraint
+CREATE TABLE pk_fail (
+ a int PRIMARY KEY
+) PARTITION BY RANGE (a);
+
+-- fail to create a partitioned table with foreign key constraint
+CREATE TABLE pk_rel(
+ a int PRIMARY KEY
+);
+CREATE TABLE fk_fail (
+ a int REFERENCES pk_rel(a)
+) PARTITION BY RANGE (a);
+DROP TABLE pk_rel;
+
+-- fail to create a partitioned table with prohibited expressions in key
+CREATE TABLE fail_const_key (
+ a int
+) PARTITION BY RANGE (('a'));
+CREATE TABLE fail_agg_in_key (
+ a int
+) PARTITION BY RANGE ((avg(a)));
+CREATE TABLE fail_window_fun_in_key (
+ a int,
+ b int
+) PARTITION BY RANGE ((avg(a) OVER (PARTITION BY b)));
--
1.7.1
[text/x-diff] 0002-Add-a-IGNORE-dependency-type-v7.patch (3.0K, ../../81371428-bb4b-1e33-5ad6-8c5c51b52cb7@lab.ntt.co.jp/3-0002-Add-a-IGNORE-dependency-type-v7.patch)
download | inline diff:
From f247bb7f1d5be15f2f00aff100532f2866a4b2fd Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Fri, 13 May 2016 12:10:10 +0900
Subject: [PATCH 2/6] Add a IGNORE dependency type.
This new type of dependency is mainly to be used internally as a signal
that no dependency should be created between the objects in question.
Use this, for example, when there is a possibility that redundant self-
dependencies could be created in certain situations - imagine creation
of dependencies for objects referenced in expressions in the partition
key of a table; it is inevitable that vars of the table in question
would be encountered which must be ignored.
---
src/backend/catalog/dependency.c | 2 ++
src/backend/catalog/pg_depend.c | 3 +++
src/include/catalog/dependency.h | 8 +++++++-
3 files changed, 12 insertions(+), 1 deletions(-)
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index 04d7840..607274d 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -717,6 +717,7 @@ findDependentObjects(const ObjectAddress *object,
getObjectDescription(object));
break;
default:
+ Assert(foundDep->deptype != DEPENDENCY_IGNORE);
elog(ERROR, "unrecognized dependency type '%c' for %s",
foundDep->deptype, getObjectDescription(object));
break;
@@ -813,6 +814,7 @@ findDependentObjects(const ObjectAddress *object,
subflags = 0; /* keep compiler quiet */
break;
default:
+ Assert(foundDep->deptype != DEPENDENCY_IGNORE);
elog(ERROR, "unrecognized dependency type '%c' for %s",
foundDep->deptype, getObjectDescription(object));
subflags = 0; /* keep compiler quiet */
diff --git a/src/backend/catalog/pg_depend.c b/src/backend/catalog/pg_depend.c
index 7a0713e..6e71b44 100644
--- a/src/backend/catalog/pg_depend.c
+++ b/src/backend/catalog/pg_depend.c
@@ -65,6 +65,9 @@ recordMultipleDependencies(const ObjectAddress *depender,
bool nulls[Natts_pg_depend];
Datum values[Natts_pg_depend];
+ if (behavior == DEPENDENCY_IGNORE)
+ return; /* nothing to do */
+
if (nreferenced <= 0)
return; /* nothing to do */
diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h
index 09b36c5..d44edc6 100644
--- a/src/include/catalog/dependency.h
+++ b/src/include/catalog/dependency.h
@@ -67,6 +67,11 @@
* created only during initdb. The fields for the dependent object
* contain zeroes.
*
+ * DEPENDENCY_IGNORE ('g'): like DEPENDENCY_PIN, there is no dependent
+ * object; this type of entry is a signal that no dependency should be
+ * created between the objects in question. However, unlike pin,
+ * dependencies, these never make it to pg_depend.
+ *
* Other dependency flavors may be needed in future.
*/
@@ -77,7 +82,8 @@ typedef enum DependencyType
DEPENDENCY_INTERNAL = 'i',
DEPENDENCY_EXTENSION = 'e',
DEPENDENCY_AUTO_EXTENSION = 'x',
- DEPENDENCY_PIN = 'p'
+ DEPENDENCY_PIN = 'p',
+ DEPENDENCY_IGNORE = 'g'
} DependencyType;
/*
--
1.7.1
[text/x-diff] 0003-Infrastructure-for-creation-of-partitioned-tables-v7.patch (87.4K, ../../81371428-bb4b-1e33-5ad6-8c5c51b52cb7@lab.ntt.co.jp/4-0003-Infrastructure-for-creation-of-partitioned-tables-v7.patch)
download | inline diff:
From 3d4e0a0d0d9707226fa8f96dc629476424ff7b0c Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Mon, 22 Feb 2016 17:38:55 +0900
Subject: [PATCH 3/6] Infrastructure for creation of partitioned tables.
First, this adds a new system catalog pg_partitioned with:
partedrelid oid
partstrat char
partnatts int16
partattrs int2vector
partclass oidvector
partexprs pg_node_tree
There is a unique index on partedrelid and associated syscache PARTEDRELID.
Second, a new relkind RELKIND_PARTITIONED_REL is added to pg_class. Tables
created with CREATE TABLE PARTITION BY are of this relkind. Some special
properties:
* Has a pg_partitioned entry
* Access to them unconditionally overrides specified inheritance option
* Dropping or altering type of columns that are part of the partition key
is disallowed
* Changing inheritance (ALTER TABLE table INHERIT parent) and linking to
a type (ALTER TABLE table OF type) is disallowed
Partition key is represented using struct PartitionKey and built during the
first relcache build of a partitioned relation. It is stored in rd_partkey
using the cache context.
---
doc/src/sgml/catalogs.sgml | 99 ++++++
src/backend/access/common/reloptions.c | 2 +
src/backend/catalog/Makefile | 4 +-
src/backend/catalog/aclchk.c | 2 +
src/backend/catalog/heap.c | 19 +-
src/backend/catalog/objectaddress.c | 10 +-
src/backend/catalog/partition.c | 457 ++++++++++++++++++++++++++++
src/backend/commands/analyze.c | 2 +
src/backend/commands/copy.c | 6 +
src/backend/commands/indexcmds.c | 3 +-
src/backend/commands/lockcmds.c | 2 +-
src/backend/commands/policy.c | 2 +-
src/backend/commands/seclabel.c | 1 +
src/backend/commands/sequence.c | 1 +
src/backend/commands/tablecmds.c | 171 ++++++++++-
src/backend/commands/trigger.c | 7 +-
src/backend/commands/vacuum.c | 1 +
src/backend/executor/execMain.c | 2 +
src/backend/executor/nodeModifyTable.c | 1 +
src/backend/parser/parse_agg.c | 6 +-
src/backend/parser/parse_clause.c | 8 +
src/backend/parser/parse_expr.c | 2 +-
src/backend/parser/parse_relation.c | 6 +-
src/backend/parser/parse_utilcmd.c | 4 +-
src/backend/rewrite/rewriteDefine.c | 1 +
src/backend/rewrite/rewriteHandler.c | 1 +
src/backend/tcop/utility.c | 5 +-
src/backend/utils/adt/ruleutils.c | 147 +++++++++
src/backend/utils/cache/relcache.c | 14 +-
src/backend/utils/cache/syscache.c | 12 +
src/bin/pg_dump/pg_dump.c | 36 ++-
src/bin/pg_dump/pg_dump.h | 1 +
src/bin/psql/describe.c | 61 +++-
src/bin/psql/tab-complete.c | 6 +-
src/include/catalog/indexing.h | 3 +
src/include/catalog/partition.h | 56 ++++
src/include/catalog/pg_class.h | 1 +
src/include/catalog/pg_partitioned.h | 67 ++++
src/include/catalog/pg_proc.h | 2 +
src/include/commands/defrem.h | 4 +-
src/include/utils/builtins.h | 1 +
src/include/utils/rel.h | 8 +
src/include/utils/syscache.h | 1 +
src/test/regress/expected/create_table.out | 30 ++-
src/test/regress/expected/sanity_check.out | 1 +
src/test/regress/sql/create_table.sql | 21 ++
46 files changed, 1239 insertions(+), 58 deletions(-)
create mode 100644 src/backend/catalog/partition.c
create mode 100644 src/include/catalog/partition.h
create mode 100644 src/include/catalog/pg_partitioned.h
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 18e5fae..dfc55ec 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -226,6 +226,11 @@
</row>
<row>
+ <entry><link linkend="catalog-pg-partitioned"><structname>pg_partitioned</structname></link></entry>
+ <entry>information about partitioned tables, including the partition key</entry>
+ </row>
+
+ <row>
<entry><link linkend="catalog-pg-policy"><structname>pg_policy</structname></link></entry>
<entry>row-security policies</entry>
</row>
@@ -4668,6 +4673,100 @@
</sect1>
+ <sect1 id="catalog-pg-partitioned">
+ <title><structname>pg_partitioned</structname></title>
+
+ <indexterm zone="catalog-pg-partitioned">
+ <primary>pg_partitioned</primary>
+ </indexterm>
+
+ <para>
+ The catalog <structname>pg_partitioned</structname> stores information
+ about the partition key of partitioned tables.
+ </para>
+
+ <table>
+ <title><structname>pg_partitioned</> Columns</title>
+
+ <tgroup cols="4">
+ <thead>
+ <row>
+ <entry>Name</entry>
+ <entry>Type</entry>
+ <entry>References</entry>
+ <entry>Description</entry>
+ </row>
+ </thead>
+
+ <tbody>
+
+ <row>
+ <entry><structfield>partedrelid</structfield></entry>
+ <entry><type>oid</type></entry>
+ <entry><literal><link linkend="catalog-pg-class"><structname>pg_class</structname></link>.oid</literal></entry>
+ <entry>The OID of the <structname>pg_class</> entry for this partitioned table</entry>
+ </row>
+
+ <row>
+ <entry><structfield>partstrat</structfield></entry>
+ <entry><type>char</type></entry>
+ <entry></entry>
+ <entry>
+ Partitioning strategy (or method); <literal>l</> = list partitioned table,
+ <literal>r</> = range partitioned table
+ </entry>
+ </row>
+
+ <row>
+ <entry><structfield>partnatts</structfield></entry>
+ <entry><type>int2</type></entry>
+ <entry></entry>
+ <entry>The number of columns in partition key</entry>
+ </row>
+
+ <row>
+ <entry><structfield>partattrs</structfield></entry>
+ <entry><type>int2vector</type></entry>
+ <entry><literal><link linkend="catalog-pg-attribute"><structname>pg_attribute</structname></link>.attnum</literal></entry>
+ <entry>
+ This is an array of <structfield>partnatts</structfield> values that
+ indicate which table columns are used as partition key. For example,
+ a value of <literal>1 3</literal> would mean that the first and the
+ third table columns make up the partition key. A zero in this array
+ indicates that the corresponding partition key column is an expression
+ over the table columns, rather than a simple column reference.
+ </entry>
+ </row>
+
+ <row>
+ <entry><structfield>partclass</structfield></entry>
+ <entry><type>oidvector</type></entry>
+ <entry><literal><link linkend="catalog-pg-opclass"><structname>pg_opclass</structname></link>.oid</literal></entry>
+ <entry>
+ For each column in the partition key, this contains the OID of
+ the operator class to use. See
+ <link linkend="catalog-pg-opclass"><structname>pg_opclass</structname></link> for details.
+ </entry>
+ </row>
+
+ <row>
+ <entry><structfield>partexprs</structfield></entry>
+ <entry><type>pg_node_tree</type></entry>
+ <entry></entry>
+ <entry>
+ Expression trees (in <function>nodeToString()</function>
+ representation) for partition key columns that are not simple column
+ references. This is a list with one element for each zero
+ entry in <structfield>partkey</>. Null if all partition key columns
+ are simple references.
+ </entry>
+ </row>
+
+ </tbody>
+ </tgroup>
+ </table>
+ </sect1>
+
<sect1 id="catalog-pg-policy">
<title><structname>pg_policy</structname></title>
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index ba1f3aa..4dc4400 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -930,6 +930,7 @@ extractRelOptions(HeapTuple tuple, TupleDesc tupdesc,
case RELKIND_RELATION:
case RELKIND_TOASTVALUE:
case RELKIND_MATVIEW:
+ case RELKIND_PARTITIONED_REL:
options = heap_reloptions(classForm->relkind, datum, false);
break;
case RELKIND_VIEW:
@@ -1381,6 +1382,7 @@ heap_reloptions(char relkind, Datum reloptions, bool validate)
return (bytea *) rdopts;
case RELKIND_RELATION:
case RELKIND_MATVIEW:
+ case RELKIND_PARTITIONED_REL:
return default_reloptions(reloptions, validate, RELOPT_KIND_HEAP);
default:
/* other relkinds are not supported */
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index 1ce7610..4dc6300 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -11,7 +11,7 @@ top_builddir = ../../..
include $(top_builddir)/src/Makefile.global
OBJS = catalog.o dependency.o heap.o index.o indexing.o namespace.o aclchk.o \
- objectaccess.o objectaddress.o pg_aggregate.o pg_collation.o \
+ objectaccess.o objectaddress.o partition.o pg_aggregate.o pg_collation.o \
pg_constraint.o pg_conversion.o \
pg_depend.o pg_enum.o pg_inherits.o pg_largeobject.o pg_namespace.o \
pg_operator.o pg_proc.o pg_range.o pg_db_role_setting.o pg_shdepend.o \
@@ -41,7 +41,7 @@ POSTGRES_BKI_SRCS = $(addprefix $(top_srcdir)/src/include/catalog/,\
pg_foreign_data_wrapper.h pg_foreign_server.h pg_user_mapping.h \
pg_foreign_table.h pg_policy.h pg_replication_origin.h \
pg_default_acl.h pg_init_privs.h pg_seclabel.h pg_shseclabel.h \
- pg_collation.h pg_range.h pg_transform.h \
+ pg_collation.h pg_range.h pg_transform.h pg_partitioned.h\
toasting.h indexing.h \
)
diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
index a585c3a..60554de 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -762,6 +762,8 @@ objectsInSchemaToOids(GrantObjectType objtype, List *nspnames)
case ACL_OBJECT_RELATION:
objs = getRelationsInNamespace(namespaceId, RELKIND_RELATION);
objects = list_concat(objects, objs);
+ objs = getRelationsInNamespace(namespaceId, RELKIND_PARTITIONED_REL);
+ objects = list_concat(objects, objs);
objs = getRelationsInNamespace(namespaceId, RELKIND_VIEW);
objects = list_concat(objects, objs);
objs = getRelationsInNamespace(namespaceId, RELKIND_MATVIEW);
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index e997b57..932a48b 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -41,6 +41,7 @@
#include "catalog/heap.h"
#include "catalog/index.h"
#include "catalog/objectaccess.h"
+#include "catalog/partition.h"
#include "catalog/pg_attrdef.h"
#include "catalog/pg_collation.h"
#include "catalog/pg_constraint.h"
@@ -1101,9 +1102,10 @@ heap_create_with_catalog(const char *relname,
{
/* Use binary-upgrade override for pg_class.oid/relfilenode? */
if (IsBinaryUpgrade &&
- (relkind == RELKIND_RELATION || relkind == RELKIND_SEQUENCE ||
- relkind == RELKIND_VIEW || relkind == RELKIND_MATVIEW ||
- relkind == RELKIND_COMPOSITE_TYPE || relkind == RELKIND_FOREIGN_TABLE))
+ (relkind == RELKIND_RELATION || relkind == RELKIND_PARTITIONED_REL ||
+ relkind == RELKIND_SEQUENCE || relkind == RELKIND_VIEW ||
+ relkind == RELKIND_MATVIEW || relkind == RELKIND_COMPOSITE_TYPE ||
+ relkind == RELKIND_FOREIGN_TABLE))
{
if (!OidIsValid(binary_upgrade_next_heap_pg_class_oid))
ereport(ERROR,
@@ -1134,6 +1136,7 @@ heap_create_with_catalog(const char *relname,
switch (relkind)
{
case RELKIND_RELATION:
+ case RELKIND_PARTITIONED_REL:
case RELKIND_VIEW:
case RELKIND_MATVIEW:
case RELKIND_FOREIGN_TABLE:
@@ -1178,6 +1181,7 @@ heap_create_with_catalog(const char *relname,
* such is an implementation detail: toast tables, sequences and indexes.
*/
if (IsUnderPostmaster && (relkind == RELKIND_RELATION ||
+ relkind == RELKIND_PARTITIONED_REL ||
relkind == RELKIND_VIEW ||
relkind == RELKIND_MATVIEW ||
relkind == RELKIND_FOREIGN_TABLE ||
@@ -1353,7 +1357,8 @@ heap_create_with_catalog(const char *relname,
if (relpersistence == RELPERSISTENCE_UNLOGGED)
{
Assert(relkind == RELKIND_RELATION || relkind == RELKIND_MATVIEW ||
- relkind == RELKIND_TOASTVALUE);
+ relkind == RELKIND_TOASTVALUE || relkind == RELKIND_PARTITIONED_REL);
+
heap_create_init_fork(new_rel_desc);
}
@@ -1800,6 +1805,12 @@ heap_drop_with_catalog(Oid relid)
}
/*
+ * If a partitioned table, delete the pg_partitioned tuples.
+ */
+ if (rel->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+ RemovePartitionKeyByRelId(relid);
+
+ /*
* Schedule unlinking of the relation's physical files at commit.
*/
if (rel->rd_rel->relkind != RELKIND_VIEW &&
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index 8068b82..6c89b08 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -1204,7 +1204,8 @@ get_relation_by_qualified_name(ObjectType objtype, List *objname,
RelationGetRelationName(relation))));
break;
case OBJECT_TABLE:
- if (relation->rd_rel->relkind != RELKIND_RELATION)
+ if (relation->rd_rel->relkind != RELKIND_RELATION &&
+ relation->rd_rel->relkind != RELKIND_PARTITIONED_REL)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not a table",
@@ -3252,6 +3253,10 @@ getRelationDescription(StringInfo buffer, Oid relid)
appendStringInfo(buffer, _("table %s"),
relname);
break;
+ case RELKIND_PARTITIONED_REL:
+ appendStringInfo(buffer, _("partitioned table %s"),
+ relname);
+ break;
case RELKIND_INDEX:
appendStringInfo(buffer, _("index %s"),
relname);
@@ -3708,6 +3713,9 @@ getRelationTypeDescription(StringInfo buffer, Oid relid, int32 objectSubId)
case RELKIND_RELATION:
appendStringInfoString(buffer, "table");
break;
+ case RELKIND_PARTITIONED_REL:
+ appendStringInfoString(buffer, "partitioned table");
+ break;
case RELKIND_INDEX:
appendStringInfoString(buffer, "index");
break;
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
new file mode 100644
index 0000000..3fa4d68
--- /dev/null
+++ b/src/backend/catalog/partition.c
@@ -0,0 +1,457 @@
+/*-------------------------------------------------------------------------
+ *
+ * partition.c
+ * Partitioning related utility functions.
+ *
+ * Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ * src/backend/utils/misc/partition.c
+ *
+ *-------------------------------------------------------------------------
+*/
+
+#include "postgres.h"
+
+#include "access/heapam.h"
+#include "access/htup_details.h"
+#include "access/nbtree.h"
+#include "catalog/dependency.h"
+#include "catalog/indexing.h"
+#include "catalog/objectaddress.h"
+#include "catalog/partition.h"
+#include "catalog/pg_collation.h"
+#include "catalog/pg_opclass.h"
+#include "catalog/pg_partitioned.h"
+#include "catalog/pg_type.h"
+#include "executor/executor.h"
+#include "miscadmin.h"
+#include "nodes/nodeFuncs.h"
+#include "optimizer/clauses.h"
+#include "optimizer/planmain.h"
+#include "storage/lmgr.h"
+#include "utils/array.h"
+#include "utils/builtins.h"
+#include "utils/datum.h"
+#include "utils/memutils.h"
+#include "utils/fmgroids.h"
+#include "utils/inval.h"
+#include "utils/lsyscache.h"
+#include "utils/syscache.h"
+
+static PartitionKey CopyPartitionKey(PartitionKey fromkey);
+static KeyTypeCollInfo *copy_key_type_coll_info(int nkeycols,
+ KeyTypeCollInfo *tcinfo);
+static void free_key_type_coll_info(KeyTypeCollInfo *tcinfo);
+
+/*
+ * StorePartitionKey
+ * Store the partition keys of rel into pg_partitioned catalog
+ */
+void
+StorePartitionKey(Relation rel,
+ char strategy,
+ int16 partnatts,
+ AttrNumber *partattrs,
+ List *partexprs,
+ Oid *partopclass)
+{
+ int i;
+ int2vector *partattrs_vec;
+ oidvector *partopclass_vec;
+ Datum partexprsDatum;
+ Relation pg_partitioned;
+ HeapTuple tuple;
+ Datum values[Natts_pg_partitioned];
+ bool nulls[Natts_pg_partitioned];
+ ObjectAddress myself;
+ ObjectAddress referenced;
+
+ Assert(rel->rd_rel->relkind == RELKIND_PARTITIONED_REL);
+
+ tuple = SearchSysCache1(PARTEDRELID,
+ ObjectIdGetDatum(RelationGetRelid(rel)));
+ /* Cannot already exist */
+ Assert(!HeapTupleIsValid(tuple));
+
+ /*
+ * Copy the partition key, opclass info into arrays (should we
+ * make the caller pass them like this to start with?)
+ */
+ partattrs_vec = buildint2vector(partattrs, partnatts);
+ partopclass_vec = buildoidvector(partopclass, partnatts);
+
+ /* Convert the partition key expressions (if any) to a text datum */
+ if (partexprs)
+ {
+ char *exprsString;
+
+ exprsString = nodeToString(partexprs);
+ partexprsDatum = CStringGetTextDatum(exprsString);
+ pfree(exprsString);
+ }
+ else
+ partexprsDatum = (Datum) 0;
+
+ pg_partitioned = heap_open(PartitionedRelationId, RowExclusiveLock);
+
+ MemSet(nulls, false, sizeof(nulls));
+
+ /* Only this can ever be NULL */
+ if (!partexprsDatum)
+ nulls[Anum_pg_partitioned_partexprs - 1] = true;
+
+ values[Anum_pg_partitioned_partedrelid - 1] = ObjectIdGetDatum(RelationGetRelid(rel));
+ values[Anum_pg_partitioned_partstrat - 1] = CharGetDatum(strategy);
+ values[Anum_pg_partitioned_partnatts - 1] = Int16GetDatum(partnatts);
+ values[Anum_pg_partitioned_partattrs - 1] = PointerGetDatum(partattrs_vec);
+ values[Anum_pg_partitioned_partclass - 1] = PointerGetDatum(partopclass_vec);
+ values[Anum_pg_partitioned_partexprs - 1] = partexprsDatum;
+
+ tuple = heap_form_tuple(RelationGetDescr(pg_partitioned), values, nulls);
+
+ simple_heap_insert(pg_partitioned, tuple);
+
+ /* Update the indexes on pg_partitioned */
+ CatalogUpdateIndexes(pg_partitioned, tuple);
+
+ /* Make this relation dependent on a few things: */
+ myself.classId = RelationRelationId;
+ myself.objectId = RelationGetRelid(rel);;
+ myself.objectSubId = 0;
+
+ /* Operator class per key column */
+ for (i = 0; i < partnatts; i++)
+ {
+ referenced.classId = OperatorClassRelationId;
+ referenced.objectId = partopclass[i];
+ referenced.objectSubId = 0;
+
+ recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+ }
+
+ /*
+ * Things inside key expressions ignoring the vars of the table which
+ * is achieved by asking the dependency-tracking system to ignore any
+ * dependencies to self (table) or more appropriately own attributes.
+ */
+ if (partexprs)
+ recordDependencyOnSingleRelExpr(&myself,
+ (Node *) partexprs,
+ RelationGetRelid(rel),
+ DEPENDENCY_NORMAL,
+ DEPENDENCY_IGNORE);
+ /* Tell world about the key */
+ CacheInvalidateRelcache(rel);
+
+ heap_close(pg_partitioned, RowExclusiveLock);
+ heap_freetuple(tuple);
+}
+
+/*
+ * RemovePartitionKeyByRelId
+ * Remove pg_partitioned entry for a relation
+ */
+void
+RemovePartitionKeyByRelId(Oid relid)
+{
+ Relation rel;
+ HeapTuple tuple;
+
+ rel = heap_open(PartitionedRelationId, RowExclusiveLock);
+
+ tuple = SearchSysCache1(PARTEDRELID, ObjectIdGetDatum(relid));
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "cache lookup failed for partition key of relation %u", relid);
+
+ simple_heap_delete(rel, &tuple->t_self);
+
+ /* Update the indexes on pg_partitioned */
+ CatalogUpdateIndexes(rel, tuple);
+
+ ReleaseSysCache(tuple);
+ heap_close(rel, RowExclusiveLock);
+}
+
+/*
+ * RelationBuildPartitionKey
+ * Build and attach to relcache partition key data of relation
+ *
+ * Note that the partition key data attached to a relcache entry must be
+ * stored CacheMemoryContext to ensure it survives as long as the relcache
+ * entry. But we should be running in a less long-lived working context.
+ * To avoid leaking cache memory if this routine fails partway through,
+ * we build in working memory and then copy the completed structure into
+ * cache memory.
+ */
+void
+RelationBuildPartitionKey(Relation relation)
+{
+ Form_pg_partitioned form;
+ Relation catalog;
+ HeapTuple tuple;
+ bool isnull;
+ int i;
+ PartitionKey key;
+ int2vector *partattrs;
+ oidvector *opclass;
+ KeyTypeCollInfo *tcinfo;
+ ListCell *partexprs_item;
+ Datum datum;
+ MemoryContext oldcxt;
+
+ tuple = SearchSysCache1(PARTEDRELID,
+ ObjectIdGetDatum(RelationGetRelid(relation)));
+ /*
+ * Happens when we have created our pg_class entry but not the
+ * pg_partitioned entry yet.
+ */
+ if (!HeapTupleIsValid(tuple))
+ return;
+
+ form = (Form_pg_partitioned) GETSTRUCT(tuple);
+
+ /* Allocate in the hopefully short-lived working context */
+ key = (PartitionKey) palloc0(sizeof(PartitionKeyData));
+ key->strategy = form->partstrat;
+ key->partnatts = form->partnatts;
+
+ /* Open the catalog for its tuple descriptor */
+ catalog = heap_open(PartitionedRelationId, AccessShareLock);
+ datum = fastgetattr(tuple, Anum_pg_partitioned_partattrs,
+ RelationGetDescr(catalog),
+ &isnull);
+ Assert(!isnull);
+ partattrs = (int2vector *) DatumGetPointer(datum);
+
+ datum = fastgetattr(tuple, Anum_pg_partitioned_partclass,
+ RelationGetDescr(catalog),
+ &isnull);
+ Assert(!isnull);
+ opclass = (oidvector *) DatumGetPointer(datum);
+
+ datum = heap_getattr(tuple,
+ Anum_pg_partitioned_partexprs,
+ RelationGetDescr(catalog),
+ &isnull);
+
+ if (!isnull)
+ {
+ char *exprsString;
+ Node *exprs;
+
+ exprsString = TextDatumGetCString(datum);
+ exprs = stringToNode(exprsString);
+ pfree(exprsString);
+
+ /*
+ * Run the expressions through eval_const_expressions. This is
+ * not just an optimization, but is necessary, because eventually
+ * the planner will be comparing them to similarly-processed qual
+ * clauses, and may fail to detect valid matches without this.
+ * We don't bother with canonicalize_qual, however.
+ */
+ exprs = eval_const_expressions(NULL, (Node *) exprs);
+
+ /* May as well fix opfuncids too */
+ fix_opfuncids((Node *) exprs);
+ key->partexprs = (List *) exprs;
+ }
+
+ key->partattrs = (AttrNumber *) palloc0(key->partnatts * sizeof(AttrNumber));
+ key->partopfamily = (Oid *) palloc0(key->partnatts * sizeof(Oid));
+ key->partopcintype = (Oid *) palloc0(key->partnatts * sizeof(Oid));
+ key->partsupfunc = (FmgrInfo *) palloc0(key->partnatts * sizeof(FmgrInfo));
+
+ /* Gather type and collation info as well */
+ key->tcinfo = tcinfo = (KeyTypeCollInfo *) palloc0(sizeof(KeyTypeCollInfo));
+ tcinfo->typid = (Oid *) palloc0(key->partnatts * sizeof(Oid));
+ tcinfo->typmod = (int32 *) palloc0(key->partnatts * sizeof(int32));
+ tcinfo->typlen = (int16 *) palloc0(key->partnatts * sizeof(int16));
+ tcinfo->typbyval = (bool *) palloc0(key->partnatts * sizeof(bool));
+ tcinfo->typalign = (char *) palloc0(key->partnatts * sizeof(char));
+ tcinfo->typcoll = (Oid *) palloc0(key->partnatts * sizeof(Oid));
+
+ /*
+ * Copy partattrs. Further, determine and store the opfamily and
+ * btree support function per attribute.
+ */
+ partexprs_item = list_head(key->partexprs);
+ for (i = 0; i < key->partnatts; i++)
+ {
+ HeapTuple tuple;
+ AttrNumber attno;
+ Form_pg_opclass form;
+ Oid funcid;
+
+ key->partattrs[i] = attno = partattrs->values[i];
+
+ /* Collect type information */
+ if (attno != InvalidAttrNumber)
+ {
+ tcinfo->typid[i] = relation->rd_att->attrs[attno - 1]->atttypid;
+ tcinfo->typmod[i] = relation->rd_att->attrs[attno - 1]->atttypmod;
+ tcinfo->typcoll[i] = relation->rd_att->attrs[attno - 1]->attcollation;
+ }
+ else
+ {
+ tcinfo->typid[i] = exprType(lfirst(partexprs_item));
+ tcinfo->typmod[i] = exprTypmod(lfirst(partexprs_item));
+ tcinfo->typcoll[i] = exprCollation(lfirst(partexprs_item));
+ partexprs_item = lnext(partexprs_item);
+ }
+ get_typlenbyvalalign(tcinfo->typid[i],
+ &tcinfo->typlen[i],
+ &tcinfo->typbyval[i],
+ &tcinfo->typalign[i]);
+
+ tuple = SearchSysCache1(CLAOID, ObjectIdGetDatum(opclass->values[i]));
+
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "cache lookup failed for opclass %u", opclass->values[i]);
+
+ form = (Form_pg_opclass) GETSTRUCT(tuple);
+ key->partopfamily[i] = form->opcfamily;
+ key->partopcintype[i] = form->opcintype;
+
+ /*
+ * Btree support function is good to cover the list and range
+ * partitionig related needs.
+ */
+ funcid = get_opfamily_proc(form->opcfamily,
+ form->opcintype, form->opcintype,
+ BTORDER_PROC);
+
+ fmgr_info(funcid, &key->partsupfunc[i]);
+ ReleaseSysCache(tuple);
+ }
+
+ ReleaseSysCache(tuple);
+ heap_close(catalog, AccessShareLock);
+
+ /* Success --- now copy to the cache memory */
+ oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
+ relation->rd_partkey = CopyPartitionKey(key);
+ MemoryContextSwitchTo(oldcxt);
+
+ FreePartitionKey(key);
+}
+
+/*
+ * CopyPartitionKey
+ *
+ * The copy is allocated in the current memory context.
+ */
+static PartitionKey
+CopyPartitionKey(PartitionKey fromkey)
+{
+ PartitionKey newkey;
+
+ newkey = (PartitionKey) palloc0(sizeof(PartitionKeyData));
+
+ newkey->strategy = fromkey->strategy;
+ newkey->partnatts = fromkey->partnatts;
+
+ newkey->partattrs = (AttrNumber *)
+ palloc(newkey->partnatts * sizeof(AttrNumber));
+ memcpy(newkey->partattrs, fromkey->partattrs,
+ newkey->partnatts * sizeof(AttrNumber));
+
+ newkey->partopfamily = (Oid *) palloc(newkey->partnatts * sizeof(Oid));
+ memcpy(newkey->partopfamily, fromkey->partopfamily,
+ newkey->partnatts * sizeof(Oid));
+
+ newkey->partopcintype = (Oid *) palloc(newkey->partnatts * sizeof(Oid));
+ memcpy(newkey->partopcintype, fromkey->partopcintype,
+ newkey->partnatts * sizeof(Oid));
+
+ newkey->partsupfunc = (FmgrInfo *)
+ palloc(newkey->partnatts * sizeof(FmgrInfo));
+ memcpy(newkey->partsupfunc, fromkey->partsupfunc,
+ newkey->partnatts * sizeof(FmgrInfo));
+
+ newkey->partexprs = copyObject(fromkey->partexprs);
+ newkey->tcinfo = copy_key_type_coll_info(newkey->partnatts,
+ fromkey->tcinfo);
+
+ return newkey;
+}
+
+/*
+ * copy_key_type_coll_info
+ *
+ * The copy is allocated in the current memory context.
+ */
+static KeyTypeCollInfo *
+copy_key_type_coll_info(int nkeycols, KeyTypeCollInfo *tcinfo)
+{
+ KeyTypeCollInfo *result = (KeyTypeCollInfo *)
+ palloc0(sizeof(KeyTypeCollInfo));
+
+ result->typid = (Oid *) palloc0(nkeycols * sizeof(Oid));
+ memcpy(result->typid, tcinfo->typid, nkeycols * sizeof(Oid));
+
+ result->typmod = (int32 *) palloc0(nkeycols * sizeof(int32));
+ memcpy(result->typmod, tcinfo->typmod, nkeycols * sizeof(int32));
+
+ result->typlen = (int16 *) palloc0(nkeycols * sizeof(int16));
+ memcpy(result->typlen, tcinfo->typlen, nkeycols * sizeof(int16));
+
+ result->typbyval = (bool *) palloc0(nkeycols * sizeof(bool));
+ memcpy(result->typbyval, tcinfo->typbyval, nkeycols * sizeof(bool));
+
+ result->typalign = (char *) palloc0(nkeycols * sizeof(bool));
+ memcpy(result->typalign, tcinfo->typalign, nkeycols * sizeof(char));
+
+ result->typcoll = (Oid *) palloc0(nkeycols * sizeof(Oid));
+ memcpy(result->typcoll, tcinfo->typcoll, nkeycols * sizeof(Oid));
+
+ return result;
+}
+
+/*
+ * free_key_type_info
+ */
+static void
+free_key_type_coll_info(KeyTypeCollInfo *tcinfo)
+{
+ Assert(tcinfo != NULL);
+
+ pfree(tcinfo->typid);
+ pfree(tcinfo->typmod);
+ pfree(tcinfo->typlen);
+ pfree(tcinfo->typbyval);
+ pfree(tcinfo->typalign);
+ pfree(tcinfo->typcoll);
+ pfree(tcinfo);
+}
+
+/*
+ * Does relid have a pg_partitioned entry?
+ */
+bool
+relid_is_partitioned(Oid relid)
+{
+ return SearchSysCacheExists1(PARTEDRELID, ObjectIdGetDatum(relid));
+}
+
+/*
+ * FreePartitionKeys
+ */
+void
+FreePartitionKey(PartitionKey key)
+{
+ if (key == NULL)
+ return;
+
+ pfree(key->partattrs);
+ pfree(key->partopfamily);
+ pfree(key->partopcintype);
+ pfree(key->partsupfunc);
+ if (key->partexprs)
+ pfree(key->partexprs);
+ free_key_type_coll_info(key->tcinfo);
+ pfree(key);
+}
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index 5fcedd7..dd0433b 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -201,6 +201,7 @@ analyze_rel(Oid relid, RangeVar *relation, int options,
* locked the relation.
*/
if (onerel->rd_rel->relkind == RELKIND_RELATION ||
+ onerel->rd_rel->relkind == RELKIND_PARTITIONED_REL ||
onerel->rd_rel->relkind == RELKIND_MATVIEW)
{
/* Regular table, so we'll use the regular row acquisition function */
@@ -1323,6 +1324,7 @@ acquire_inherited_sample_rows(Relation onerel, int elevel,
/* Check table type (MATVIEW can't happen, but might as well allow) */
if (childrel->rd_rel->relkind == RELKIND_RELATION ||
+ childrel->rd_rel->relkind == RELKIND_PARTITIONED_REL ||
childrel->rd_rel->relkind == RELKIND_MATVIEW)
{
/* Regular table, so use the regular row acquisition function */
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index f45b330..53b2226 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -1718,6 +1718,12 @@ BeginCopyTo(Relation rel,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot copy from sequence \"%s\"",
RelationGetRelationName(rel))));
+ else if (rel->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot copy from partitioned table \"%s\"",
+ RelationGetRelationName(rel)),
+ errhint("Try the COPY (SELECT ...) TO variant.")));
else
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 4ef6b80..e8d25e8 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -369,7 +369,8 @@ DefineIndex(Oid relationId,
namespaceId = RelationGetNamespace(rel);
if (rel->rd_rel->relkind != RELKIND_RELATION &&
- rel->rd_rel->relkind != RELKIND_MATVIEW)
+ rel->rd_rel->relkind != RELKIND_MATVIEW &&
+ rel->rd_rel->relkind != RELKIND_PARTITIONED_REL)
{
if (rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
diff --git a/src/backend/commands/lockcmds.c b/src/backend/commands/lockcmds.c
index 175d1f3..62671a8 100644
--- a/src/backend/commands/lockcmds.c
+++ b/src/backend/commands/lockcmds.c
@@ -88,7 +88,7 @@ RangeVarCallbackForLockTable(const RangeVar *rv, Oid relid, Oid oldrelid,
* check */
/* Currently, we only allow plain tables to be locked */
- if (relkind != RELKIND_RELATION)
+ if (relkind != RELKIND_RELATION && relkind != RELKIND_PARTITIONED_REL)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not a table",
diff --git a/src/backend/commands/policy.c b/src/backend/commands/policy.c
index bc2e4af..cadc9e4 100644
--- a/src/backend/commands/policy.c
+++ b/src/backend/commands/policy.c
@@ -88,7 +88,7 @@ RangeVarCallbackForPolicy(const RangeVar *rv, Oid relid, Oid oldrelid,
rv->relname)));
/* Relation type MUST be a table. */
- if (relkind != RELKIND_RELATION)
+ if (relkind != RELKIND_RELATION && relkind != RELKIND_PARTITIONED_REL)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not a table", rv->relname)));
diff --git a/src/backend/commands/seclabel.c b/src/backend/commands/seclabel.c
index 5bd7e12..8bc8638 100644
--- a/src/backend/commands/seclabel.c
+++ b/src/backend/commands/seclabel.c
@@ -107,6 +107,7 @@ ExecSecLabelStmt(SecLabelStmt *stmt)
* are the only relkinds for which pg_dump will dump labels).
*/
if (relation->rd_rel->relkind != RELKIND_RELATION &&
+ relation->rd_rel->relkind != RELKIND_PARTITIONED_REL &&
relation->rd_rel->relkind != RELKIND_VIEW &&
relation->rd_rel->relkind != RELKIND_MATVIEW &&
relation->rd_rel->relkind != RELKIND_COMPOSITE_TYPE &&
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index c98f981..5dcd4cf 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -1467,6 +1467,7 @@ process_owned_by(Relation seqrel, List *owned_by)
/* Must be a regular or foreign table */
if (!(tablerel->rd_rel->relkind == RELKIND_RELATION ||
+ tablerel->rd_rel->relkind == RELKIND_PARTITIONED_REL ||
tablerel->rd_rel->relkind == RELKIND_FOREIGN_TABLE))
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 7c82f39..c0a9e6a 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -29,6 +29,7 @@
#include "catalog/indexing.h"
#include "catalog/namespace.h"
#include "catalog/objectaccess.h"
+#include "catalog/partition.h"
#include "catalog/pg_am.h"
#include "catalog/pg_collation.h"
#include "catalog/pg_constraint.h"
@@ -262,6 +263,12 @@ struct DropRelationCallbackState
bool concurrent;
};
+/* for find_attr_reference_walker */
+typedef struct
+{
+ AttrNumber attnum;
+} find_attr_reference_context;
+
/* Alter table target-type flags for ATSimplePermissions */
#define ATT_TABLE 0x0001
#define ATT_VIEW 0x0002
@@ -438,6 +445,9 @@ static void ComputePartitionAttrs(Oid relid, List *partParams,
AttrNumber *partattrs,
List **partexprs,
Oid *partoplass);
+static bool find_attr_reference_walker(Node *node,
+ find_attr_reference_context *context);
+static bool is_partition_attr(Relation rel, AttrNumber attnum, bool *is_expr);
/* ----------------------------------------------------------------
@@ -601,7 +611,8 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
* affect other relkinds, but it would complicate interpretOidsOption().
*/
localHasOids = interpretOidsOption(stmt->options,
- (relkind == RELKIND_RELATION));
+ (relkind == RELKIND_RELATION ||
+ relkind == RELKIND_PARTITIONED_REL));
descriptor->tdhasoid = (localHasOids || parentOidCount > 0);
/*
@@ -715,9 +726,10 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
AddRelationNewConstraints(rel, rawDefaults, stmt->constraints,
true, true, false);
- /* Process partition key, if any */
+ /* Process and store partition key, if any */
if (stmt->partby)
{
+ int partnatts;
AttrNumber partattrs[PARTITION_MAX_KEYS];
Oid partopclass[PARTITION_MAX_KEYS];
List *partexprs = NIL;
@@ -725,6 +737,10 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
stmt->partby = transformPartitionBy(rel, stmt->partby);
ComputePartitionAttrs(relationId, stmt->partby->partParams,
partattrs, &partexprs, partopclass);
+
+ partnatts = list_length(stmt->partby->partParams);
+ StorePartitionKey(rel, stmt->partby->strategy, partnatts,
+ partattrs, partexprs, partopclass);
}
ObjectAddressSet(address, RelationRelationId, relationId);
@@ -972,7 +988,15 @@ RangeVarCallbackForDropRelation(const RangeVar *rel, Oid relOid, Oid oldRelOid,
return; /* concurrently dropped, so nothing to do */
classform = (Form_pg_class) GETSTRUCT(tuple);
- if (classform->relkind != relkind)
+ /*
+ * Both normal and partitioned tables are dropped using DROP TABLE.
+ * RemoveRelations however never passes RELKIND_PARTITIONED_REL as
+ * relkind for OBJECT_TABLE relations. A mismatch below may have
+ * to do with that. So, check.
+ */
+ if (classform->relkind != relkind &&
+ (relkind == RELKIND_RELATION &&
+ classform->relkind != RELKIND_PARTITIONED_REL))
DropErrorMsgWrongType(rel->relname, classform->relkind, relkind);
/* Allow DROP to either table owner or schema owner */
@@ -1310,7 +1334,8 @@ truncate_check_rel(Relation rel)
AclResult aclresult;
/* Only allow truncate on regular tables */
- if (rel->rd_rel->relkind != RELKIND_RELATION)
+ if (rel->rd_rel->relkind != RELKIND_RELATION &&
+ rel->rd_rel->relkind != RELKIND_PARTITIONED_REL)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not a table",
@@ -1538,8 +1563,16 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
*/
relation = heap_openrv(parent, ShareUpdateExclusiveLock);
+ /* Cannot inherit from partitioned tables */
+ if (relation->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot inherit from partitioned table \"%s\"",
+ parent->relname)));
+
if (relation->rd_rel->relkind != RELKIND_RELATION &&
- relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
+ relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE &&
+ relation->rd_rel->relkind != RELKIND_PARTITIONED_REL)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("inherited relation \"%s\" is not a table or foreign table",
@@ -2179,6 +2212,7 @@ renameatt_check(Oid myrelid, Form_pg_class classform, bool recursing)
* restriction.
*/
if (relkind != RELKIND_RELATION &&
+ relkind != RELKIND_PARTITIONED_REL &&
relkind != RELKIND_VIEW &&
relkind != RELKIND_MATVIEW &&
relkind != RELKIND_COMPOSITE_TYPE &&
@@ -4308,6 +4342,7 @@ ATSimplePermissions(Relation rel, int allowed_targets)
switch (rel->rd_rel->relkind)
{
case RELKIND_RELATION:
+ case RELKIND_PARTITIONED_REL:
actual_target = ATT_TABLE;
break;
case RELKIND_VIEW:
@@ -4544,6 +4579,7 @@ find_composite_type_dependencies(Oid typeOid, Relation origRelation,
att = rel->rd_att->attrs[pg_depend->objsubid - 1];
if (rel->rd_rel->relkind == RELKIND_RELATION ||
+ rel->rd_rel->relkind == RELKIND_PARTITIONED_REL ||
rel->rd_rel->relkind == RELKIND_MATVIEW)
{
if (origTypeName)
@@ -5434,6 +5470,7 @@ ATPrepSetStatistics(Relation rel, const char *colName, Node *newValue, LOCKMODE
* allowSystemTableMods to be turned on.
*/
if (rel->rd_rel->relkind != RELKIND_RELATION &&
+ rel->rd_rel->relkind != RELKIND_PARTITIONED_REL &&
rel->rd_rel->relkind != RELKIND_MATVIEW &&
rel->rd_rel->relkind != RELKIND_INDEX &&
rel->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
@@ -5708,6 +5745,73 @@ ATPrepDropColumn(List **wqueue, Relation rel, bool recurse, bool recursing,
cmd->subtype = AT_DropColumnRecurse;
}
+/* Checks if a Var node is for a given attnum */
+static bool
+find_attr_reference_walker(Node *node, find_attr_reference_context *context)
+{
+ if (node == NULL)
+ return false;
+
+ if (IsA(node, Var))
+ {
+ Var *variable = (Var *) node;
+ AttrNumber attnum = variable->varattno;
+
+ if (attnum == context->attnum)
+ return true;
+ }
+
+ return expression_tree_walker(node, find_attr_reference_walker, context);
+}
+
+/*
+ * Checks if attnum is a partition attribute for rel
+ *
+ * Sets *is_expr if attnum is found to be referenced in some partition key
+ * expression.
+ */
+static bool
+is_partition_attr(Relation rel, AttrNumber attnum, bool *is_expr)
+{
+ PartitionKey key;
+ ListCell *partexpr_item;
+ int i;
+
+ if (rel->rd_rel->relkind != RELKIND_PARTITIONED_REL)
+ {
+ *is_expr = false;
+ return false;
+ }
+
+ key = RelationGetPartitionKey(rel);
+
+ partexpr_item = list_head(key->partexprs);
+ for (i = 0; i < key->partnatts; i++)
+ {
+ AttrNumber partatt = key->partattrs[i];
+
+ if(partatt != 0)
+ {
+ *is_expr = false;
+ if (attnum == partatt)
+ return true;
+ }
+ else
+ {
+ find_attr_reference_context context;
+
+ *is_expr = true;
+ context.attnum = attnum;
+ if (find_attr_reference_walker(lfirst(partexpr_item), &context))
+ return true;
+
+ partexpr_item = lnext(partexpr_item);
+ }
+ }
+
+ return false;
+}
+
/*
* Return value is the address of the dropped column.
*/
@@ -5722,6 +5826,7 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
AttrNumber attnum;
List *children;
ObjectAddress object;
+ bool is_expr;
/* At top level, permission check was done in ATPrepCmd, else do it */
if (recursing)
@@ -5766,6 +5871,19 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
errmsg("cannot drop inherited column \"%s\"",
colName)));
+ /* Don't drop columns used in partition key */
+ if (is_partition_attr(rel, attnum, &is_expr))
+ {
+ if (!is_expr)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("cannot drop column named in partition key")));
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("cannot drop column referenced in partition key expression")));
+ }
+
ReleaseSysCache(tuple);
/*
@@ -6284,6 +6402,12 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
* Validity checks (permission checks wait till we have the column
* numbers)
*/
+ if (pkrel->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot reference relation \"%s\"", RelationGetRelationName(pkrel)),
+ errdetail("Referencing partitioned tables in foreign key constraints is not supported.")));
+
if (pkrel->rd_rel->relkind != RELKIND_RELATION)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
@@ -7878,6 +8002,7 @@ ATPrepAlterColumnType(List **wqueue,
NewColumnValue *newval;
ParseState *pstate = make_parsestate(NULL);
AclResult aclresult;
+ bool is_expr;
if (rel->rd_rel->reloftype && !recursing)
ereport(ERROR,
@@ -7908,6 +8033,19 @@ ATPrepAlterColumnType(List **wqueue,
errmsg("cannot alter inherited column \"%s\"",
colName)));
+ /* Don't alter columns used in partition key */
+ if (is_partition_attr(rel, attnum, &is_expr))
+ {
+ if (!is_expr)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("cannot alter column named in partition key")));
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("cannot alter column referenced in partition key expression")));
+ }
+
/* Look up the target type */
typenameTypeIdAndMod(NULL, typeName, &targettype, &targettypmod);
@@ -7923,7 +8061,8 @@ ATPrepAlterColumnType(List **wqueue,
list_make1_oid(rel->rd_rel->reltype),
false);
- if (tab->relkind == RELKIND_RELATION)
+ if (tab->relkind == RELKIND_RELATION ||
+ tab->relkind == RELKIND_PARTITIONED_REL)
{
/*
* Set up an expression to transform the old data value to the new
@@ -8950,6 +9089,7 @@ ATExecChangeOwner(Oid relationOid, Oid newOwnerId, bool recursing, LOCKMODE lock
switch (tuple_class->relkind)
{
case RELKIND_RELATION:
+ case RELKIND_PARTITIONED_REL:
case RELKIND_VIEW:
case RELKIND_MATVIEW:
case RELKIND_FOREIGN_TABLE:
@@ -9412,6 +9552,7 @@ ATExecSetRelOptions(Relation rel, List *defList, AlterTableType operation,
switch (rel->rd_rel->relkind)
{
case RELKIND_RELATION:
+ case RELKIND_PARTITIONED_REL:
case RELKIND_TOASTVALUE:
case RELKIND_MATVIEW:
(void) heap_reloptions(rel->rd_rel->relkind, newOptions, true);
@@ -9834,7 +9975,8 @@ AlterTableMoveAll(AlterTableMoveAllStmt *stmt)
/* Only move the object type requested */
if ((stmt->objtype == OBJECT_TABLE &&
- relForm->relkind != RELKIND_RELATION) ||
+ relForm->relkind != RELKIND_RELATION &&
+ relForm->relkind != RELKIND_PARTITIONED_REL) ||
(stmt->objtype == OBJECT_INDEX &&
relForm->relkind != RELKIND_INDEX) ||
(stmt->objtype == OBJECT_MATVIEW &&
@@ -10033,6 +10175,11 @@ ATPrepAddInherit(Relation child_rel)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot change inheritance of typed table")));
+
+ if (child_rel->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot change inheritance of partitioned table")));
}
/*
@@ -10084,6 +10231,12 @@ ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode)
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot inherit to temporary relation of another session")));
+ /* Prevent partitioned tables from becoming inheritance parents */
+ if (parent_rel->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot inherit from partitioned table \"%s\"", parent->relname)));
+
/*
* Check for duplicates in the list of parents, and determine the highest
* inhseqno already present; we'll use the next one for the new parent.
@@ -11462,6 +11615,7 @@ AlterTableNamespaceInternal(Relation rel, Oid oldNspOid, Oid nspOid,
/* Fix other dependent stuff */
if (rel->rd_rel->relkind == RELKIND_RELATION ||
+ rel->rd_rel->relkind == RELKIND_PARTITIONED_REL ||
rel->rd_rel->relkind == RELKIND_MATVIEW)
{
AlterIndexNamespaces(classRel, rel, oldNspOid, nspOid, objsMoved);
@@ -11911,7 +12065,7 @@ RangeVarCallbackOwnsTable(const RangeVar *relation,
if (!relkind)
return;
if (relkind != RELKIND_RELATION && relkind != RELKIND_TOASTVALUE &&
- relkind != RELKIND_MATVIEW)
+ relkind != RELKIND_MATVIEW && relkind != RELKIND_PARTITIONED_REL)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not a table or materialized view", relation->relname)));
@@ -12065,6 +12219,7 @@ RangeVarCallbackForAlterRelation(const RangeVar *rv, Oid relid, Oid oldrelid,
*/
if (IsA(stmt, AlterObjectSchemaStmt) &&
relkind != RELKIND_RELATION &&
+ relkind != RELKIND_PARTITIONED_REL &&
relkind != RELKIND_VIEW &&
relkind != RELKIND_MATVIEW &&
relkind != RELKIND_SEQUENCE &&
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index 99a659a..6da6f32 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -174,7 +174,8 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
* Triggers must be on tables or views, and there are additional
* relation-type-specific restrictions.
*/
- if (rel->rd_rel->relkind == RELKIND_RELATION)
+ if (rel->rd_rel->relkind == RELKIND_RELATION ||
+ rel->rd_rel->relkind == RELKIND_PARTITIONED_REL)
{
/* Tables can't have INSTEAD OF triggers */
if (stmt->timing != TRIGGER_TYPE_BEFORE &&
@@ -1112,6 +1113,7 @@ RemoveTriggerById(Oid trigOid)
rel = heap_open(relid, AccessExclusiveLock);
if (rel->rd_rel->relkind != RELKIND_RELATION &&
+ rel->rd_rel->relkind != RELKIND_PARTITIONED_REL &&
rel->rd_rel->relkind != RELKIND_VIEW &&
rel->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
ereport(ERROR,
@@ -1218,7 +1220,8 @@ RangeVarCallbackForRenameTrigger(const RangeVar *rv, Oid relid, Oid oldrelid,
/* only tables and views can have triggers */
if (form->relkind != RELKIND_RELATION && form->relkind != RELKIND_VIEW &&
- form->relkind != RELKIND_FOREIGN_TABLE)
+ form->relkind != RELKIND_FOREIGN_TABLE &&
+ form->relkind != RELKIND_PARTITIONED_REL)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not a table, view, or foreign table",
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 0563e63..804e56f 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -1315,6 +1315,7 @@ vacuum_rel(Oid relid, RangeVar *relation, int options, VacuumParams *params)
* relation.
*/
if (onerel->rd_rel->relkind != RELKIND_RELATION &&
+ onerel->rd_rel->relkind != RELKIND_PARTITIONED_REL &&
onerel->rd_rel->relkind != RELKIND_MATVIEW &&
onerel->rd_rel->relkind != RELKIND_TOASTVALUE)
{
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 32bb3f9..3c73e81 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -1019,6 +1019,7 @@ CheckValidResultRel(Relation resultRel, CmdType operation)
switch (resultRel->rd_rel->relkind)
{
case RELKIND_RELATION:
+ case RELKIND_PARTITIONED_REL:
/* OK */
break;
case RELKIND_SEQUENCE:
@@ -1152,6 +1153,7 @@ CheckValidRowMarkRel(Relation rel, RowMarkType markType)
switch (rel->rd_rel->relkind)
{
case RELKIND_RELATION:
+ case RELKIND_PARTITIONED_REL:
/* OK */
break;
case RELKIND_SEQUENCE:
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index af7b26c..8e2da9c 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1871,6 +1871,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
relkind = resultRelInfo->ri_RelationDesc->rd_rel->relkind;
if (relkind == RELKIND_RELATION ||
+ relkind == RELKIND_PARTITIONED_REL ||
relkind == RELKIND_MATVIEW)
{
j->jf_junkAttNo = ExecFindJunkAttribute(j, "ctid");
diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c
index 6c50a56..3e8d457 100644
--- a/src/backend/parser/parse_agg.c
+++ b/src/backend/parser/parse_agg.c
@@ -503,9 +503,9 @@ check_agglevels_and_constraints(ParseState *pstate, Node *expr)
break;
case EXPR_KIND_PARTITION_KEY:
if (isAgg)
- err = _("aggregate functions are not allowed in partition key expressions");
+ err = _("aggregate functions are not allowed in partition key expression");
else
- err = _("grouping operations are not allowed in partition key expressions");
+ err = _("grouping operations are not allowed in partition key expression");
break;
@@ -867,7 +867,7 @@ transformWindowFuncCall(ParseState *pstate, WindowFunc *wfunc,
err = _("window functions are not allowed in trigger WHEN conditions");
break;
case EXPR_KIND_PARTITION_KEY:
- err = _("window functions are not allowed in partition key expressions");
+ err = _("window functions are not allowed in partition key expression");
break;
/*
diff --git a/src/backend/parser/parse_clause.c b/src/backend/parser/parse_clause.c
index 751de4b..307e383 100644
--- a/src/backend/parser/parse_clause.c
+++ b/src/backend/parser/parse_clause.c
@@ -21,6 +21,7 @@
#include "access/tsmapi.h"
#include "catalog/catalog.h"
#include "catalog/heap.h"
+#include "catalog/partition.h"
#include "catalog/pg_am.h"
#include "catalog/pg_constraint_fn.h"
#include "catalog/pg_type.h"
@@ -200,6 +201,13 @@ setTargetTable(ParseState *pstate, RangeVar *relation,
relation->alias, inh, false);
pstate->p_target_rangetblentry = rte;
+ /*
+ * Override specified inheritance option, if relation is a partitioned
+ * table and it's a target of INSERT (ie, alsoSource == false).
+ */
+ if (alsoSource)
+ rte->inh |= relid_is_partitioned(rte->relid);
+
/* assume new rte is at end */
rtindex = list_length(pstate->p_rtable);
Assert(rte == rt_fetch(rtindex, pstate->p_rtable));
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index b53b7f1..147c6da 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -1724,7 +1724,7 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
err = _("cannot use subquery in trigger WHEN condition");
break;
case EXPR_KIND_PARTITION_KEY:
- err = _("cannot use subquery in partition key expressions");
+ err = _("cannot use subquery in partition key expression");
break;
/*
diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c
index 1e3ecbc..fd4c9bf 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -20,6 +20,7 @@
#include "access/sysattr.h"
#include "catalog/heap.h"
#include "catalog/namespace.h"
+#include "catalog/partition.h"
#include "catalog/pg_type.h"
#include "funcapi.h"
#include "nodes/makefuncs.h"
@@ -1216,9 +1217,12 @@ addRangeTableEntry(ParseState *pstate,
*
* The initial default on access checks is always check-for-READ-access,
* which is the right thing for all except target tables.
+ *
+ * Note: we override the specified inheritance option, if relation is a
+ * partitioned table.
*/
rte->lateral = false;
- rte->inh = inh;
+ rte->inh = inh || relid_is_partitioned(RelationGetRelid(rel));
rte->inFromCl = inFromCl;
rte->requiredPerms = ACL_SELECT;
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 4591215..bce3041 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -33,6 +33,7 @@
#include "catalog/heap.h"
#include "catalog/index.h"
#include "catalog/namespace.h"
+#include "catalog/partition.h"
#include "catalog/pg_am.h"
#include "catalog/pg_collation.h"
#include "catalog/pg_constraint.h"
@@ -821,6 +822,7 @@ transformTableLikeClause(CreateStmtContext *cxt, TableLikeClause *table_like_cla
relation = relation_openrv(table_like_clause->relation, AccessShareLock);
if (relation->rd_rel->relkind != RELKIND_RELATION &&
+ relation->rd_rel->relkind != RELKIND_PARTITIONED_REL &&
relation->rd_rel->relkind != RELKIND_VIEW &&
relation->rd_rel->relkind != RELKIND_MATVIEW &&
relation->rd_rel->relkind != RELKIND_COMPOSITE_TYPE &&
@@ -2577,7 +2579,7 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
cxt.blist = NIL;
cxt.alist = NIL;
cxt.pkey = NULL;
- cxt.ispartitioned = false;
+ cxt.ispartitioned = relid_is_partitioned(relid);
/*
* The only subtypes that currently require parse transformation handling
diff --git a/src/backend/rewrite/rewriteDefine.c b/src/backend/rewrite/rewriteDefine.c
index f82d891..eb71efc 100644
--- a/src/backend/rewrite/rewriteDefine.c
+++ b/src/backend/rewrite/rewriteDefine.c
@@ -260,6 +260,7 @@ DefineQueryRewrite(char *rulename,
* blocks them for users. Don't mention them in the error message.
*/
if (event_relation->rd_rel->relkind != RELKIND_RELATION &&
+ event_relation->rd_rel->relkind != RELKIND_PARTITIONED_REL &&
event_relation->rd_rel->relkind != RELKIND_MATVIEW &&
event_relation->rd_rel->relkind != RELKIND_VIEW)
ereport(ERROR,
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index a22a11e..f429dd6 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -1222,6 +1222,7 @@ rewriteTargetListUD(Query *parsetree, RangeTblEntry *target_rte,
TargetEntry *tle;
if (target_relation->rd_rel->relkind == RELKIND_RELATION ||
+ target_relation->rd_rel->relkind == RELKIND_PARTITIONED_REL ||
target_relation->rd_rel->relkind == RELKIND_MATVIEW)
{
/*
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index ac50c2a..220cbfc 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -975,10 +975,13 @@ ProcessUtilitySlow(Node *parsetree,
{
Datum toast_options;
static char *validnsps[] = HEAP_RELOPT_NAMESPACES;
+ char relkind = ((CreateStmt *) stmt)->partby != NULL
+ ? RELKIND_PARTITIONED_REL
+ : RELKIND_RELATION;
/* Create the table itself */
address = DefineRelation((CreateStmt *) stmt,
- RELKIND_RELATION,
+ relkind,
InvalidOid, NULL);
EventTriggerCollectSimpleCommand(address,
secondaryObject,
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index afc26d4..37f5b5c 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -33,6 +33,7 @@
#include "catalog/pg_language.h"
#include "catalog/pg_opclass.h"
#include "catalog/pg_operator.h"
+#include "catalog/pg_partitioned.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_trigger.h"
#include "catalog/pg_type.h"
@@ -315,6 +316,7 @@ static char *pg_get_indexdef_worker(Oid indexrelid, int colno,
const Oid *excludeOps,
bool attrsOnly, bool showTblSpc,
int prettyFlags);
+static char *pg_get_partkeydef_worker(Oid relId, int prettyFlags);
static char *pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
int prettyFlags);
static text *pg_get_expr_worker(text *expr, Oid relid, const char *relname,
@@ -1297,6 +1299,151 @@ pg_get_indexdef_worker(Oid indexrelid, int colno,
return buf.data;
}
+/*
+ * pg_get_partkeydef
+ *
+ * Returns the partition key specification, ie, the following:
+ *
+ * PARTITION BY { RANGE | LIST } (column [USING opclass_name] [, ...])
+ */
+Datum
+pg_get_partkeydef(PG_FUNCTION_ARGS)
+{
+ Oid relationId = PG_GETARG_OID(0);
+ int prettyFlags;
+
+ prettyFlags = PRETTYFLAG_INDENT;
+ PG_RETURN_TEXT_P(string_to_text(pg_get_partkeydef_worker(relationId,
+ prettyFlags)));
+}
+
+/*
+ * Internal workhorse to decompile a partition key definition.
+ */
+static char *
+pg_get_partkeydef_worker(Oid relId, int prettyFlags)
+{
+ Form_pg_partitioned form;
+ HeapTuple tuple;
+ oidvector *partclass;
+ List *partexprs;
+ ListCell *partexpr_item;
+ List *context;
+ Datum datum;
+ bool isnull;
+ StringInfoData buf;
+ int keyno;
+ char *str;
+ char *sep;
+
+ tuple = SearchSysCache1(PARTEDRELID, ObjectIdGetDatum(relId));
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "cache lookup failed for partition key of %u", relId);
+
+ form = (Form_pg_partitioned) GETSTRUCT(tuple);
+
+ Assert(form->partedrelid == relId);
+
+ /* Must get partclass, and partexprs the hard way */
+ datum = SysCacheGetAttr(PARTEDRELID, tuple,
+ Anum_pg_partitioned_partclass, &isnull);
+ Assert(!isnull);
+ partclass = (oidvector *) DatumGetPointer(datum);
+
+ /*
+ * Get the partition key expressions, if any. (NOTE: we do not use the
+ * relcache versions of the expressions, because we want to display
+ * non-const-folded expressions.)
+ */
+ if (!heap_attisnull(tuple, Anum_pg_partitioned_partexprs))
+ {
+ Datum exprsDatum;
+ bool isnull;
+ char *exprsString;
+
+ exprsDatum = SysCacheGetAttr(PARTEDRELID, tuple,
+ Anum_pg_partitioned_partexprs, &isnull);
+ Assert(!isnull);
+ exprsString = TextDatumGetCString(exprsDatum);
+ partexprs = (List *) stringToNode(exprsString);
+ pfree(exprsString);
+ }
+ else
+ partexprs = NIL;
+
+ partexpr_item = list_head(partexprs);
+ context = deparse_context_for(get_relation_name(relId), relId);
+
+ /*
+ * Start the partition key definition.
+ */
+ initStringInfo(&buf);
+
+ appendStringInfo(&buf, "PARTITION BY ");
+
+ switch (form->partstrat)
+ {
+ case 'l':
+ appendStringInfo(&buf, "LIST");
+ break;
+ case 'r':
+ appendStringInfo(&buf, "RANGE");
+ break;
+ }
+
+ /*
+ * Report the partition key columns
+ */
+ appendStringInfo(&buf, " (");
+ sep = "";
+ for (keyno = 0; keyno < form->partnatts; keyno++)
+ {
+ AttrNumber attnum = form->partattrs.values[keyno];
+ Oid keycoltype;
+
+ appendStringInfoString(&buf, sep);
+ sep = ", ";
+ if (attnum != 0)
+ {
+ /* Simple partition key column */
+ char *attname;
+
+ attname = get_relid_attribute_name(relId, attnum);
+ appendStringInfoString(&buf, quote_identifier(attname));
+ keycoltype = get_atttype(relId, attnum);
+ }
+ else
+ {
+ /* partition key expression */
+ Node *partkey;
+
+ if (partexpr_item == NULL)
+ elog(ERROR, "too few entries in partexprs list");
+ partkey = (Node *) lfirst(partexpr_item);
+ partexpr_item = lnext(partexpr_item);
+ /* Deparse */
+ str = deparse_expression_pretty(partkey, context, false, false,
+ prettyFlags, 0);
+
+ /* Need parens if it's not a bare function call */
+ if (partkey && IsA(partkey, FuncExpr) &&
+ ((FuncExpr *) partkey)->funcformat == COERCE_EXPLICIT_CALL)
+ appendStringInfoString(&buf, str);
+ else
+ appendStringInfo(&buf, "(%s)", str);
+ keycoltype = exprType(partkey);
+ }
+
+ /* Add the operator class name, if not default */
+ get_opclass_name(partclass->values[keyno], keycoltype, &buf);
+ }
+ appendStringInfoChar(&buf, ')');
+
+ /* Clean up */
+ ReleaseSysCache(tuple);
+
+ return buf.data;
+}
/*
* pg_get_constraintdef
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 8d2ad01..3787441 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -40,6 +40,7 @@
#include "catalog/index.h"
#include "catalog/indexing.h"
#include "catalog/namespace.h"
+#include "catalog/partition.h"
#include "catalog/pg_am.h"
#include "catalog/pg_amproc.h"
#include "catalog/pg_attrdef.h"
@@ -431,6 +432,7 @@ RelationParseRelOptions(Relation relation, HeapTuple tuple)
switch (relation->rd_rel->relkind)
{
case RELKIND_RELATION:
+ case RELKIND_PARTITIONED_REL:
case RELKIND_TOASTVALUE:
case RELKIND_INDEX:
case RELKIND_VIEW:
@@ -1053,6 +1055,12 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
relation->rd_fkeylist = NIL;
relation->rd_fkeyvalid = false;
+ /* if it's a partitioned table, initialize key info */
+ if (relation->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+ RelationBuildPartitionKey(relation);
+ else
+ relation->rd_partkey = NULL;
+
/*
* if it's an index, initialize index-related information
*/
@@ -2050,6 +2058,7 @@ RelationDestroyRelation(Relation relation, bool remember_tupdesc)
MemoryContextDelete(relation->rd_rulescxt);
if (relation->rd_rsdesc)
MemoryContextDelete(relation->rd_rsdesc->rscxt);
+ FreePartitionKey(relation->rd_partkey);
if (relation->rd_fdwroutine)
pfree(relation->rd_fdwroutine);
pfree(relation);
@@ -2991,7 +3000,9 @@ RelationBuildLocalRelation(const char *relname,
/* system relations and non-table objects don't have one */
if (!IsSystemNamespace(relnamespace) &&
- (relkind == RELKIND_RELATION || relkind == RELKIND_MATVIEW))
+ (relkind == RELKIND_RELATION ||
+ relkind == RELKIND_PARTITIONED_REL ||
+ relkind == RELKIND_MATVIEW))
rel->rd_rel->relreplident = REPLICA_IDENTITY_DEFAULT;
else
rel->rd_rel->relreplident = REPLICA_IDENTITY_NOTHING;
@@ -5045,6 +5056,7 @@ load_relcache_init_file(bool shared)
rel->rd_rulescxt = NULL;
rel->trigdesc = NULL;
rel->rd_rsdesc = NULL;
+ rel->rd_partkey = NULL;
rel->rd_indexprs = NIL;
rel->rd_indpred = NIL;
rel->rd_exclops = NULL;
diff --git a/src/backend/utils/cache/syscache.c b/src/backend/utils/cache/syscache.c
index 65ffe84..a6563fe 100644
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -48,6 +48,7 @@
#include "catalog/pg_opclass.h"
#include "catalog/pg_operator.h"
#include "catalog/pg_opfamily.h"
+#include "catalog/pg_partitioned.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_range.h"
#include "catalog/pg_rewrite.h"
@@ -568,6 +569,17 @@ static const struct cachedesc cacheinfo[] = {
},
8
},
+ {PartitionedRelationId, /* PARTEDRELID */
+ PartitionedRelidIndexId,
+ 1,
+ {
+ Anum_pg_partitioned_partedrelid,
+ 0,
+ 0,
+ 0
+ },
+ 128
+ },
{ProcedureRelationId, /* PROCNAMEARGSNSP */
ProcedureNameArgsNspIndexId,
3,
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 08c2b0c..16d8d33 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -1253,9 +1253,10 @@ expand_table_name_patterns(Archive *fout,
"SELECT c.oid"
"\nFROM pg_catalog.pg_class c"
"\n LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace"
- "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c')\n",
+ "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c')\n",
RELKIND_RELATION, RELKIND_SEQUENCE, RELKIND_VIEW,
- RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+ RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+ RELKIND_PARTITIONED_REL);
processSQLNamePattern(GetConnection(fout), query, cell->val, true,
false, "n.nspname", "c.relname", NULL,
"pg_catalog.pg_table_is_visible(c.oid)");
@@ -2117,6 +2118,9 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo, bool oids)
/* Skip FOREIGN TABLEs (no data to dump) */
if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
return;
+ /* Skip partitioned tables (data in partitions) */
+ if (tbinfo->relkind == RELKIND_PARTITIONED_REL)
+ return;
/* Don't dump data in unlogged tables, if so requested */
if (tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED &&
@@ -5195,6 +5199,7 @@ getTables(Archive *fout, int *numTables)
int i_reloftype;
int i_relpages;
int i_changed_acl;
+ int i_partkeydef;
/* Make sure we are in proper schema */
selectSourceSchema(fout, "pg_catalog");
@@ -5280,7 +5285,8 @@ getTables(Archive *fout, int *numTables)
"OR %s IS NOT NULL "
"OR %s IS NOT NULL"
"))"
- "AS changed_acl "
+ "AS changed_acl, "
+ "CASE WHEN c.relkind = 'P' THEN pg_catalog.pg_get_partkeydef(c.oid) ELSE NULL END AS partkeydef "
"FROM pg_class c "
"LEFT JOIN pg_depend d ON "
"(c.relkind = '%c' AND "
@@ -5292,7 +5298,7 @@ getTables(Archive *fout, int *numTables)
"(c.oid = pip.objoid "
"AND pip.classoid = 'pg_class'::regclass "
"AND pip.objsubid = 0) "
- "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c') "
+ "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c', '%c') "
"ORDER BY c.oid",
acl_subquery->data,
racl_subquery->data,
@@ -5306,7 +5312,8 @@ getTables(Archive *fout, int *numTables)
RELKIND_SEQUENCE,
RELKIND_RELATION, RELKIND_SEQUENCE,
RELKIND_VIEW, RELKIND_COMPOSITE_TYPE,
- RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+ RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+ RELKIND_PARTITIONED_REL);
destroyPQExpBuffer(acl_subquery);
destroyPQExpBuffer(racl_subquery);
@@ -5868,6 +5875,7 @@ getTables(Archive *fout, int *numTables)
i_toastreloptions = PQfnumber(res, "toast_reloptions");
i_reloftype = PQfnumber(res, "reloftype");
i_changed_acl = PQfnumber(res, "changed_acl");
+ i_partkeydef = PQfnumber(res, "partkeydef");
if (dopt->lockWaitTimeout && fout->remoteVersion >= 70300)
{
@@ -5938,6 +5946,7 @@ getTables(Archive *fout, int *numTables)
else
tblinfo[i].checkoption = pg_strdup(PQgetvalue(res, i, i_checkoption));
tblinfo[i].toast_reloptions = pg_strdup(PQgetvalue(res, i, i_toastreloptions));
+ tblinfo[i].partkeydef = pg_strdup(PQgetvalue(res, i, i_partkeydef));
/* other fields were zeroed above */
@@ -5982,7 +5991,9 @@ getTables(Archive *fout, int *numTables)
* We only need to lock the table for certain components; see
* pg_dump.h
*/
- if (tblinfo[i].dobj.dump && tblinfo[i].relkind == RELKIND_RELATION &&
+ if (tblinfo[i].dobj.dump &&
+ (tblinfo[i].relkind == RELKIND_RELATION ||
+ tblinfo->relkind == RELKIND_PARTITIONED_REL) &&
(tblinfo[i].dobj.dump & DUMP_COMPONENTS_REQUIRING_LOCK))
{
resetPQExpBuffer(query);
@@ -6070,7 +6081,10 @@ getInherits(Archive *fout, int *numInherits)
/* find all the inheritance information */
- appendPQExpBufferStr(query, "SELECT inhrelid, inhparent FROM pg_inherits");
+ appendPQExpBufferStr(query,
+ "SELECT inhrelid, inhparent "
+ "FROM pg_inherits "
+ "WHERE inhparent NOT IN (SELECT oid FROM pg_class WHERE relkind = 'P')");
res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
@@ -15423,6 +15437,9 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
appendPQExpBufferChar(q, ')');
}
+ if (tbinfo->relkind == RELKIND_PARTITIONED_REL)
+ appendPQExpBuffer(q, "\n%s", tbinfo->partkeydef);
+
if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
appendPQExpBuffer(q, "\nSERVER %s", fmtId(srvname));
}
@@ -15483,6 +15500,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
*/
if (dopt->binary_upgrade &&
(tbinfo->relkind == RELKIND_RELATION ||
+ tbinfo->relkind == RELKIND_PARTITIONED_REL ||
tbinfo->relkind == RELKIND_FOREIGN_TABLE))
{
for (j = 0; j < tbinfo->numatts; j++)
@@ -15501,7 +15519,8 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
appendStringLiteralAH(q, fmtId(tbinfo->dobj.name), fout);
appendPQExpBufferStr(q, "::pg_catalog.regclass;\n");
- if (tbinfo->relkind == RELKIND_RELATION)
+ if (tbinfo->relkind == RELKIND_RELATION ||
+ tbinfo->relkind == RELKIND_PARTITIONED_REL)
appendPQExpBuffer(q, "ALTER TABLE ONLY %s ",
fmtId(tbinfo->dobj.name));
else
@@ -15718,6 +15737,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
* dump properties we only have ALTER TABLE syntax for
*/
if ((tbinfo->relkind == RELKIND_RELATION ||
+ tbinfo->relkind == RELKIND_PARTITIONED_REL ||
tbinfo->relkind == RELKIND_MATVIEW) &&
tbinfo->relreplident != REPLICA_IDENTITY_DEFAULT)
{
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 2bfa2d9..0292859 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -310,6 +310,7 @@ typedef struct _tableInfo
bool *inhNotNull; /* true if NOT NULL is inherited */
struct _attrDefInfo **attrdefs; /* DEFAULT expressions */
struct _constraintInfo *checkexprs; /* CHECK constraints */
+ char *partkeydef; /* partition key definition */
/*
* Stuff computed only for dumpable tables.
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 2cdc5ac..71d6cdc 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -828,6 +828,7 @@ permissionsList(const char *pattern)
" WHEN 'm' THEN '%s'"
" WHEN 'S' THEN '%s'"
" WHEN 'f' THEN '%s'"
+ " WHEN 'P' THEN '%s'"
" END as \"%s\",\n"
" ",
gettext_noop("Schema"),
@@ -837,6 +838,7 @@ permissionsList(const char *pattern)
gettext_noop("materialized view"),
gettext_noop("sequence"),
gettext_noop("foreign table"),
+ gettext_noop("partitioned table"),
gettext_noop("Type"));
printACLColumn(&buf, "c.relacl");
@@ -883,7 +885,7 @@ permissionsList(const char *pattern)
appendPQExpBufferStr(&buf, "\nFROM pg_catalog.pg_class c\n"
" LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace\n"
- "WHERE c.relkind IN ('r', 'v', 'm', 'S', 'f')\n");
+ "WHERE c.relkind IN ('r', 'v', 'm', 'S', 'f', 'P')\n");
/*
* Unless a schema pattern is specified, we suppress system and temp
@@ -1527,8 +1529,8 @@ describeOneTableDetails(const char *schemaname,
* types, and foreign tables (c.f. CommentObject() in comment.c).
*/
if (tableinfo.relkind == 'r' || tableinfo.relkind == 'v' ||
- tableinfo.relkind == 'm' ||
- tableinfo.relkind == 'f' || tableinfo.relkind == 'c')
+ tableinfo.relkind == 'm' || tableinfo.relkind == 'f' ||
+ tableinfo.relkind == 'c' || tableinfo.relkind == 'P')
appendPQExpBufferStr(&buf, ", pg_catalog.col_description(a.attrelid, a.attnum)");
}
@@ -1593,6 +1595,14 @@ describeOneTableDetails(const char *schemaname,
printfPQExpBuffer(&title, _("Foreign table \"%s.%s\""),
schemaname, relationname);
break;
+ case 'P':
+ if (tableinfo.relpersistence == 'u')
+ printfPQExpBuffer(&title, _("Unlogged partitioned table \"%s.%s\""),
+ schemaname, relationname);
+ else
+ printfPQExpBuffer(&title, _("Partitioned table \"%s.%s\""),
+ schemaname, relationname);
+ break;
default:
/* untranslated unknown relkind */
printfPQExpBuffer(&title, "?%c? \"%s.%s\"",
@@ -1606,8 +1616,8 @@ describeOneTableDetails(const char *schemaname,
cols = 2;
if (tableinfo.relkind == 'r' || tableinfo.relkind == 'v' ||
- tableinfo.relkind == 'm' ||
- tableinfo.relkind == 'f' || tableinfo.relkind == 'c')
+ tableinfo.relkind == 'm' || tableinfo.relkind == 'f' ||
+ tableinfo.relkind == 'c' || tableinfo.relkind == 'P')
{
show_modifiers = true;
headers[cols++] = gettext_noop("Modifiers");
@@ -1627,12 +1637,12 @@ describeOneTableDetails(const char *schemaname,
{
headers[cols++] = gettext_noop("Storage");
if (tableinfo.relkind == 'r' || tableinfo.relkind == 'm' ||
- tableinfo.relkind == 'f')
+ tableinfo.relkind == 'f' || tableinfo.relkind == 'P')
headers[cols++] = gettext_noop("Stats target");
/* Column comments, if the relkind supports this feature. */
if (tableinfo.relkind == 'r' || tableinfo.relkind == 'v' ||
- tableinfo.relkind == 'm' ||
- tableinfo.relkind == 'c' || tableinfo.relkind == 'f')
+ tableinfo.relkind == 'm' || tableinfo.relkind == 'c' ||
+ tableinfo.relkind == 'f' || tableinfo.relkind == 'P')
headers[cols++] = gettext_noop("Description");
}
@@ -1732,7 +1742,7 @@ describeOneTableDetails(const char *schemaname,
/* Statistics target, if the relkind supports this feature */
if (tableinfo.relkind == 'r' || tableinfo.relkind == 'm' ||
- tableinfo.relkind == 'f')
+ tableinfo.relkind == 'f' || tableinfo.relkind == 'P')
{
printTableAddCell(&cont, PQgetvalue(res, i, firstvcol + 1),
false, false);
@@ -1740,14 +1750,33 @@ describeOneTableDetails(const char *schemaname,
/* Column comments, if the relkind supports this feature. */
if (tableinfo.relkind == 'r' || tableinfo.relkind == 'v' ||
- tableinfo.relkind == 'm' ||
- tableinfo.relkind == 'c' || tableinfo.relkind == 'f')
+ tableinfo.relkind == 'm' || tableinfo.relkind == 'c' ||
+ tableinfo.relkind == 'f' || tableinfo.relkind == 'P')
printTableAddCell(&cont, PQgetvalue(res, i, firstvcol + 2),
false, false);
}
}
/* Make footers */
+ if (pset.sversion >= 90600 && tableinfo.relkind == 'P')
+ {
+ /* Get the partition key information */
+ PGresult *result;
+ char *partkeydef;
+
+ printfPQExpBuffer(&buf,
+ "SELECT pg_catalog.pg_get_partkeydef('%s'::pg_catalog.oid);",
+ oid);
+ result = PSQLexec(buf.data);
+ if (!result || PQntuples(result) != 1)
+ goto error_return;
+
+ partkeydef = PQgetvalue(result, 0, 0);
+ printfPQExpBuffer(&tmpbuf, _("Partition Key: %s"), partkeydef);
+ printTableAddFooter(&cont, tmpbuf.data);
+ PQclear(result);
+ }
+
if (tableinfo.relkind == 'i')
{
/* Footer information about an index */
@@ -1886,7 +1915,7 @@ describeOneTableDetails(const char *schemaname,
PQclear(result);
}
else if (tableinfo.relkind == 'r' || tableinfo.relkind == 'm' ||
- tableinfo.relkind == 'f')
+ tableinfo.relkind == 'f' || tableinfo.relkind == 'P')
{
/* Footer information about a table */
PGresult *result = NULL;
@@ -2445,7 +2474,7 @@ describeOneTableDetails(const char *schemaname,
* Finish printing the footer information about a table.
*/
if (tableinfo.relkind == 'r' || tableinfo.relkind == 'm' ||
- tableinfo.relkind == 'f')
+ tableinfo.relkind == 'f' || tableinfo.relkind == 'P')
{
PGresult *result;
int tuples;
@@ -2656,7 +2685,7 @@ add_tablespace_footer(printTableContent *const cont, char relkind,
Oid tablespace, const bool newline)
{
/* relkinds for which we support tablespaces */
- if (relkind == 'r' || relkind == 'm' || relkind == 'i')
+ if (relkind == 'r' || relkind == 'm' || relkind == 'i' || relkind == 'P')
{
/*
* We ignore the database default tablespace so that users not using
@@ -2990,6 +3019,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
" WHEN 'S' THEN '%s'"
" WHEN 's' THEN '%s'"
" WHEN 'f' THEN '%s'"
+ " WHEN 'P' THEN '%s'"
" END as \"%s\",\n"
" pg_catalog.pg_get_userbyid(c.relowner) as \"%s\"",
gettext_noop("Schema"),
@@ -3001,6 +3031,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
gettext_noop("sequence"),
gettext_noop("special"),
gettext_noop("foreign table"),
+ gettext_noop("partitioned table"),
gettext_noop("Type"),
gettext_noop("Owner"));
@@ -3039,7 +3070,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
appendPQExpBufferStr(&buf, "\nWHERE c.relkind IN (");
if (showTables)
- appendPQExpBufferStr(&buf, "'r',");
+ appendPQExpBufferStr(&buf, "'r', 'P',");
if (showViews)
appendPQExpBufferStr(&buf, "'v',");
if (showMatViews)
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 8469d9f..cd4eb7c 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -427,7 +427,7 @@ static const SchemaQuery Query_for_list_of_tables = {
/* catname */
"pg_catalog.pg_class c",
/* selcondition */
- "c.relkind IN ('r')",
+ "c.relkind IN ('r', 'P')",
/* viscondition */
"pg_catalog.pg_table_is_visible(c.oid)",
/* namespace */
@@ -458,7 +458,7 @@ static const SchemaQuery Query_for_list_of_updatables = {
/* catname */
"pg_catalog.pg_class c",
/* selcondition */
- "c.relkind IN ('r', 'f', 'v')",
+ "c.relkind IN ('r', 'f', 'v', 'P')",
/* viscondition */
"pg_catalog.pg_table_is_visible(c.oid)",
/* namespace */
@@ -488,7 +488,7 @@ static const SchemaQuery Query_for_list_of_tsvmf = {
/* catname */
"pg_catalog.pg_class c",
/* selcondition */
- "c.relkind IN ('r', 'S', 'v', 'm', 'f')",
+ "c.relkind IN ('r', 'S', 'v', 'm', 'f', 'P')",
/* viscondition */
"pg_catalog.pg_table_is_visible(c.oid)",
/* namespace */
diff --git a/src/include/catalog/indexing.h b/src/include/catalog/indexing.h
index ca5eb3d..cadb741 100644
--- a/src/include/catalog/indexing.h
+++ b/src/include/catalog/indexing.h
@@ -319,6 +319,9 @@ DECLARE_UNIQUE_INDEX(pg_replication_origin_roiident_index, 6001, on pg_replicati
DECLARE_UNIQUE_INDEX(pg_replication_origin_roname_index, 6002, on pg_replication_origin using btree(roname text_pattern_ops));
#define ReplicationOriginNameIndex 6002
+DECLARE_UNIQUE_INDEX(pg_partitioned_partedrelid_index, 3351, on pg_partitioned using btree(partedrelid oid_ops));
+#define PartitionedRelidIndexId 3351
+
/* last step of initialization script: build the indexes declared above */
BUILD_INDICES
diff --git a/src/include/catalog/partition.h b/src/include/catalog/partition.h
new file mode 100644
index 0000000..e1d8030
--- /dev/null
+++ b/src/include/catalog/partition.h
@@ -0,0 +1,56 @@
+/*-------------------------------------------------------------------------
+ *
+ * partition.h
+ * Header file for structures and utility functions related to
+ * partitioning
+ *
+ * Copyright (c) 2007-2016, PostgreSQL Global Development Group
+ *
+ * src/include/utils/partition.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PARTITION_H
+#define PARTITION_H
+
+#include "fmgr.h"
+#include "utils/relcache.h"
+
+/* Type and collation information for partition key columns */
+typedef struct
+{
+ Oid *typid;
+ int32 *typmod;
+ int16 *typlen;
+ bool *typbyval;
+ char *typalign;
+ Oid *typcoll;
+} KeyTypeCollInfo;
+
+/* Partition key information for one level */
+typedef struct PartitionKeyData
+{
+ char strategy; /* partition strategy */
+ int16 partnatts; /* number of partition attributes */
+ AttrNumber *partattrs; /* partition attnums */
+ Oid *partopfamily; /* OIDs of operator families */
+ Oid *partopcintype; /* OIDs of opclass declared input data types */
+ FmgrInfo *partsupfunc; /* lookup info for support funcs */
+ List *partexprs; /* partition key expressions, if any */
+ KeyTypeCollInfo *tcinfo; /* type and collation info (all columns) */
+} PartitionKeyData;
+
+typedef struct PartitionKeyData *PartitionKey;
+
+extern void StorePartitionKey(Relation rel,
+ char strategy,
+ int16 partnatts,
+ AttrNumber *partattrs,
+ List *partexprs,
+ Oid *partopclass);
+extern void RemovePartitionKeyByRelId(Oid relid);
+extern void RelationBuildPartitionKey(Relation relation);
+extern void FreePartitionKey(PartitionKey key);
+extern bool relid_is_partitioned(Oid relid);
+
+#endif /* PARTITION_H */
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index e57b81c..57c7ca4 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -154,6 +154,7 @@ DESCR("");
#define RELKIND_RELATION 'r' /* ordinary table */
+#define RELKIND_PARTITIONED_REL 'P' /* partitioned table */
#define RELKIND_INDEX 'i' /* secondary index */
#define RELKIND_SEQUENCE 'S' /* sequence object */
#define RELKIND_TOASTVALUE 't' /* for out-of-line values */
diff --git a/src/include/catalog/pg_partitioned.h b/src/include/catalog/pg_partitioned.h
new file mode 100644
index 0000000..edc52db
--- /dev/null
+++ b/src/include/catalog/pg_partitioned.h
@@ -0,0 +1,67 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_partitioned.h
+ * definition of the system "partitioned" relation (pg_partitioned)
+ * along with the relation's initial contents.
+ *
+ *
+ * Portions Copyright (c) 1996-2015, PostgreSQL Global Development Group
+ *
+ * $PostgreSQL: pgsql/src/include/catalog/pg_partitioned.h $
+ *
+ * NOTES
+ * the genbki.sh script reads this file and generates .bki
+ * information from the DATA() statements.
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_PARTITIONED_REL_H
+#define PG_PARTITIONED_REL_H
+
+#include "catalog/genbki.h"
+
+/* ----------------
+ * pg_partitioned definition. cpp turns this into
+ * typedef struct FormData_pg_partitioned
+ * ----------------
+ */
+#define PartitionedRelationId 3350
+
+CATALOG(pg_partitioned,3350) BKI_WITHOUT_OIDS
+{
+ Oid partedrelid; /* partitioned table oid */
+ char partstrat; /* partition key strategy */
+ int16 partnatts; /* number of partition key columns */
+
+ /* variable-length fields start here, but we allow direct access to partattrs */
+ int2vector partattrs; /* attribute numbers of partition key
+ * columns */
+
+#ifdef CATALOG_VARLEN
+ oidvector partclass; /* operator class to compare keys */
+ pg_node_tree partexprs; /* expression trees for partition key members
+ * that are not simple column references; one
+ * for each zero entry in partkey[] */
+#endif
+} FormData_pg_partitioned;
+
+/* ----------------
+ * Form_pg_partitioned corresponds to a pointer to a tuple with
+ * the format of pg_partitioned relation.
+ * ----------------
+ */
+typedef FormData_pg_partitioned *Form_pg_partitioned;
+
+/* ----------------
+ * compiler constants for pg_partitioned
+ * ----------------
+ */
+#define Natts_pg_partitioned 6
+#define Anum_pg_partitioned_partedrelid 1
+#define Anum_pg_partitioned_partstrat 2
+#define Anum_pg_partitioned_partnatts 3
+#define Anum_pg_partitioned_partattrs 4
+#define Anum_pg_partitioned_partclass 5
+#define Anum_pg_partitioned_partexprs 6
+
+#endif /* PG_PARTITIONED_REL_H */
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index f441025..878522d 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -1969,6 +1969,8 @@ DATA(insert OID = 1642 ( pg_get_userbyid PGNSP PGUID 12 1 0 0 0 f f f f t f
DESCR("role name by OID (with fallback)");
DATA(insert OID = 1643 ( pg_get_indexdef PGNSP PGUID 12 1 0 0 0 f f f f t f s s 1 0 25 "26" _null_ _null_ _null_ _null_ _null_ pg_get_indexdef _null_ _null_ _null_ ));
DESCR("index description");
+DATA(insert OID = 3352 ( pg_get_partkeydef PGNSP PGUID 12 1 0 0 0 f f f f t f s s 1 0 25 "26" _null_ _null_ _null_ _null_ _null_ pg_get_partkeydef _null_ _null_ _null_ ));
+DESCR("partition key description");
DATA(insert OID = 1662 ( pg_get_triggerdef PGNSP PGUID 12 1 0 0 0 f f f f t f s s 1 0 25 "26" _null_ _null_ _null_ _null_ _null_ pg_get_triggerdef _null_ _null_ _null_ ));
DESCR("trigger description");
DATA(insert OID = 1387 ( pg_get_constraintdef PGNSP PGUID 12 1 0 0 0 f f f f t f s s 1 0 25 "26" _null_ _null_ _null_ _null_ _null_ pg_get_constraintdef _null_ _null_ _null_ ));
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index 28024f8..ceeb41f 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -42,8 +42,8 @@ extern bool CheckIndexCompatible(Oid oldId,
List *attributeList,
List *exclusionOpNames);
extern Oid GetDefaultOpClass(Oid type_id, Oid am_id);
-extern Oid GetIndexOpClass(List *opclass, Oid attrType,
- char *accessMethodName, Oid accessMethodId);
+extern Oid GetIndexOpClass(List *opclass, Oid attrType,
+ char *accessMethodName, Oid accessMethodId);
/* commands/functioncmds.c */
extern ObjectAddress CreateFunction(CreateFunctionStmt *stmt, const char *queryString);
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
index 01976a1..027665c 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -720,6 +720,7 @@ extern Datum pg_get_viewdef_wrap(PG_FUNCTION_ARGS);
extern Datum pg_get_viewdef_name(PG_FUNCTION_ARGS);
extern Datum pg_get_viewdef_name_ext(PG_FUNCTION_ARGS);
extern Datum pg_get_indexdef(PG_FUNCTION_ARGS);
+extern Datum pg_get_partkeydef(PG_FUNCTION_ARGS);
extern Datum pg_get_indexdef_ext(PG_FUNCTION_ARGS);
extern Datum pg_get_triggerdef(PG_FUNCTION_ARGS);
extern Datum pg_get_triggerdef_ext(PG_FUNCTION_ARGS);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index ed14442..e05d122 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -94,6 +94,8 @@ typedef struct RelationData
List *rd_fkeylist; /* list of ForeignKeyCacheInfo (see below) */
bool rd_fkeyvalid; /* true if list has been computed */
+ struct PartitionKeyData *rd_partkey; /* partition key, or NULL */
+
/* data managed by RelationGetIndexList: */
List *rd_indexlist; /* list of OIDs of indexes on relation */
Oid rd_oidindex; /* OID of unique index on OID, if any */
@@ -532,6 +534,12 @@ typedef struct ViewOptions
RelationNeedsWAL(relation) && \
!IsCatalogRelation(relation))
+/*
+ * RelationGetPartitionKey
+ * Returns partition key for a relation.
+ */
+#define RelationGetPartitionKey(relation) ((relation)->rd_partkey)
+
/* routines in utils/cache/relcache.c */
extern void RelationIncrementReferenceCount(Relation rel);
extern void RelationDecrementReferenceCount(Relation rel);
diff --git a/src/include/utils/syscache.h b/src/include/utils/syscache.h
index 256615b..e727842 100644
--- a/src/include/utils/syscache.h
+++ b/src/include/utils/syscache.h
@@ -72,6 +72,7 @@ enum SysCacheIdentifier
OPEROID,
OPFAMILYAMNAMENSP,
OPFAMILYOID,
+ PARTEDRELID,
PROCNAMEARGSNSP,
PROCOID,
RANGETYPE,
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index b3a392f..513debf 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -314,9 +314,35 @@ ERROR: cannot use a constant expression as partition key
CREATE TABLE fail_agg_in_key (
a int
) PARTITION BY RANGE ((avg(a)));
-ERROR: aggregate functions are not allowed in partition key expressions
+ERROR: aggregate functions are not allowed in partition key expression
CREATE TABLE fail_window_fun_in_key (
a int,
b int
) PARTITION BY RANGE ((avg(a) OVER (PARTITION BY b)));
-ERROR: window functions are not allowed in partition key expressions
+ERROR: window functions are not allowed in partition key expression
+-- check relkind
+CREATE TABLE check_relkind (
+ a int
+) PARTITION BY RANGE (a);
+SELECT relkind FROM pg_class WHERE relname = 'check_relkind';
+ relkind
+---------
+ P
+(1 row)
+
+-- cannot drop column that is part of the partition key
+CREATE TABLE no_drop_alter_partcol (
+ a int
+) PARTITION BY RANGE (a);
+ALTER TABLE no_drop_alter_partcol DROP COLUMN a;
+ERROR: cannot drop column named in partition key
+ALTER TABLE no_drop_alter_partcol ALTER COLUMN a TYPE char(5);
+ERROR: cannot alter column named in partition key
+CREATE TABLE no_drop_alter_partexpr (
+ a text
+) PARTITION BY RANGE ((substring(a from 1 for 1)));
+ALTER TABLE no_drop_alter_partexpr DROP COLUMN a;
+ERROR: cannot drop column referenced in partition key expression
+ALTER TABLE no_drop_alter_partcol ALTER COLUMN a TYPE char(5);
+ERROR: cannot alter column named in partition key
+DROP TABLE check_relkind, no_drop_alter_partcol, no_drop_alter_partexpr;
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index 1c087a3..b1e473d 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -118,6 +118,7 @@ pg_namespace|t
pg_opclass|t
pg_operator|t
pg_opfamily|t
+pg_partitioned|t
pg_pltemplate|t
pg_policy|t
pg_proc|t
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index de20fff..baa426d 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -325,3 +325,24 @@ CREATE TABLE fail_window_fun_in_key (
a int,
b int
) PARTITION BY RANGE ((avg(a) OVER (PARTITION BY b)));
+
+-- check relkind
+CREATE TABLE check_relkind (
+ a int
+) PARTITION BY RANGE (a);
+SELECT relkind FROM pg_class WHERE relname = 'check_relkind';
+
+-- cannot drop column that is part of the partition key
+CREATE TABLE no_drop_alter_partcol (
+ a int
+) PARTITION BY RANGE (a);
+ALTER TABLE no_drop_alter_partcol DROP COLUMN a;
+ALTER TABLE no_drop_alter_partcol ALTER COLUMN a TYPE char(5);
+
+CREATE TABLE no_drop_alter_partexpr (
+ a text
+) PARTITION BY RANGE ((substring(a from 1 for 1)));
+ALTER TABLE no_drop_alter_partexpr DROP COLUMN a;
+ALTER TABLE no_drop_alter_partcol ALTER COLUMN a TYPE char(5);
+
+DROP TABLE check_relkind, no_drop_alter_partcol, no_drop_alter_partexpr;
--
1.7.1
[text/x-diff] 0004-Add-syntax-to-create-partitions-v7.patch (81.6K, ../../81371428-bb4b-1e33-5ad6-8c5c51b52cb7@lab.ntt.co.jp/5-0004-Add-syntax-to-create-partitions-v7.patch)
download | inline diff:
From ad0c759faddcb2f2833670689c90e40031ae00f8 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Thu, 3 Mar 2016 18:29:15 +0900
Subject: [PATCH 4/6] Add syntax to create partitions.
As follows:
CREATE TABLE name PARTITION OF parent
[ ( { column_name WITH OPTION column_constraint | table_constraint } [, ...] ) ]
partition_bound_spec [ PARTITION BY partition_key_spec ]
CREATE FOREIGN TABLE name PARTITION OF parent
[ ( { column_name WITH OPTION column_constraint | table_constraint } [, ...] ) ]
partition_bound_spec
SERVER server_name [ OPTIONS (...) ]
ALTER TABLE parent ATTACH PARTITION name
partition_bound_spec [ VALIDATE | NO VALIDATE ]
ALTER TABLE parent DETACH PARTITION name
partition_bound_spec refers to either list partition bound of form:
FOR VALUES IN (list-of-expressions)
or range partition bound of form:
FOR VALUES range_spec
where range_spec is:
START lower-bound [ INCLUSIVE | EXCLUSIVE ] END upper-bound [ INCLUSIVE | EXCLUSIVE ]
lower-bound and upper-bound is:
{ ( expression [,...] ) | UNBOUNDED }
In the above, each of lower-bound and upper-bound is either a single
expression for a single column key or list of expressions for a multi-
column key or empty. Note that in the absence of explicit specification,
lower-bound is inclusive and upper-bound is exclusive.
A partition is created as inheritance child of the parent with certain
differences from regular inheritance behavior. For example, when a new
partition is created, it cannot contain its own column definitions or
constraints (unlike CREATE TABLE child(...) INHERITS(parent)). When
"attaching" a table as partition of parent, it cannot contain columns
that are not contained in the parent (unlike ALTER TABLE child INHERIT
parent). No dependency is created between a partition and the parent.
One cannot create regular inheritance children of a partitioned table.
Whereas this commit enables inheritance, it does not yet store partition
bound info anywhere, IOW, the fact that a given inheritance child is a
"partition" is not persisted.
---
doc/src/sgml/ref/alter_table.sgml | 57 ++++
doc/src/sgml/ref/create_foreign_table.sgml | 15 +
doc/src/sgml/ref/create_table.sgml | 77 +++++-
src/backend/commands/createas.c | 1 +
src/backend/commands/sequence.c | 1 +
src/backend/commands/tablecmds.c | 490 +++++++++++++++++++++-------
src/backend/commands/view.c | 1 +
src/backend/nodes/copyfuncs.c | 48 +++
src/backend/nodes/equalfuncs.c | 42 +++
src/backend/nodes/outfuncs.c | 27 ++
src/backend/parser/gram.y | 209 ++++++++++++-
src/backend/parser/parse_agg.c | 7 +-
src/backend/parser/parse_expr.c | 23 ++
src/backend/parser/parse_utilcmd.c | 318 ++++++++++++++++++
src/include/catalog/partition.h | 1 +
src/include/nodes/nodes.h | 3 +
src/include/nodes/parsenodes.h | 43 +++-
src/include/parser/kwlist.h | 3 +
src/include/parser/parse_node.h | 3 +-
19 files changed, 1239 insertions(+), 130 deletions(-)
diff --git a/doc/src/sgml/ref/alter_table.sgml b/doc/src/sgml/ref/alter_table.sgml
index 5ca211e..97744c9 100644
--- a/doc/src/sgml/ref/alter_table.sgml
+++ b/doc/src/sgml/ref/alter_table.sgml
@@ -77,12 +77,15 @@ ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable>
NOT OF
OWNER TO { <replaceable class="PARAMETER">new_owner</replaceable> | CURRENT_USER | SESSION_USER }
REPLICA IDENTITY { DEFAULT | USING INDEX <replaceable class="PARAMETER">index_name</replaceable> | FULL | NOTHING }
+ ATTACH PARTITION <replaceable class="PARAMETER">name</replaceable> <replaceable class="PARAMETER">partition_bound_spec</replaceable> [ VALIDATE | NO VALIDATE ]
+ DETACH PARTITION <replaceable class="PARAMETER">name</replaceable>
<phrase>and <replaceable class="PARAMETER">table_constraint_using_index</replaceable> is:</phrase>
[ CONSTRAINT <replaceable class="PARAMETER">constraint_name</replaceable> ]
{ UNIQUE | PRIMARY KEY } USING INDEX <replaceable class="PARAMETER">index_name</replaceable>
[ DEFERRABLE | NOT DEFERRABLE ] [ INITIALLY DEFERRED | INITIALLY IMMEDIATE ]
+
</synopsis>
</refsynopsisdiv>
@@ -704,6 +707,51 @@ ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable>
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>ATTACH PARTITION</literal></term>
+ <listitem>
+ <para>
+ This form attaches an existing table as partition of the target table,
+ provided the target table is partitioned. The partition bound
+ specification must correspond with partition method and key of the
+ partitioned table. The table being attached must have all the columns
+ as the target table and no more. Also, it must have all the matching
+ constraints as the target table. That includes <literal>NOT NULL</>
+ and <literal>CHECK</> constraints. <literal>UNIQUE</literal>,
+ <literal>PRIMARY KEY</literal>, and <literal>FOREIGN KEY</literal>
+ constraints are not considered, but this might change in the future.
+ </para>
+
+ <para>
+ If <literal>NO VALIDATE</literal> clause is specified, existing rows in
+ source table are are not checked to see if they violate the partition
+ bound specification. This allows to quickly load a large amount of data
+ into the partitioned table. The default behavior is to perform the
+ check, as if <literal>VALIDATE</literal> clause were specified.
+ </para>
+
+ <para>
+ Note that once the table becomes a partition, it will be dropped or
+ truncated when the parent table is dropped or truncated. Dropping
+ it directly using <literal>DROP TABLE</literal> will fail; it must
+ first be detached from the parent. However, truncating a partition
+ directly works.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><literal>DETACH PARTITION</literal></term>
+ <listitem>
+ <para>
+ This form detaches specified partition of the target table, provided the
+ target table is partitioned and the specified partition exists. The
+ resulting table continues to exist as a standalone table with no ties
+ remaining with the parent partitioned table.
+ </para>
+ </listitem>
+ </varlistentry>
+
</variablelist>
</para>
@@ -936,6 +984,15 @@ ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable>
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><replaceable class="PARAMETER">partition_bound_spec</replaceable></term>
+ <listitem>
+ <para>
+ The partition bound specification for a new partition.
+ </para>
+ </listitem>
+ </varlistentry>
+
</variablelist>
</refsect1>
diff --git a/doc/src/sgml/ref/create_foreign_table.sgml b/doc/src/sgml/ref/create_foreign_table.sgml
index 413b033..c01ebf5 100644
--- a/doc/src/sgml/ref/create_foreign_table.sgml
+++ b/doc/src/sgml/ref/create_foreign_table.sgml
@@ -27,6 +27,15 @@ CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name
SERVER <replaceable class="parameter">server_name</replaceable>
[ OPTIONS ( <replaceable class="PARAMETER">option</replaceable> '<replaceable class="PARAMETER">value</replaceable>' [, ... ] ) ]
+CREATE FOREIGN TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name</replaceable>
+ PARTITION OF <replaceable class="PARAMETER">parent_table</replaceable> [ (
+ { <replaceable class="PARAMETER">column_name</replaceable> WITH OPTIONS [ <replaceable class="PARAMETER">column_constraint</replaceable> [ ... ] ]
+ | <replaceable>table_constraint</replaceable> }
+ [, ... ]
+) ] <replaceable class="PARAMETER">partition_bound_spec</replaceable>
+ SERVER <replaceable class="parameter">server_name</replaceable>
+[ OPTIONS ( <replaceable class="PARAMETER">option</replaceable> '<replaceable class="PARAMETER">value</replaceable>' [, ... ] ) ]
+
<phrase>where <replaceable class="PARAMETER">column_constraint</replaceable> is:</phrase>
[ CONSTRAINT <replaceable class="PARAMETER">constraint_name</replaceable> ]
@@ -68,6 +77,12 @@ CHECK ( <replaceable class="PARAMETER">expression</replaceable> ) [ NO INHERIT ]
</para>
<para>
+ If <literal>PARTITION OF</literal> clause is specified then the table is
+ created as a partition of <literal>parent_table</literal> with specified
+ bounds.
+ </para>
+
+ <para>
To be able to create a foreign table, you must have <literal>USAGE</literal>
privilege on the foreign server, as well as <literal>USAGE</literal>
privilege on all column types used in the table.
diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml
index c023260..4b11e96 100644
--- a/doc/src/sgml/ref/create_table.sgml
+++ b/doc/src/sgml/ref/create_table.sgml
@@ -44,6 +44,17 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
[ ON COMMIT { PRESERVE ROWS | DELETE ROWS | DROP } ]
[ TABLESPACE <replaceable class="PARAMETER">tablespace_name</replaceable> ]
+CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXISTS ] <replaceable class="PARAMETER">table_name</replaceable>
+ PARTITION OF <replaceable class="PARAMETER">parent_table</replaceable> [ (
+ { <replaceable class="PARAMETER">column_name</replaceable> WITH OPTIONS [ <replaceable class="PARAMETER">column_constraint</replaceable> [ ... ] ]
+ | <replaceable>table_constraint</replaceable> }
+ [, ... ]
+) ] <replaceable class="PARAMETER">partition_bound_spec</replaceable>
+[ PARTITION BY {RANGE | LIST} ( { <replaceable class="parameter">column_name</replaceable> | ( <replaceable class="parameter">expression</replaceable> ) } [ <replaceable class="parameter">opclass</replaceable> ] [, ...] )
+[ WITH ( <replaceable class="PARAMETER">storage_parameter</replaceable> [= <replaceable class="PARAMETER">value</replaceable>] [, ... ] ) | WITH OIDS | WITHOUT OIDS ]
+[ ON COMMIT { PRESERVE ROWS | DELETE ROWS | DROP } ]
+[ TABLESPACE <replaceable class="PARAMETER">tablespace_name</replaceable> ]
+
<phrase>where <replaceable class="PARAMETER">column_constraint</replaceable> is:</phrase>
[ CONSTRAINT <replaceable class="PARAMETER">constraint_name</replaceable> ]
@@ -72,6 +83,10 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
{ INCLUDING | EXCLUDING } { DEFAULTS | CONSTRAINTS | INDEXES | STORAGE | COMMENTS | ALL }
+<phrase>and <replaceable class="PARAMETER">partition_bound_spec</replaceable> is:</phrase>
+
+FOR VALUES { <replaceable class="PARAMETER">list_spec</replaceable> | <replaceable class="PARAMETER">range_spec</replaceable> }
+
<phrase><replaceable class="PARAMETER">index_parameters</replaceable> in <literal>UNIQUE</literal>, <literal>PRIMARY KEY</literal>, and <literal>EXCLUDE</literal> constraints are:</phrase>
[ WITH ( <replaceable class="PARAMETER">storage_parameter</replaceable> [= <replaceable class="PARAMETER">value</replaceable>] [, ... ] ) ]
@@ -80,8 +95,20 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
<phrase><replaceable class="PARAMETER">exclude_element</replaceable> in an <literal>EXCLUDE</literal> constraint is:</phrase>
{ <replaceable class="parameter">column_name</replaceable> | ( <replaceable class="parameter">expression</replaceable> ) } [ <replaceable class="parameter">opclass</replaceable> ] [ ASC | DESC ] [ NULLS { FIRST | LAST } ]
-</synopsis>
+<phrase><replaceable class="PARAMETER">list_spec</replaceable> in <literal>FOR VALUES</literal> is:</phrase>
+
+IN ( <replaceable class="PARAMETER">expression</replaceable> [, ...] )
+
+<phrase><replaceable class="PARAMETER">range_spec</replaceable> in <literal>FOR VALUES</literal> is:</phrase>
+
+START <replaceable class="PARAMETER">lower-bound</replaceable> [ INCLUSIVE | EXCLUSIVE ] END <replaceable class="PARAMETER">upper-bound</replaceable> [ INCLUSIVE | EXCLUSIVE ]
+
+<phrase>where <replaceable class="PARAMETER">lower-bound</replaceable> and <replaceable class="PARAMETER">upper-bound</replaceable> are:</phrase>
+
+{ ( <replaceable class="PARAMETER">expression</replaceable> [, ...] ) | UNBOUNDED }
+
+</synopsis>
</refsynopsisdiv>
<refsect1 id="SQL-CREATETABLE-description">
@@ -232,6 +259,49 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
</varlistentry>
<varlistentry>
+ <term><literal>PARTITION OF <replaceable class="PARAMETER">parent_table</replaceable></literal></term>
+ <listitem>
+ <para>
+ Creates the table as <firstterm>partition</firstterm> of the specified
+ parent table (name optionally schema-qualified).
+ </para>
+
+ <para>
+ A partition bound specification must be present and must correspond with
+ partition method and key of the parent table. It is checked using the
+ specification that the new partition does not overlap with any existing
+ partitions of the parent.
+ </para>
+
+ <para>
+ A partition cannot have columns other than those inherited from the
+ parent. That includes the <structfield>oid</> column, which can be
+ specified using the <literal>WITH (OIDS)</literal> clause. On the other
+ hand, if parent has the <structfield>oid</> column, the partition
+ inherits the same, overriding the <literal>WITH (OIDS=FALSE)</literal>
+ clause, if any. Defaults and constraints can optionally be specified
+ for each of the inherited columns, which override those in the parent.
+ One can also specify table constraints, in addition to those inherited
+ from the parent. Note that all subsequent schema modifications to the
+ parent propagate to partition.
+ </para>
+
+ <para>
+ Any data row subsequently inserted into the parent table is mapped to
+ and stored in the partition, provided partition key of the row falls
+ within the partition bounds.
+ </para>
+
+ <para>
+ A partition is dropped or truncated when the parent table is dropped or
+ truncated. Dropping it directly using <literal>DROP TABLE</literal>
+ will fail; it must first be <firstterm>detached</> from the parent.
+ However, truncating a partition directly works.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><replaceable class="PARAMETER">column_name</replaceable></term>
<listitem>
<para>
@@ -340,6 +410,11 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
</para>
<para>
+ When specifying for a table being created as partition, one needs to
+ use column names from the parent table as part of the key.
+ </para>
+
+ <para>
Currently, there are following limitations on definition of partitioned
tables: one cannot specify any UNIQUE, PRIMARY KEY, EXCLUDE and/or
FOREIGN KEY constraints.
diff --git a/src/backend/commands/createas.c b/src/backend/commands/createas.c
index 5a853c4..b2274e1 100644
--- a/src/backend/commands/createas.c
+++ b/src/backend/commands/createas.c
@@ -342,6 +342,7 @@ intorel_startup(DestReceiver *self, int operation, TupleDesc typeinfo)
col->is_local = true;
col->is_not_null = false;
col->is_from_type = false;
+ col->is_for_partition = false;
col->storage = 0;
col->raw_default = NULL;
col->cooked_default = NULL;
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index 5dcd4cf..21d9f96 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -159,6 +159,7 @@ DefineSequence(CreateSeqStmt *seq)
coldef->is_local = true;
coldef->is_not_null = true;
coldef->is_from_type = false;
+ coldef->is_for_partition = false;
coldef->storage = 0;
coldef->raw_default = NULL;
coldef->cooked_default = NULL;
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index c0a9e6a..db1ee74 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -279,7 +279,8 @@ typedef struct
static void truncate_check_rel(Relation rel);
static List *MergeAttributes(List *schema, List *supers, char relpersistence,
- List **supOids, List **supconstr, int *supOidCount);
+ List **supOids, List **supconstr, int *supOidCount,
+ bool is_partition);
static bool MergeCheckConstraint(List *constraints, char *name, Node *expr);
static void MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel);
static void MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel);
@@ -448,6 +449,11 @@ static void ComputePartitionAttrs(Oid relid, List *partParams,
static bool find_attr_reference_walker(Node *node,
find_attr_reference_context *context);
static bool is_partition_attr(Relation rel, AttrNumber attnum, bool *is_expr);
+static void CreateInheritance(Relation child_rel, Relation parent_rel);
+static void RemoveInheritance(Relation child_rel, Relation parent_rel);
+static ObjectAddress ATExecAttachPartition(List **wqueue, Relation rel,
+ PartitionCmd *cmd);
+static ObjectAddress ATExecDetachPartition(Relation rel, RangeVar *name);
/* ----------------------------------------------------------------
@@ -590,10 +596,16 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
/*
* Look up inheritance ancestors and generate relation schema, including
* inherited attributes.
+ *
+ * The last parameter implicitly specifies that the table is being created
+ * as partition and schema consists of columns definitions corresponding
+ * to non-dropped columns of the parent constructed such that each
+ * attribute of the table is created as inherited and non-local.
*/
schema = MergeAttributes(schema, stmt->inhRelations,
stmt->relation->relpersistence,
- &inheritOids, &old_constraints, &parentOidCount);
+ &inheritOids, &old_constraints, &parentOidCount,
+ stmt->partbound != NULL);
/*
* Create a tuple descriptor from the relation schema. Note that this
@@ -1452,7 +1464,8 @@ storage_name(char c)
*/
static List *
MergeAttributes(List *schema, List *supers, char relpersistence,
- List **supOids, List **supconstr, int *supOidCount)
+ List **supOids, List **supconstr, int *supOidCount,
+ bool is_partition)
{
ListCell *entry;
List *inhSchema = NIL;
@@ -1497,8 +1510,8 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
/*
* Typed table column option that does not belong to a column from
- * the type. This works because the columns from the type come
- * first in the list.
+ * the type or the partition parent. This works because the columns
+ * from the type or the partition parent come first in the list.
*/
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_COLUMN),
@@ -1525,6 +1538,18 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
coldef->is_from_type = false;
list_delete_cell(schema, rest, prev);
}
+ else if (coldef->is_for_partition)
+ {
+ /*
+ * merge the column options into the column from the parent
+ */
+ coldef->is_not_null = restdef->is_not_null;
+ coldef->raw_default = restdef->raw_default;
+ coldef->cooked_default = restdef->cooked_default;
+ coldef->constraints = restdef->constraints;
+ coldef->is_for_partition = false; /* job is done */
+ list_delete_cell(schema, rest, prev);
+ }
else
ereport(ERROR,
(errcode(ERRCODE_DUPLICATE_COLUMN),
@@ -1563,8 +1588,12 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
*/
relation = heap_openrv(parent, ShareUpdateExclusiveLock);
- /* Cannot inherit from partitioned tables */
- if (relation->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+ /*
+ * Cannot inherit from a partitioned table unless being created as
+ * partition.
+ */
+ if (relation->rd_rel->relkind == RELKIND_PARTITIONED_REL &&
+ !is_partition)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot inherit from partitioned table \"%s\"",
@@ -1710,6 +1739,7 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
def->is_local = false;
def->is_not_null = attribute->attnotnull;
def->is_from_type = false;
+ def->is_for_partition = false;
def->storage = attribute->attstorage;
def->raw_default = NULL;
def->cooked_default = NULL;
@@ -1838,6 +1868,9 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
* If we had no inherited attributes, the result schema is just the
* explicitly declared columns. Otherwise, we need to merge the declared
* columns into the inherited schema list.
+ *
+ * Note: In case of a partition, there are only inherited attributes that
+ * we copied from the parent as so, in transformPartitionOf().
*/
if (inhSchema != NIL)
{
@@ -1867,16 +1900,20 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
/*
* Yes, try to merge the two column definitions. They must
- * have the same type, typmod, and collation.
+ * have the same type, typmod, and collation. Don't output
+ * the notices, if partition, per note above.
*/
- if (exist_attno == schema_attno)
- ereport(NOTICE,
- (errmsg("merging column \"%s\" with inherited definition",
- attributeName)));
- else
- ereport(NOTICE,
- (errmsg("moving and merging column \"%s\" with inherited definition", attributeName),
- errdetail("User-specified column moved to the position of the inherited column.")));
+ if (!is_partition)
+ {
+ if (exist_attno == schema_attno)
+ ereport(NOTICE,
+ (errmsg("merging column \"%s\" with inherited definition",
+ attributeName)));
+ else
+ ereport(NOTICE,
+ (errmsg("moving and merging column \"%s\" with inherited definition", attributeName),
+ errdetail("User-specified column moved to the position of the inherited column.")));
+ }
def = (ColumnDef *) list_nth(inhSchema, exist_attno - 1);
typenameTypeIdAndMod(NULL, def->typeName, &defTypeId, &deftypmod);
typenameTypeIdAndMod(NULL, newdef->typeName, &newTypeId, &newtypmod);
@@ -1913,8 +1950,9 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
storage_name(def->storage),
storage_name(newdef->storage))));
- /* Mark the column as locally defined */
- def->is_local = true;
+ /* Mark the column as locally defined, unless partition */
+ if (!is_partition)
+ def->is_local = true;
/* Merge of NOT NULL constraints = OR 'em together */
def->is_not_null |= newdef->is_not_null;
/* If new def has a default, override previous default */
@@ -2093,16 +2131,20 @@ StoreCatalogInheritance1(Oid relationId, Oid parentOid,
heap_freetuple(tuple);
/*
- * Store a dependency too
+ * Store a dependency too, iff a regular inheritance relationship, which
+ * it's not, if parent is a partitioned table.
*/
- parentobject.classId = RelationRelationId;
- parentobject.objectId = parentOid;
- parentobject.objectSubId = 0;
- childobject.classId = RelationRelationId;
- childobject.objectId = relationId;
- childobject.objectSubId = 0;
+ if (!relid_is_partitioned(parentOid))
+ {
+ parentobject.classId = RelationRelationId;
+ parentobject.objectId = parentOid;
+ parentobject.objectSubId = 0;
+ childobject.classId = RelationRelationId;
+ childobject.objectId = relationId;
+ childobject.objectSubId = 0;
- recordDependencyOn(&childobject, &parentobject, DEPENDENCY_NORMAL);
+ recordDependencyOn(&childobject, &parentobject, DEPENDENCY_NORMAL);
+ }
/*
* Post creation hook of this inheritance. Since object_access_hook
@@ -3108,6 +3150,11 @@ AlterTableGetLockLevel(List *cmds)
cmd_lockmode = AlterTableGetRelOptionsLockLevel((List *) cmd->def);
break;
+ case AT_AttachPartition:
+ case AT_DetachPartition:
+ cmd_lockmode = AccessExclusiveLock;
+ break;
+
default: /* oops */
elog(ERROR, "unrecognized alter table type: %d",
(int) cmd->subtype);
@@ -3425,6 +3472,12 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
/* No command-specific prep needed */
pass = AT_PASS_MISC;
break;
+ case AT_AttachPartition:
+ case AT_DetachPartition:
+ ATSimplePermissions(rel, ATT_TABLE);
+ /* No command-specific prep needed */
+ pass = AT_PASS_MISC;
+ break;
default: /* oops */
elog(ERROR, "unrecognized alter table type: %d",
(int) cmd->subtype);
@@ -3744,6 +3797,12 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
case AT_GenericOptions:
ATExecGenericOptions(rel, (List *) cmd->def);
break;
+ case AT_AttachPartition:
+ ATExecAttachPartition(wqueue, rel, (PartitionCmd *) cmd->def);
+ break;
+ case AT_DetachPartition:
+ ATExecDetachPartition(rel, ((PartitionCmd *) cmd->def)->name);
+ break;
default: /* oops */
elog(ERROR, "unrecognized alter table type: %d",
(int) cmd->subtype);
@@ -10188,12 +10247,7 @@ ATPrepAddInherit(Relation child_rel)
static ObjectAddress
ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode)
{
- Relation parent_rel,
- catalogRelation;
- SysScanDesc scan;
- ScanKeyData key;
- HeapTuple inheritsTuple;
- int32 inhseqno;
+ Relation parent_rel;
List *children;
ObjectAddress address;
@@ -10238,38 +10292,6 @@ ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode)
errmsg("cannot inherit from partitioned table \"%s\"", parent->relname)));
/*
- * Check for duplicates in the list of parents, and determine the highest
- * inhseqno already present; we'll use the next one for the new parent.
- * (Note: get RowExclusiveLock because we will write pg_inherits below.)
- *
- * Note: we do not reject the case where the child already inherits from
- * the parent indirectly; CREATE TABLE doesn't reject comparable cases.
- */
- catalogRelation = heap_open(InheritsRelationId, RowExclusiveLock);
- ScanKeyInit(&key,
- Anum_pg_inherits_inhrelid,
- BTEqualStrategyNumber, F_OIDEQ,
- ObjectIdGetDatum(RelationGetRelid(child_rel)));
- scan = systable_beginscan(catalogRelation, InheritsRelidSeqnoIndexId,
- true, NULL, 1, &key);
-
- /* inhseqno sequences start at 1 */
- inhseqno = 0;
- while (HeapTupleIsValid(inheritsTuple = systable_getnext(scan)))
- {
- Form_pg_inherits inh = (Form_pg_inherits) GETSTRUCT(inheritsTuple);
-
- if (inh->inhparent == RelationGetRelid(parent_rel))
- ereport(ERROR,
- (errcode(ERRCODE_DUPLICATE_TABLE),
- errmsg("relation \"%s\" would be inherited from more than once",
- RelationGetRelationName(parent_rel))));
- if (inh->inhseqno > inhseqno)
- inhseqno = inh->inhseqno;
- }
- systable_endscan(scan);
-
- /*
* Prevent circularity by seeing if proposed parent inherits from child.
* (In particular, this disallows making a rel inherit from itself.)
*
@@ -10302,6 +10324,69 @@ ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode)
RelationGetRelationName(child_rel),
RelationGetRelationName(parent_rel))));
+ /* OK to create inheritance */
+ CreateInheritance(child_rel, parent_rel);
+
+ ObjectAddressSet(address, RelationRelationId,
+ RelationGetRelid(parent_rel));
+
+ /* keep our lock on the parent relation until commit */
+ heap_close(parent_rel, NoLock);
+
+ return address;
+}
+
+/*
+ * CreateInheritance
+ * Catalog manipulation portion of creating inheritance between a child
+ * table and a parent table.
+ *
+ * Common to ATExecAddInherit() and ATExecAttachPartition().
+ */
+static void
+CreateInheritance(Relation child_rel, Relation parent_rel)
+{
+ Relation catalogRelation;
+ SysScanDesc scan;
+ ScanKeyData key;
+ HeapTuple inheritsTuple;
+ int32 inhseqno;
+
+ /* Note: get RowExclusiveLock because we will write pg_inherits below. */
+ catalogRelation = heap_open(InheritsRelationId, RowExclusiveLock);
+
+ /*
+ * Check for duplicates in the list of parents, and determine the highest
+ * inhseqno already present; we'll use the next one for the new parent.
+ * Also, if proposed child is a partition, it cannot already be inheriting.
+ *
+ * Note: we do not reject the case where the child already inherits from
+ * the parent indirectly; CREATE TABLE doesn't reject comparable cases.
+ */
+ ScanKeyInit(&key,
+ Anum_pg_inherits_inhrelid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(RelationGetRelid(child_rel)));
+ scan = systable_beginscan(catalogRelation, InheritsRelidSeqnoIndexId,
+ true, NULL, 1, &key);
+
+ /* inhseqno sequences start at 1 */
+ inhseqno = 0;
+ while (HeapTupleIsValid(inheritsTuple = systable_getnext(scan)))
+ {
+ Form_pg_inherits inh = (Form_pg_inherits) GETSTRUCT(inheritsTuple);
+
+ if (inh->inhparent == RelationGetRelid(parent_rel))
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_TABLE),
+ errmsg("relation \"%s\" would be inherited from more than once",
+ RelationGetRelationName(parent_rel))));
+
+ if (inh->inhseqno > inhseqno)
+ inhseqno = inh->inhseqno;
+ }
+ systable_endscan(scan);
+
/* Match up the columns and bump attinhcount as needed */
MergeAttributesIntoExisting(child_rel, parent_rel);
@@ -10316,16 +10401,8 @@ ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode)
inhseqno + 1,
catalogRelation);
- ObjectAddressSet(address, RelationRelationId,
- RelationGetRelid(parent_rel));
-
/* Now we're done with pg_inherits */
heap_close(catalogRelation, RowExclusiveLock);
-
- /* keep our lock on the parent relation until commit */
- heap_close(parent_rel, NoLock);
-
- return address;
}
/*
@@ -10376,7 +10453,7 @@ constraints_equivalent(HeapTuple a, HeapTuple b, TupleDesc tupleDesc)
* Check columns in child table match up with columns in parent, and increment
* their attinhcount.
*
- * Called by ATExecAddInherit
+ * Called by CreateInheritance
*
* Currently all parent columns must be found in child. Missing columns are an
* error. One day we might consider creating new columns like CREATE TABLE
@@ -10394,11 +10471,13 @@ MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel)
int parent_natts;
TupleDesc tupleDesc;
HeapTuple tuple;
+ bool is_attach_partition;
attrrel = heap_open(AttributeRelationId, RowExclusiveLock);
tupleDesc = RelationGetDescr(parent_rel);
parent_natts = tupleDesc->natts;
+ is_attach_partition = relid_is_partitioned(RelationGetRelid(parent_rel));
for (parent_attno = 1; parent_attno <= parent_natts; parent_attno++)
{
@@ -10421,14 +10500,16 @@ MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel)
attribute->atttypmod != childatt->atttypmod)
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
- errmsg("child table \"%s\" has different type for column \"%s\"",
+ errmsg("%s table \"%s\" has different type for column \"%s\"",
+ is_attach_partition ? "source" : "child",
RelationGetRelationName(child_rel),
attributeName)));
if (attribute->attcollation != childatt->attcollation)
ereport(ERROR,
(errcode(ERRCODE_COLLATION_MISMATCH),
- errmsg("child table \"%s\" has different collation for column \"%s\"",
+ errmsg("%s table \"%s\" has different collation for column \"%s\"",
+ is_attach_partition ? "source" : "child",
RelationGetRelationName(child_rel),
attributeName)));
@@ -10439,8 +10520,9 @@ MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel)
if (attribute->attnotnull && !childatt->attnotnull)
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
- errmsg("column \"%s\" in child table must be marked NOT NULL",
- attributeName)));
+ errmsg("column \"%s\" in %s table must be marked NOT NULL",
+ attributeName,
+ is_attach_partition ? "source" : "child")));
/*
* OK, bump the child column's inheritance count. (If we fail
@@ -10455,7 +10537,8 @@ MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel)
{
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
- errmsg("child table is missing column \"%s\"",
+ errmsg("%s table is missing column \"%s\"",
+ is_attach_partition ? "source" : "child",
attributeName)));
}
}
@@ -10469,7 +10552,7 @@ MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel)
*
* Constraints that are marked ONLY in the parent are ignored.
*
- * Called by ATExecAddInherit
+ * Called by CreateInheritance
*
* Currently all constraints in parent must be present in the child. One day we
* may consider adding new constraints like CREATE TABLE does.
@@ -10488,9 +10571,11 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
SysScanDesc parent_scan;
ScanKeyData parent_key;
HeapTuple parent_tuple;
+ bool is_attach_partition;
catalog_relation = heap_open(ConstraintRelationId, RowExclusiveLock);
tuple_desc = RelationGetDescr(catalog_relation);
+ is_attach_partition = relid_is_partitioned(RelationGetRelid(parent_rel));
/* Outer loop scans through the parent's constraint definitions */
ScanKeyInit(&parent_key,
@@ -10538,7 +10623,8 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
if (!constraints_equivalent(parent_tuple, child_tuple, tuple_desc))
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
- errmsg("child table \"%s\" has different definition for check constraint \"%s\"",
+ errmsg("%s table \"%s\" has different definition for check constraint \"%s\"",
+ is_attach_partition ? "source" : "child",
RelationGetRelationName(child_rel),
NameStr(parent_con->conname))));
@@ -10546,8 +10632,9 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
if (child_con->connoinherit)
ereport(ERROR,
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
- errmsg("constraint \"%s\" conflicts with non-inherited constraint on child table \"%s\"",
+ errmsg("constraint \"%s\" conflicts with non-inherited constraint on %s table \"%s\"",
NameStr(child_con->conname),
+ is_attach_partition ? "source" : "child",
RelationGetRelationName(child_rel))));
/*
@@ -10570,7 +10657,8 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
if (!found)
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
- errmsg("child table is missing constraint \"%s\"",
+ errmsg("%s table is missing constraint \"%s\"",
+ is_attach_partition ? "source" : "child",
NameStr(parent_con->conname))));
}
@@ -10581,6 +10669,41 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
/*
* ALTER TABLE NO INHERIT
*
+ * Return value is the address of the relation that is no longer parent.
+ */
+static ObjectAddress
+ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
+{
+ ObjectAddress address;
+ Relation parent_rel;
+
+ /*
+ * AccessShareLock on the parent is probably enough, seeing that DROP
+ * TABLE doesn't lock parent tables at all. We need some lock since we'll
+ * be inspecting the parent's schema.
+ */
+ parent_rel = heap_openrv(parent, AccessShareLock);
+
+ /*
+ * We don't bother to check ownership of the parent table --- ownership of
+ * the child is presumed enough rights.
+ */
+
+ /* Off to RemoveInheritance() where most of the work happens */
+ RemoveInheritance(rel, parent_rel);
+
+ /* keep our lock on the parent relation until commit */
+ heap_close(parent_rel, NoLock);
+
+ ObjectAddressSet(address, RelationRelationId,
+ RelationGetRelid(parent_rel));
+
+ return address;
+}
+
+/*
+ * RemoveInheritance
+ *
* Drop a parent from the child's parents. This just adjusts the attinhcount
* and attislocal of the columns and removes the pg_inherit and pg_depend
* entries.
@@ -10594,13 +10717,11 @@ MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel)
* coninhcount and conislocal for inherited constraints are adjusted in
* exactly the same way.
*
- * Return value is the address of the relation that is no longer parent.
+ * Common to ATExecDropInherit() and ATExecDetachPartition().
*/
-static ObjectAddress
-ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
+static void
+RemoveInheritance(Relation child_rel, Relation parent_rel)
{
- Relation parent_rel;
- Oid parent_oid;
Relation catalogRelation;
SysScanDesc scan;
ScanKeyData key[3];
@@ -10609,19 +10730,9 @@ ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
constraintTuple;
List *connames;
bool found = false;
- ObjectAddress address;
+ bool is_detach_partition;
- /*
- * AccessShareLock on the parent is probably enough, seeing that DROP
- * TABLE doesn't lock parent tables at all. We need some lock since we'll
- * be inspecting the parent's schema.
- */
- parent_rel = heap_openrv(parent, AccessShareLock);
-
- /*
- * We don't bother to check ownership of the parent table --- ownership of
- * the child is presumed enough rights.
- */
+ is_detach_partition = relid_is_partitioned(RelationGetRelid(parent_rel));
/*
* Find and destroy the pg_inherits entry linking the two, or error out if
@@ -10631,7 +10742,7 @@ ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
ScanKeyInit(&key[0],
Anum_pg_inherits_inhrelid,
BTEqualStrategyNumber, F_OIDEQ,
- ObjectIdGetDatum(RelationGetRelid(rel)));
+ ObjectIdGetDatum(RelationGetRelid(child_rel)));
scan = systable_beginscan(catalogRelation, InheritsRelidSeqnoIndexId,
true, NULL, 1, key);
@@ -10652,11 +10763,20 @@ ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
heap_close(catalogRelation, RowExclusiveLock);
if (!found)
- ereport(ERROR,
- (errcode(ERRCODE_UNDEFINED_TABLE),
- errmsg("relation \"%s\" is not a parent of relation \"%s\"",
- RelationGetRelationName(parent_rel),
- RelationGetRelationName(rel))));
+ {
+ if (!is_detach_partition)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_TABLE),
+ errmsg("relation \"%s\" is not a parent of relation \"%s\"",
+ RelationGetRelationName(parent_rel),
+ RelationGetRelationName(child_rel))));
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_TABLE),
+ errmsg("relation \"%s\" is not a partition of relation \"%s\"",
+ RelationGetRelationName(child_rel),
+ RelationGetRelationName(parent_rel))));
+ }
/*
* Search through child columns looking for ones matching parent rel
@@ -10665,7 +10785,7 @@ ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
ScanKeyInit(&key[0],
Anum_pg_attribute_attrelid,
BTEqualStrategyNumber, F_OIDEQ,
- ObjectIdGetDatum(RelationGetRelid(rel)));
+ ObjectIdGetDatum(RelationGetRelid(child_rel)));
scan = systable_beginscan(catalogRelation, AttributeRelidNumIndexId,
true, NULL, 1, key);
while (HeapTupleIsValid(attributeTuple = systable_getnext(scan)))
@@ -10727,7 +10847,7 @@ ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
ScanKeyInit(&key[0],
Anum_pg_constraint_conrelid,
BTEqualStrategyNumber, F_OIDEQ,
- ObjectIdGetDatum(RelationGetRelid(rel)));
+ ObjectIdGetDatum(RelationGetRelid(child_rel)));
scan = systable_beginscan(catalogRelation, ConstraintRelidIndexId,
true, NULL, 1, key);
@@ -10758,7 +10878,7 @@ ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
if (copy_con->coninhcount <= 0) /* shouldn't happen */
elog(ERROR, "relation %u has non-inherited constraint \"%s\"",
- RelationGetRelid(rel), NameStr(copy_con->conname));
+ RelationGetRelid(child_rel), NameStr(copy_con->conname));
copy_con->coninhcount--;
if (copy_con->coninhcount == 0)
@@ -10770,30 +10890,22 @@ ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
}
}
- parent_oid = RelationGetRelid(parent_rel);
-
systable_endscan(scan);
heap_close(catalogRelation, RowExclusiveLock);
- drop_parent_dependency(RelationGetRelid(rel),
- RelationRelationId,
- RelationGetRelid(parent_rel));
-
+ /* Remember that dependency exists iff regular inheritance. */
+ if (!is_detach_partition)
+ drop_parent_dependency(RelationGetRelid(child_rel),
+ RelationRelationId,
+ RelationGetRelid(parent_rel));
/*
* Post alter hook of this inherits. Since object_access_hook doesn't take
* multiple object identifiers, we relay oid of parent relation using
* auxiliary_id argument.
*/
InvokeObjectPostAlterHookArg(InheritsRelationId,
- RelationGetRelid(rel), 0,
+ RelationGetRelid(child_rel), 0,
RelationGetRelid(parent_rel), false);
-
- /* keep our lock on the parent relation until commit */
- heap_close(parent_rel, NoLock);
-
- ObjectAddressSet(address, RelationRelationId, parent_oid);
-
- return address;
}
/*
@@ -12426,3 +12538,137 @@ ComputePartitionAttrs(Oid relid, List *partParams, AttrNumber *partattrs,
partopclass[attn++] = opclassOid;
}
}
+
+
+/*
+ * ALTER TABLE <name> ATTACH PARTITION <partition-name> FOR VALUES
+ *
+ * Return the address of the newly attached partition.
+ */
+static ObjectAddress
+ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
+{
+ Relation attachRel,
+ inheritsRelation;
+ SysScanDesc scan;
+ ScanKeyData key;
+ HeapTuple tuple;
+ AttrNumber attno;
+ int natts;
+ TupleDesc tupleDesc;
+ ObjectAddress address;
+
+ attachRel = heap_openrv(cmd->name, AccessShareLock);
+
+ /*
+ * Must be owner of both parent and source table -- parent was checked by
+ * ATSimplePermissions call in ATPrepCmd
+ */
+ ATSimplePermissions(attachRel, ATT_TABLE);
+
+ if (attachRel->rd_rel->reloftype)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot attach a typed table as partition")));
+
+ if (attachRel->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot attach a partitioned table as partition")));
+
+ /* attachRel should not already be a inheritance child of some relation */
+ inheritsRelation = heap_open(InheritsRelationId, AccessShareLock);
+ ScanKeyInit(&key,
+ Anum_pg_inherits_inhrelid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(RelationGetRelid(attachRel)));
+ scan = systable_beginscan(inheritsRelation, InheritsRelidSeqnoIndexId,
+ true, NULL, 1, &key);
+ if (HeapTupleIsValid(systable_getnext(scan)))
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot attach table that inherits as partition")));
+ systable_endscan(scan);
+ heap_close(inheritsRelation, AccessShareLock);
+
+ /* If attachRel is temp, it must belong to this session */
+ if (attachRel->rd_rel->relpersistence == RELPERSISTENCE_TEMP &&
+ !attachRel->rd_islocaltemp)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot attach a temporary relation of another session as partition ")));
+
+ /* If parent has OIDs, attachRel must have OIDs */
+ if (rel->rd_rel->relhasoids && !attachRel->rd_rel->relhasoids)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot attach table \"%s\" without OIDs as partition of"
+ " table \"%s\" with OIDs", RelationGetRelationName(attachRel),
+ RelationGetRelationName(rel))));
+
+ /* OTOH, if parent doesn't have them, do not allow in attachRel either */
+ if (attachRel->rd_rel->relhasoids && !rel->rd_rel->relhasoids)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot attach table \"%s\" with OIDs as partition of table"
+ " \"%s\" without OIDs", RelationGetRelationName(attachRel),
+ RelationGetRelationName(rel))));
+
+ /* Check if there are any columns in attachRel that aren't in the parent */
+ tupleDesc = RelationGetDescr(attachRel);
+ natts = tupleDesc->natts;
+ for (attno = 1; attno <= natts; attno++)
+ {
+ Form_pg_attribute attribute = tupleDesc->attrs[attno - 1];
+ char *attributeName = NameStr(attribute->attname);
+
+ /* Ignore dropped */
+ if (attribute->attisdropped)
+ continue;
+
+ /* Find same column in parent (matching on column name). */
+ tuple = SearchSysCacheCopyAttName(RelationGetRelid(rel), attributeName);
+ if (!HeapTupleIsValid(tuple))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("table \"%s\" contains column \"%s\" not found in parent \"%s\"",
+ RelationGetRelationName(attachRel), attributeName,
+ RelationGetRelationName(rel)),
+ errdetail("Table being attached should contain only the columns"
+ " present in parent.")));
+ }
+
+ /* OK to create inheritance. Rest of the checks performed there */
+ CreateInheritance(attachRel, rel);
+
+ ObjectAddressSet(address, RelationRelationId, RelationGetRelid(attachRel));
+
+ /* keep our lock until commit */
+ heap_close(attachRel, NoLock);
+
+ return address;
+}
+
+/*
+ * ALTER TABLE DETACH PARTITION
+ *
+ * Return the address of the relation that is no longer a partition of rel.
+ */
+static ObjectAddress
+ATExecDetachPartition(Relation rel, RangeVar *name)
+{
+ Relation partRel;
+ ObjectAddress address;
+
+ partRel = heap_openrv(name, AccessShareLock);
+
+ /* All inheritance related checks are performed within the function */
+ RemoveInheritance(partRel, rel);
+
+ ObjectAddressSet(address, RelationRelationId, RelationGetRelid(partRel));
+
+ /* keep our lock until commit */
+ heap_close(partRel, NoLock);
+
+ return address;
+}
diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c
index e9d9ba2..c9c72d9 100644
--- a/src/backend/commands/view.c
+++ b/src/backend/commands/view.c
@@ -95,6 +95,7 @@ DefineVirtualRelation(RangeVar *relation, List *tlist, bool replace,
def->is_local = true;
def->is_not_null = false;
def->is_from_type = false;
+ def->is_for_partition = false;
def->storage = 0;
def->raw_default = NULL;
def->cooked_default = NULL;
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index ecf9cbf..7876728 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2605,6 +2605,7 @@ _copyColumnDef(const ColumnDef *from)
COPY_SCALAR_FIELD(is_local);
COPY_SCALAR_FIELD(is_not_null);
COPY_SCALAR_FIELD(is_from_type);
+ COPY_SCALAR_FIELD(is_for_partition);
COPY_SCALAR_FIELD(storage);
COPY_NODE_FIELD(raw_default);
COPY_NODE_FIELD(cooked_default);
@@ -3000,6 +3001,7 @@ CopyCreateStmtFields(const CreateStmt *from, CreateStmt *newnode)
COPY_NODE_FIELD(relation);
COPY_NODE_FIELD(tableElts);
COPY_NODE_FIELD(inhRelations);
+ COPY_NODE_FIELD(partbound);
COPY_NODE_FIELD(partby);
COPY_NODE_FIELD(ofTypename);
COPY_NODE_FIELD(constraints);
@@ -4182,6 +4184,43 @@ _copyPartitionElem(const PartitionElem *from)
return newnode;
}
+static PartitionListSpec *
+_copyPartitionListSpec(const PartitionListSpec *from)
+{
+ PartitionListSpec *newnode = makeNode(PartitionListSpec);
+
+ COPY_NODE_FIELD(values);
+ COPY_LOCATION_FIELD(location);
+
+ return newnode;
+}
+
+static PartitionRangeSpec *
+_copyPartitionRangeSpec(const PartitionRangeSpec *from)
+{
+ PartitionRangeSpec *newnode = makeNode(PartitionRangeSpec);
+
+ COPY_SCALAR_FIELD(lowerinc);
+ COPY_NODE_FIELD(lower);
+ COPY_SCALAR_FIELD(upperinc);
+ COPY_NODE_FIELD(upper);
+ COPY_LOCATION_FIELD(location);
+
+ return newnode;
+}
+
+static PartitionCmd *
+_copyPartitionCmd(const PartitionCmd *from)
+{
+ PartitionCmd *newnode = makeNode(PartitionCmd);
+
+ COPY_NODE_FIELD(name);
+ COPY_NODE_FIELD(bound);
+ COPY_SCALAR_FIELD(skip_validate);
+
+ return newnode;
+}
+
/* ****************************************************************
* pg_list.h copy functions
* ****************************************************************
@@ -5099,6 +5138,15 @@ copyObject(const void *from)
case T_PartitionElem:
retval = _copyPartitionElem(from);
break;
+ case T_PartitionListSpec:
+ retval = _copyPartitionListSpec(from);
+ break;
+ case T_PartitionRangeSpec:
+ retval = _copyPartitionRangeSpec(from);
+ break;
+ case T_PartitionCmd:
+ retval = _copyPartitionCmd(from);
+ break;
/*
* MISCELLANEOUS NODES
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 9554c2f..93fc8f2 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1156,6 +1156,7 @@ _equalCreateStmt(const CreateStmt *a, const CreateStmt *b)
COMPARE_NODE_FIELD(relation);
COMPARE_NODE_FIELD(tableElts);
COMPARE_NODE_FIELD(inhRelations);
+ COMPARE_NODE_FIELD(partbound);
COMPARE_NODE_FIELD(partby);
COMPARE_NODE_FIELD(ofTypename);
COMPARE_NODE_FIELD(constraints);
@@ -2363,6 +2364,7 @@ _equalColumnDef(const ColumnDef *a, const ColumnDef *b)
COMPARE_SCALAR_FIELD(is_local);
COMPARE_SCALAR_FIELD(is_not_null);
COMPARE_SCALAR_FIELD(is_from_type);
+ COMPARE_SCALAR_FIELD(is_for_partition);
COMPARE_SCALAR_FIELD(storage);
COMPARE_NODE_FIELD(raw_default);
COMPARE_NODE_FIELD(cooked_default);
@@ -2641,6 +2643,37 @@ _equalPartitionElem(const PartitionElem *a, const PartitionElem *b)
return true;
}
+static bool
+_equalPartitionListSpec(const PartitionListSpec *a, const PartitionListSpec *b)
+{
+ COMPARE_NODE_FIELD(values);
+ COMPARE_LOCATION_FIELD(location);
+
+ return true;
+}
+
+static bool
+_equalPartitionRangeSpec(const PartitionRangeSpec *a, const PartitionRangeSpec *b)
+{
+ COMPARE_SCALAR_FIELD(lowerinc);
+ COMPARE_NODE_FIELD(lower);
+ COMPARE_SCALAR_FIELD(upperinc);
+ COMPARE_NODE_FIELD(upper);
+ COMPARE_LOCATION_FIELD(location);
+
+ return true;
+}
+
+static bool
+_equalPartitionCmd(const PartitionCmd *a, const PartitionCmd *b)
+{
+ COMPARE_NODE_FIELD(name);
+ COMPARE_NODE_FIELD(bound);
+ COMPARE_SCALAR_FIELD(skip_validate);
+
+ return true;
+}
+
/*
* Stuff from pg_list.h
*/
@@ -3397,6 +3430,15 @@ equal(const void *a, const void *b)
case T_PartitionElem:
retval = _equalPartitionElem(a, b);
break;
+ case T_PartitionListSpec:
+ retval = _equalPartitionListSpec(a, b);
+ break;
+ case T_PartitionRangeSpec:
+ retval = _equalPartitionRangeSpec(a, b);
+ break;
+ case T_PartitionCmd:
+ retval = _equalPartitionCmd(a, b);
+ break;
default:
elog(ERROR, "unrecognized node type: %d",
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 0b7cec1..d1ef6db 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2392,6 +2392,7 @@ _outCreateStmtInfo(StringInfo str, const CreateStmt *node)
WRITE_NODE_FIELD(relation);
WRITE_NODE_FIELD(tableElts);
WRITE_NODE_FIELD(inhRelations);
+ WRITE_NODE_FIELD(partbound);
WRITE_NODE_FIELD(partby);
WRITE_NODE_FIELD(ofTypename);
WRITE_NODE_FIELD(constraints);
@@ -2572,6 +2573,7 @@ _outColumnDef(StringInfo str, const ColumnDef *node)
WRITE_BOOL_FIELD(is_local);
WRITE_BOOL_FIELD(is_not_null);
WRITE_BOOL_FIELD(is_from_type);
+ WRITE_BOOL_FIELD(is_for_partition);
WRITE_CHAR_FIELD(storage);
WRITE_NODE_FIELD(raw_default);
WRITE_NODE_FIELD(cooked_default);
@@ -3283,6 +3285,25 @@ _outPartitionElem(StringInfo str, const PartitionElem *node)
WRITE_LOCATION_FIELD(location);
}
+static void
+_outPartitionListSpec(StringInfo str, const PartitionListSpec *node)
+{
+ WRITE_NODE_TYPE("PARTITIONLISTVALUES");
+
+ WRITE_NODE_FIELD(values);
+}
+
+static void
+_outPartitionRangeSpec(StringInfo str, const PartitionRangeSpec *node)
+{
+ WRITE_NODE_TYPE("PARTITIONRANGE");
+
+ WRITE_BOOL_FIELD(lowerinc);
+ WRITE_NODE_FIELD(lower);
+ WRITE_BOOL_FIELD(upperinc);
+ WRITE_NODE_FIELD(upper);
+}
+
/*
* outNode -
* converts a Node into ascii string and append it to 'str'
@@ -3869,6 +3890,12 @@ outNode(StringInfo str, const void *obj)
case T_PartitionElem:
_outPartitionElem(str, obj);
break;
+ case T_PartitionListSpec:
+ _outPartitionListSpec(str, obj);
+ break;
+ case T_PartitionRangeSpec:
+ _outPartitionRangeSpec(str, obj);
+ break;
default:
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index bf29205..460a00e 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -229,6 +229,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
VariableSetStmt *vsetstmt;
PartitionElem *partelem;
PartitionBy *partby;
+ PartitionRangeSpec *partrange;
}
%type <node> stmt schema_stmt
@@ -544,6 +545,14 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <partby> PartitionBy OptPartitionBy
%type <partelem> part_elem
%type <list> part_params
+%type <list> OptPartitionElementList PartitionElementList
+%type <node> PartitionElement
+%type <node> ForValues
+%type <node> partvalue
+%type <list> partvalue_list
+%type <boolean> lb_inc ub_inc
+%type <list> RangeBound
+%type <boolean> opt_validate_spec
/*
* Non-keyword token types. These are hard-wired into the "flex" lexer.
@@ -569,7 +578,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
/* ordinary key words in alphabetical order */
%token <keyword> ABORT_P ABSOLUTE_P ACCESS ACTION ADD_P ADMIN AFTER
AGGREGATE ALL ALSO ALTER ALWAYS ANALYSE ANALYZE AND ANY ARRAY AS ASC
- ASSERTION ASSIGNMENT ASYMMETRIC AT ATTRIBUTE AUTHORIZATION
+ ASSERTION ASSIGNMENT ASYMMETRIC AT ATTACH ATTRIBUTE AUTHORIZATION
BACKWARD BEFORE BEGIN_P BETWEEN BIGINT BINARY BIT
BOOLEAN_P BOTH BY
@@ -585,7 +594,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
DATA_P DATABASE DAY_P DEALLOCATE DEC DECIMAL_P DECLARE DEFAULT DEFAULTS
DEFERRABLE DEFERRED DEFINER DELETE_P DELIMITER DELIMITERS DEPENDS DESC
- DICTIONARY DISABLE_P DISCARD DISTINCT DO DOCUMENT_P DOMAIN_P DOUBLE_P DROP
+ DETACH DICTIONARY DISABLE_P DISCARD DISTINCT DO DOCUMENT_P DOMAIN_P
+ DOUBLE_P DROP
EACH ELSE ENABLE_P ENCODING ENCRYPTED END_P ENUM_P ESCAPE EVENT EXCEPT
EXCLUDE EXCLUDING EXCLUSIVE EXECUTE EXISTS EXPLAIN
@@ -599,7 +609,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
HANDLER HAVING HEADER_P HOLD HOUR_P
IDENTITY_P IF_P ILIKE IMMEDIATE IMMUTABLE IMPLICIT_P IMPORT_P IN_P
- INCLUDING INCREMENT INDEX INDEXES INHERIT INHERITS INITIALLY INLINE_P
+ INCLUDING INCLUSIVE INCREMENT INDEX INDEXES INHERIT INHERITS INITIALLY INLINE_P
INNER_P INOUT INPUT_P INSENSITIVE INSERT INSTEAD INT_P INTEGER
INTERSECT INTERVAL INTO INVOKER IS ISNULL ISOLATION
@@ -2373,6 +2383,38 @@ alter_table_cmd:
n->def = (Node *)$1;
$$ = (Node *) n;
}
+ /* ALTER TABLE <name> ATTACH PARTITION <table_name> FOR VALUES */
+ | ATTACH PARTITION qualified_name ForValues opt_validate_spec
+ {
+ AlterTableCmd *n = makeNode(AlterTableCmd);
+ PartitionCmd *cmd = makeNode(PartitionCmd);
+
+ n->subtype = AT_AttachPartition;
+ cmd->name = $3;
+ cmd->bound = (Node *) $4;
+ cmd->skip_validate = $5;
+ n->def = (Node *) cmd;
+
+ $$ = (Node *) n;
+ }
+ /* ALTER TABLE <name> DETACH PARTITION <partition_name> */
+ | DETACH PARTITION qualified_name
+ {
+ AlterTableCmd *n = makeNode(AlterTableCmd);
+ PartitionCmd *cmd = makeNode(PartitionCmd);
+
+ n->subtype = AT_DetachPartition;
+ cmd->name = $3;
+ n->def = (Node *) cmd;
+
+ $$ = (Node *) n;
+ }
+ ;
+
+opt_validate_spec:
+ NO VALIDATE { $$ = true; }
+ | VALIDATE { $$ = false; }
+ | /* EMPTY */ { $$ = false; }
;
alter_column_default:
@@ -2468,6 +2510,60 @@ reloption_elem:
}
;
+ForValues:
+ /* a LIST partition */
+ FOR VALUES IN_P '(' partvalue_list ')'
+ {
+ PartitionListSpec *n = makeNode(PartitionListSpec);
+
+ n->values = $5;
+ n->location = @1;
+
+ $$ = (Node *) n;
+ }
+
+ /* a RANGE partition */
+ | FOR VALUES START RangeBound lb_inc END_P RangeBound ub_inc
+ {
+ PartitionRangeSpec *n = makeNode(PartitionRangeSpec);
+
+ n->lowerinc = $5;
+ n->lower = $4;
+ n->upperinc = $8;
+ n->upper = $7;
+ n->location = @1;
+
+ $$ = (Node *) n;
+ }
+ ;
+
+RangeBound:
+ UNBOUNDED { $$ = NIL; }
+ | '(' partvalue_list ')' { $$ = $2; }
+ ;
+
+lb_inc:
+ EXCLUSIVE { $$ = false;}
+ | INCLUSIVE { $$ = true; }
+ | /* EMPTY */ { $$ = true; }
+ ;
+
+ub_inc:
+ INCLUSIVE { $$ = true; }
+ | EXCLUSIVE { $$ = false; }
+ | /* EMPTY */ { $$ = false; }
+ ;
+
+partvalue:
+ Sconst { $$ = makeStringConst($1, @1); }
+ | NumericOnly { $$ = makeAConst($1, @1); }
+ | NULL_P { $$ = makeNullAConst(@1); }
+ ;
+
+partvalue_list:
+ partvalue { $$ = list_make1($1); }
+ | partvalue_list ',' partvalue { $$ = lappend($1, $3); }
+ ;
/*****************************************************************************
*
@@ -2885,6 +2981,44 @@ CreateStmt: CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
n->if_not_exists = true;
$$ = (Node *)n;
}
+ | CREATE OptTemp TABLE qualified_name PARTITION OF qualified_name
+ OptPartitionElementList ForValues OptPartitionBy OptWith
+ OnCommitOption OptTableSpace
+ {
+ CreateStmt *n = makeNode(CreateStmt);
+ $4->relpersistence = $2;
+ n->relation = $4;
+ n->tableElts = $8;
+ n->inhRelations = list_make1($7);
+ n->partbound = (Node *) $9;
+ n->partby = $10;
+ n->ofTypename = NULL;
+ n->constraints = NIL;
+ n->options = $11;
+ n->oncommit = $12;
+ n->tablespacename = $13;
+ n->if_not_exists = false;
+ $$ = (Node *)n;
+ }
+ | CREATE OptTemp TABLE IF_P NOT EXISTS qualified_name PARTITION OF
+ qualified_name OptPartitionElementList ForValues OptPartitionBy
+ OptWith OnCommitOption OptTableSpace
+ {
+ CreateStmt *n = makeNode(CreateStmt);
+ $7->relpersistence = $2;
+ n->relation = $7;
+ n->tableElts = $11;
+ n->inhRelations = list_make1($10);
+ n->partbound = (Node *) $12;
+ n->partby = $13;
+ n->ofTypename = NULL;
+ n->constraints = NIL;
+ n->options = $14;
+ n->oncommit = $15;
+ n->tablespacename = $16;
+ n->if_not_exists = true;
+ $$ = (Node *)n;
+ }
;
/*
@@ -2930,6 +3064,11 @@ OptTypedTableElementList:
| /*EMPTY*/ { $$ = NIL; }
;
+OptPartitionElementList:
+ '(' PartitionElementList ')' { $$ = $2; }
+ | /*EMPTY*/ { $$ = NIL; }
+ ;
+
TableElementList:
TableElement
{
@@ -2952,6 +3091,17 @@ TypedTableElementList:
}
;
+PartitionElementList:
+ PartitionElement
+ {
+ $$ = list_make1($1);
+ }
+ | PartitionElementList ',' PartitionElement
+ {
+ $$ = lappend($1, $3);
+ }
+ ;
+
TableElement:
columnDef { $$ = $1; }
| TableLikeClause { $$ = $1; }
@@ -2963,6 +3113,11 @@ TypedTableElement:
| TableConstraint { $$ = $1; }
;
+PartitionElement:
+ columnOptions { $$ = $1; }
+ | TableConstraint { $$ = $1; }
+ ;
+
columnDef: ColId Typename create_generic_options ColQualList
{
ColumnDef *n = makeNode(ColumnDef);
@@ -2972,6 +3127,7 @@ columnDef: ColId Typename create_generic_options ColQualList
n->is_local = true;
n->is_not_null = false;
n->is_from_type = false;
+ n->is_for_partition = false;
n->storage = 0;
n->raw_default = NULL;
n->cooked_default = NULL;
@@ -2993,6 +3149,7 @@ columnOptions: ColId WITH OPTIONS ColQualList
n->is_local = true;
n->is_not_null = false;
n->is_from_type = false;
+ n->is_for_partition = false;
n->storage = 0;
n->raw_default = NULL;
n->cooked_default = NULL;
@@ -4544,6 +4701,48 @@ CreateForeignTableStmt:
n->options = $14;
$$ = (Node *) n;
}
+ | CREATE FOREIGN TABLE qualified_name
+ PARTITION OF qualified_name OptPartitionElementList ForValues
+ SERVER name create_generic_options
+ {
+ CreateForeignTableStmt *n = makeNode(CreateForeignTableStmt);
+ $4->relpersistence = RELPERSISTENCE_PERMANENT;
+ n->base.relation = $4;
+ n->base.inhRelations = list_make1($7);
+ n->base.tableElts = $8;
+ n->base.partbound = (Node *) $9;
+ n->base.ofTypename = NULL;
+ n->base.constraints = NIL;
+ n->base.options = NIL;
+ n->base.oncommit = ONCOMMIT_NOOP;
+ n->base.tablespacename = NULL;
+ n->base.if_not_exists = false;
+ /* FDW-specific data */
+ n->servername = $11;
+ n->options = $12;
+ $$ = (Node *) n;
+ }
+ | CREATE FOREIGN TABLE IF_P NOT EXISTS qualified_name
+ PARTITION OF qualified_name OptPartitionElementList ForValues
+ SERVER name create_generic_options
+ {
+ CreateForeignTableStmt *n = makeNode(CreateForeignTableStmt);
+ $7->relpersistence = RELPERSISTENCE_PERMANENT;
+ n->base.relation = $7;
+ n->base.inhRelations = list_make1($10);
+ n->base.tableElts = $11;
+ n->base.tableElts = (Node *) $12;
+ n->base.ofTypename = NULL;
+ n->base.constraints = NIL;
+ n->base.options = NIL;
+ n->base.oncommit = ONCOMMIT_NOOP;
+ n->base.tablespacename = NULL;
+ n->base.if_not_exists = true;
+ /* FDW-specific data */
+ n->servername = $14;
+ n->options = $15;
+ $$ = (Node *) n;
+ }
;
/*****************************************************************************
@@ -11133,6 +11332,7 @@ TableFuncElement: ColId Typename opt_collate_clause
n->is_local = true;
n->is_not_null = false;
n->is_from_type = false;
+ n->is_for_partition = false;
n->storage = 0;
n->raw_default = NULL;
n->cooked_default = NULL;
@@ -13816,6 +14016,7 @@ unreserved_keyword:
| ASSERTION
| ASSIGNMENT
| AT
+ | ATTACH
| ATTRIBUTE
| BACKWARD
| BEFORE
@@ -13862,6 +14063,7 @@ unreserved_keyword:
| DELIMITER
| DELIMITERS
| DEPENDS
+ | DETACH
| DICTIONARY
| DISABLE_P
| DISCARD
@@ -13904,6 +14106,7 @@ unreserved_keyword:
| IMPLICIT_P
| IMPORT_P
| INCLUDING
+ | INCLUSIVE
| INCREMENT
| INDEX
| INDEXES
diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c
index 3e8d457..142d60b 100644
--- a/src/backend/parser/parse_agg.c
+++ b/src/backend/parser/parse_agg.c
@@ -508,7 +508,9 @@ check_agglevels_and_constraints(ParseState *pstate, Node *expr)
err = _("grouping operations are not allowed in partition key expression");
break;
-
+ case EXPR_KIND_PARTITION_FOR_VALUES:
+ errkind = true;
+ break;
/*
* There is intentionally no default: case here, so that the
@@ -869,6 +871,9 @@ transformWindowFuncCall(ParseState *pstate, WindowFunc *wfunc,
case EXPR_KIND_PARTITION_KEY:
err = _("window functions are not allowed in partition key expression");
break;
+ case EXPR_KIND_PARTITION_FOR_VALUES:
+ errkind = true;
+ break;
/*
* There is intentionally no default: case here, so that the
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 147c6da..f14d776 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -352,6 +352,26 @@ transformExprRecurse(ParseState *pstate, Node *expr)
result = (Node *) expr;
break;
}
+ case T_PartitionListSpec:
+ {
+ PartitionListSpec *list = (PartitionListSpec *) expr;
+
+ list->values = transformExpressionList(pstate, list->values,
+ pstate->p_expr_kind);
+ result = expr;
+ break;
+ }
+ case T_PartitionRangeSpec:
+ {
+ PartitionRangeSpec *range = (PartitionRangeSpec *) expr;
+
+ range->lower = transformExpressionList(pstate, range->lower,
+ pstate->p_expr_kind);
+ range->upper = transformExpressionList(pstate, range->upper,
+ pstate->p_expr_kind);
+ result = expr;
+ break;
+ }
default:
/* should not reach here */
@@ -1698,6 +1718,7 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
case EXPR_KIND_OFFSET:
case EXPR_KIND_RETURNING:
case EXPR_KIND_VALUES:
+ case EXPR_KIND_PARTITION_FOR_VALUES:
/* okay */
break;
case EXPR_KIND_CHECK_CONSTRAINT:
@@ -3254,6 +3275,8 @@ ParseExprKindName(ParseExprKind exprKind)
return "WHEN";
case EXPR_KIND_PARTITION_KEY:
return "partition key expression";
+ case EXPR_KIND_PARTITION_FOR_VALUES:
+ return "FOR VALUES";
/*
* There is intentionally no default: case here, so that the
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index bce3041..8fc3e50 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -48,8 +48,10 @@
#include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "nodes/nodeFuncs.h"
+#include "optimizer/planner.h"
#include "parser/analyze.h"
#include "parser/parse_clause.h"
+#include "parser/parse_coerce.h"
#include "parser/parse_collate.h"
#include "parser/parse_expr.h"
#include "parser/parse_relation.h"
@@ -89,6 +91,7 @@ typedef struct
* the table */
IndexStmt *pkey; /* PRIMARY KEY index, if any */
bool ispartitioned; /* true if table is partitioned */
+ Node *partbound; /* transformed FOR VALUES */
} CreateStmtContext;
/* State shared by transformCreateSchemaStmt and its subroutines */
@@ -131,6 +134,10 @@ static void transformConstraintAttrs(CreateStmtContext *cxt,
List *constraintList);
static void transformColumnType(CreateStmtContext *cxt, ColumnDef *column);
static void setSchemaName(char *context_schema, char **stmt_schema_name);
+static void transformPartitionOf(CreateStmtContext *cxt, Node *bound);
+static void transformAttachPartition(CreateStmtContext *cxt, PartitionCmd *cmd);
+static Node *transformPartitionBound(CreateStmtContext *cxt, Relation parent,
+ Node *bound);
/*
@@ -232,6 +239,7 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
cxt.alist = NIL;
cxt.pkey = NULL;
cxt.ispartitioned = stmt->partby != NULL;
+ cxt.partbound = NULL;
/*
* Notice that we allow OIDs here only for plain tables, even though
@@ -250,6 +258,9 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
if (stmt->ofTypename)
transformOfType(&cxt, stmt->ofTypename);
+ if (stmt->partbound)
+ transformPartitionOf(&cxt, stmt->partbound);
+
if (stmt->partby)
{
int partnatts = list_length(stmt->partby->partParams);
@@ -355,6 +366,7 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
*/
stmt->tableElts = cxt.columns;
stmt->constraints = cxt.ckconstraints;
+ stmt->partbound = cxt.partbound;
result = lappend(cxt.blist, stmt);
result = list_concat(result, cxt.alist);
@@ -894,6 +906,7 @@ transformTableLikeClause(CreateStmtContext *cxt, TableLikeClause *table_like_cla
def->is_local = true;
def->is_not_null = attribute->attnotnull;
def->is_from_type = false;
+ def->is_for_partition = false;
def->storage = 0;
def->raw_default = NULL;
def->cooked_default = NULL;
@@ -1113,6 +1126,7 @@ transformOfType(CreateStmtContext *cxt, TypeName *ofTypename)
n->is_local = true;
n->is_not_null = false;
n->is_from_type = true;
+ n->is_for_partition = false;
n->storage = 0;
n->raw_default = NULL;
n->cooked_default = NULL;
@@ -2580,6 +2594,7 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
cxt.alist = NIL;
cxt.pkey = NULL;
cxt.ispartitioned = relid_is_partitioned(relid);
+ cxt.partbound = NULL;
/*
* The only subtypes that currently require parse transformation handling
@@ -2668,6 +2683,22 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
break;
}
+ case AT_AttachPartition:
+ {
+ PartitionCmd *partcmd = (PartitionCmd *) cmd->def;
+
+ transformAttachPartition(&cxt, partcmd);
+
+ /* assign transformed values */
+ partcmd->bound = cxt.partbound;
+ }
+
+ newcmds = lappend(newcmds, cmd);
+ break;
+ case AT_DetachPartition:
+ newcmds = lappend(newcmds, cmd);
+ break;
+
default:
newcmds = lappend(newcmds, cmd);
break;
@@ -3032,3 +3063,290 @@ setSchemaName(char *context_schema, char **stmt_schema_name)
"different from the one being created (%s)",
*stmt_schema_name, context_schema)));
}
+
+/*
+ * transformPartitionOf
+ * Analyze PARTITION OF ... FOR VALUES ...
+ */
+static void
+transformPartitionOf(CreateStmtContext *cxt, Node *bound)
+{
+ TupleDesc tupdesc;
+ int i;
+ Relation parentRel;
+ RangeVar *partof = linitial(cxt->inhRelations);
+
+ parentRel = heap_openrv(partof, AccessShareLock);
+
+ /* Check if the target table is partitioned at all */
+ if (parentRel->rd_rel->relkind != RELKIND_PARTITIONED_REL)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("\"%s\" is not partitioned",
+ RelationGetRelationName(parentRel))));
+ Assert(RelationGetPartitionKey(parentRel) != NULL);
+
+ /* If parent rel is temp, it must belong to this session */
+ if (parentRel->rd_rel->relpersistence == RELPERSISTENCE_TEMP &&
+ !parentRel->rd_islocaltemp)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot create as partition of temporary relation of another session")));
+
+ /*
+ * Do not allow OIDs in a partition, if not present in the parent. But
+ * force them in partition, if they are present in the parent.
+ */
+ if (cxt->hasoids && !parentRel->rd_rel->relhasoids)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot create table with OIDs as partition of table without OIDs")));
+
+ if (parentRel->rd_rel->relhasoids)
+ cxt->hasoids = true;
+
+ tupdesc = RelationGetDescr(parentRel);
+ for (i = 0; i < tupdesc->natts; i++)
+ {
+ Form_pg_attribute attribute = tupdesc->attrs[i];
+ ColumnDef *def;
+
+ if (attribute->attisdropped)
+ continue;
+
+ def = makeNode(ColumnDef);
+ def->colname = pstrdup(NameStr(attribute->attname));
+ def->typeName = makeTypeNameFromOid(attribute->atttypid,
+ attribute->atttypmod);
+ def->inhcount = 1;
+ def->is_local = false;
+ def->is_not_null = attribute->attnotnull;
+ def->is_from_type = false;
+ def->is_for_partition = true;
+ def->storage = attribute->attstorage;
+ def->raw_default = NULL;
+ def->cooked_default = NULL;
+ def->collClause = NULL;
+ def->collOid = attribute->attcollation;
+ def->constraints = NIL;
+ def->location = -1;
+
+ cxt->columns = lappend(cxt->columns, def);
+ }
+
+ /* tranform the values */
+ cxt->partbound = transformPartitionBound(cxt, parentRel, bound);
+
+ heap_close(parentRel, AccessShareLock);
+}
+
+/*
+ * transformAttachPartition
+ * Analyze ATTACH PARTITION ... FOR VALUES ...
+ */
+static void
+transformAttachPartition(CreateStmtContext *cxt, PartitionCmd *cmd)
+{
+ Relation parentRel = cxt->rel;
+
+ /* Check if the target table is partitioned at all */
+ if (parentRel->rd_rel->relkind != RELKIND_PARTITIONED_REL)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("\"%s\" is not partitioned", cxt->relation->relname)));
+ Assert(RelationGetPartitionKey(parentRel) != NULL);
+
+ /* tranform the values */
+ cxt->partbound = transformPartitionBound(cxt, parentRel, cmd->bound);
+}
+
+/*
+ * transformPartitionBound
+ *
+ * Transform partition bound per the partition key
+ */
+static Node *
+transformPartitionBound(CreateStmtContext *cxt, Relation parent, Node *bound)
+{
+ int i;
+ ListCell *cell;
+ Oid parentId = RelationGetRelid(parent);
+ PartitionKey key = RelationGetPartitionKey(parent);
+ PartitionListSpec *list, *result_list;
+ PartitionRangeSpec *range, *result_range;
+
+ switch (key->strategy)
+ {
+ case PARTITION_STRAT_LIST:
+ if (!IsA(bound, PartitionListSpec))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("invalid bound specification for a list partition"),
+ parser_errposition(cxt->pstate, exprLocation(bound))));
+
+ list = (PartitionListSpec *) transformExpr(cxt->pstate, bound,
+ EXPR_KIND_PARTITION_FOR_VALUES);
+
+ result_list = makeNode(PartitionListSpec);
+
+ foreach(cell, list->values)
+ {
+ Node *value = (Node *) lfirst(cell);
+ Node *orig_value = value;
+ Oid valuetype;
+
+ valuetype = exprType(value);
+ value = coerce_to_target_type(cxt->pstate,
+ value, valuetype,
+ key->tcinfo->typid[0],
+ key->tcinfo->typmod[0],
+ COERCION_ASSIGNMENT,
+ COERCE_IMPLICIT_CAST,
+ -1);
+ if (value == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("specified value cannot be cast to type \"%s\""
+ " of key column \"%s\"",
+ format_type_be(key->tcinfo->typid[0]),
+ get_relid_attribute_name(parentId, key->partattrs[0])),
+ parser_errposition(cxt->pstate, exprLocation(orig_value))));
+
+ /* Simplify the expression */
+ value = (Node *) expression_planner((Expr *) value);
+
+ result_list->values = lappend(result_list->values, value);
+ }
+ return (Node *) result_list;
+
+ case PARTITION_STRAT_RANGE:
+ if (!IsA(bound, PartitionRangeSpec))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("invalid bound specification for a range partition"),
+ parser_errposition(cxt->pstate, exprLocation(bound))));
+
+ range = (PartitionRangeSpec *) transformExpr(cxt->pstate, bound,
+ EXPR_KIND_PARTITION_FOR_VALUES);
+
+ if (!range->lower && !range->upper)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("both START and END cannot be UNBOUNDED"),
+ parser_errposition(cxt->pstate, range->location)));
+
+ result_range = makeNode(PartitionRangeSpec);
+ result_range->lowerinc = range->lowerinc;
+ result_range->upperinc = range->upperinc;
+
+ if (range->lower)
+ {
+ if (list_length(range->lower) > key->partnatts)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("START has more values specified than number"
+ " of columns in the partition key"),
+ parser_errposition(cxt->pstate,
+ exprLocation(list_nth(range->lower,
+ list_length(range->lower) - 1)))));
+ else if (list_length(range->lower) < key->partnatts)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("START has fewer values specified than number"
+ " of columns in the partition key"),
+ parser_errposition(cxt->pstate,
+ exprLocation(list_nth(range->lower,
+ list_length(range->lower) - 1)))));
+
+ i = 0;
+ foreach (cell, range->lower)
+ {
+ Node *value = (Node *) lfirst(cell);
+ Node *orig_value = value;
+ Oid valuetype;
+
+ valuetype = exprType(value);
+ value = coerce_to_target_type(cxt->pstate,
+ value, valuetype,
+ key->tcinfo->typid[i],
+ key->tcinfo->typmod[i],
+ COERCION_ASSIGNMENT,
+ COERCE_IMPLICIT_CAST,
+ -1);
+ if (value == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("specified value cannot be cast to type"
+ " \"%s\" of key column \"%s\"",
+ format_type_be(key->tcinfo->typid[i]),
+ get_relid_attribute_name(parentId, key->partattrs[i])),
+ parser_errposition(cxt->pstate, exprLocation(orig_value))));
+
+ /* Simplify the expression */
+ value = (Node *) expression_planner((Expr *) value);
+
+ result_range->lower = lappend(result_range->lower, value);
+ ++i;
+ }
+ }
+ else
+ result_range->lowerinc = false;
+
+ if (range->upper)
+ {
+ if (list_length(range->upper) > key->partnatts)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("END has more values specified than number"
+ " of columns in the partition key"),
+ parser_errposition(cxt->pstate,
+ exprLocation(list_nth(range->upper,
+ list_length(range->upper) - 1)))));
+ else if (list_length(range->upper) < key->partnatts)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("END has fewer values specified than number"
+ " of columns in the partition key"),
+ parser_errposition(cxt->pstate,
+ exprLocation(list_nth(range->upper,
+ list_length(range->upper) - 1)))));
+
+ i = 0;
+ foreach (cell, range->upper)
+ {
+ Node *value = (Node *) lfirst(cell);
+ Node *orig_value = value;
+ Oid valuetype;
+
+ valuetype = exprType(value);
+ value = coerce_to_target_type(cxt->pstate,
+ value, valuetype,
+ key->tcinfo->typid[i],
+ key->tcinfo->typmod[i],
+ COERCION_ASSIGNMENT,
+ COERCE_IMPLICIT_CAST,
+ -1);
+ if (value == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("specified value cannot be cast to type"
+ " \"%s\" of key column \"%s\"",
+ format_type_be(key->tcinfo->typid[i]),
+ get_relid_attribute_name(parentId, key->partattrs[i])),
+ parser_errposition(cxt->pstate, exprLocation(orig_value))));
+
+ /* Simplify the expression */
+ value = (Node *) expression_planner((Expr *) value);
+
+ result_range->upper = lappend(result_range->upper, value);
+ ++i;
+ }
+ }
+ else
+ result_range->upperinc = false;
+
+ return (Node *) result_range;
+ }
+
+ return NULL;
+}
diff --git a/src/include/catalog/partition.h b/src/include/catalog/partition.h
index e1d8030..b3f3a1b 100644
--- a/src/include/catalog/partition.h
+++ b/src/include/catalog/partition.h
@@ -14,6 +14,7 @@
#define PARTITION_H
#include "fmgr.h"
+#include "parser/parse_node.h"
#include "utils/relcache.h"
/* Type and collation information for partition key columns */
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index c316af6..365073b 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -405,6 +405,7 @@ typedef enum NodeTag
T_AlterPolicyStmt,
T_CreateTransformStmt,
T_CreateAmStmt,
+ T_PartitionCmd,
/*
* TAGS FOR PARSE TREE NODES (parsenodes.h)
@@ -454,6 +455,8 @@ typedef enum NodeTag
T_RoleSpec,
T_PartitionElem,
T_PartitionBy,
+ T_PartitionListSpec,
+ T_PartitionRangeSpec,
/*
* TAGS FOR REPLICATION GRAMMAR PARSE NODES (replnodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index fe81f83..129f0b6 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -591,6 +591,7 @@ typedef struct ColumnDef
bool is_local; /* column has local (non-inherited) def'n */
bool is_not_null; /* NOT NULL constraint specified? */
bool is_from_type; /* column definition came from table type */
+ bool is_for_partition; /* column definition is for a partition */
char storage; /* attstorage setting, or 0 for default */
Node *raw_default; /* default value (untransformed parse tree) */
Node *cooked_default; /* default value (transformed expr tree) */
@@ -731,6 +732,40 @@ typedef struct PartitionBy
int location; /* token location, or -1 if unknown */
} PartitionBy;
+/*
+ * PartitionRangeSpec - range type syntax for range partition bound
+ */
+typedef struct PartitionListSpec
+{
+ NodeTag type;
+ List *values;
+ int location;
+} PartitionListSpec;
+
+/*
+ * PartitionRangeSpec - range type syntax for range partition bound
+ */
+typedef struct PartitionRangeSpec
+{
+ NodeTag type;
+ bool lowerinc;
+ List *lower;
+ bool upperinc;
+ List *upper;
+ int location; /* token location, or -1 if unknown */
+} PartitionRangeSpec;
+
+/*
+ * PartitionCmd - ALTER TABLE partition commands
+ */
+typedef struct PartitionCmd
+{
+ NodeTag type;
+ RangeVar *name;
+ Node *bound;
+ bool skip_validate;
+} PartitionCmd;
+
/****************************************************************************
* Nodes for a Query tree
****************************************************************************/
@@ -1558,7 +1593,9 @@ typedef enum AlterTableType
AT_DisableRowSecurity, /* DISABLE ROW SECURITY */
AT_ForceRowSecurity, /* FORCE ROW SECURITY */
AT_NoForceRowSecurity, /* NO FORCE ROW SECURITY */
- AT_GenericOptions /* OPTIONS (...) */
+ AT_GenericOptions, /* OPTIONS (...) */
+ AT_AttachPartition, /* ATTACH PARTITION */
+ AT_DetachPartition /* DETACH PARTITION */
} AlterTableType;
typedef struct ReplicaIdentityStmt
@@ -1783,7 +1820,9 @@ typedef struct CreateStmt
RangeVar *relation; /* relation to create */
List *tableElts; /* column definitions (list of ColumnDef) */
List *inhRelations; /* relations to inherit from (list of
- * inhRelation) */
+ * inhRelation); (ab)used also as partition
+ * parent */
+ Node *partbound; /* FOR VALUES clause */
PartitionBy *partby; /* PARTITION BY clause */
TypeName *ofTypename; /* OF typename */
List *constraints; /* constraints (list of Constraint nodes) */
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 40da67a..70c264c 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -49,6 +49,7 @@ PG_KEYWORD("assertion", ASSERTION, UNRESERVED_KEYWORD)
PG_KEYWORD("assignment", ASSIGNMENT, UNRESERVED_KEYWORD)
PG_KEYWORD("asymmetric", ASYMMETRIC, RESERVED_KEYWORD)
PG_KEYWORD("at", AT, UNRESERVED_KEYWORD)
+PG_KEYWORD("attach", ATTACH, UNRESERVED_KEYWORD)
PG_KEYWORD("attribute", ATTRIBUTE, UNRESERVED_KEYWORD)
PG_KEYWORD("authorization", AUTHORIZATION, TYPE_FUNC_NAME_KEYWORD)
PG_KEYWORD("backward", BACKWARD, UNRESERVED_KEYWORD)
@@ -127,6 +128,7 @@ PG_KEYWORD("delimiter", DELIMITER, UNRESERVED_KEYWORD)
PG_KEYWORD("delimiters", DELIMITERS, UNRESERVED_KEYWORD)
PG_KEYWORD("depends", DEPENDS, UNRESERVED_KEYWORD)
PG_KEYWORD("desc", DESC, RESERVED_KEYWORD)
+PG_KEYWORD("detach", DETACH, UNRESERVED_KEYWORD)
PG_KEYWORD("dictionary", DICTIONARY, UNRESERVED_KEYWORD)
PG_KEYWORD("disable", DISABLE_P, UNRESERVED_KEYWORD)
PG_KEYWORD("discard", DISCARD, UNRESERVED_KEYWORD)
@@ -191,6 +193,7 @@ PG_KEYWORD("implicit", IMPLICIT_P, UNRESERVED_KEYWORD)
PG_KEYWORD("import", IMPORT_P, UNRESERVED_KEYWORD)
PG_KEYWORD("in", IN_P, RESERVED_KEYWORD)
PG_KEYWORD("including", INCLUDING, UNRESERVED_KEYWORD)
+PG_KEYWORD("inclusive", INCLUSIVE, UNRESERVED_KEYWORD)
PG_KEYWORD("increment", INCREMENT, UNRESERVED_KEYWORD)
PG_KEYWORD("index", INDEX, UNRESERVED_KEYWORD)
PG_KEYWORD("indexes", INDEXES, UNRESERVED_KEYWORD)
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index a13c6fb..3d45663 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -65,7 +65,8 @@ typedef enum ParseExprKind
EXPR_KIND_EXECUTE_PARAMETER, /* parameter value in EXECUTE */
EXPR_KIND_TRIGGER_WHEN, /* WHEN condition in CREATE TRIGGER */
EXPR_KIND_POLICY, /* USING or WITH CHECK expr in policy */
- EXPR_KIND_PARTITION_KEY /* partition key expression */
+ EXPR_KIND_PARTITION_KEY, /* partition key expression */
+ EXPR_KIND_PARTITION_FOR_VALUES /* partition bound */
} ParseExprKind;
--
1.7.1
[text/x-diff] 0005-Infrastructure-for-partition-metadata-storage-and-ma-v7.patch (103.5K, ../../81371428-bb4b-1e33-5ad6-8c5c51b52cb7@lab.ntt.co.jp/6-0005-Infrastructure-for-partition-metadata-storage-and-ma-v7.patch)
download | inline diff:
From a15296fbbddd6fa495940ada6aff562c6de6d0fa Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Fri, 18 Mar 2016 15:54:17 +0900
Subject: [PATCH 5/6] Infrastructure for partition metadata storage and manipulation.
Catalog pg_partition with:
partrelid oid
partbound pg_node_tree
Now since partition metadata is stored in a dedicated catalog, it is possible
to use that knowledge to enforce special behaviors for inheritance children
(or relations in general) that are partitions. Some such behaviors include:
* Cannot drop using DROP TABLE without first "detaching" from its parent
* Cannot add, drop new columns
* Cannot rename, change type of existing columns
It also becomes now possible to check, from the FOR VALUES specification, that
the new partition (in CREATE TABLE PARTITION OF parent or ALTER TABLE parent
ATTACH PARTITION) does not overlap with any existing partition of parent.
A partition's check expression (that is, an expression tree that can be used
a check constraint for the rows in it) can be obtained with
RelationGetPartitionCheckExpr(). ExecConstraints() now checks using that
that expression, whether a row being inserted or new row value in update case
violate the partition boundary specification. Also, constraint exclusion
based partition-pruning is done using them.
When attaching a partition, rows in the table are checked whether any violates
the partition bound specification (AND'd with the parent's check expression,
if parent itself is a partition), unless NO VALIDATE clause is specified with
the command.
This commit also invents an internal representation of partition bounds in
the form of structs PartitionListBound and PartitionRangeBound, which consist
of datums and information like number of values in a list, booleans for
whether a range bound is infinite, inclusive (vs. exclusive), lower bound
(vs. upper bound), etc. "Partition descriptor" of a partitioned table
(struct PartitionDesc) consists of an array of oids of partitions, array
of pointers to PartitionListBound structs (NULL if not list partitioned,
and 2 arrays of pointers to PartitionRangeBound structs, (each NULL,
if not range partitioned).
One more data structure that this commit invents for future use is struct
PartitionDescNode. It represents a node in partition tree where each node
of the tree corresponds to a partitioned table in the tree. The tree
structure is such that a parent node links to the first child node (which
corresponds with the first partition of the parent that is partitioned
itself), which in turn, links to its right sibling (the next partition
that is partitioned) and so on. Each node consists of some book-keeping
information including a pointer to PartitionDesc of the corresponding table
and recursive pointers just mentioned viz. downlink and next.
---
doc/src/sgml/catalogs.sgml | 56 ++
src/backend/catalog/Makefile | 2 +-
src/backend/catalog/heap.c | 6 +
src/backend/catalog/partition.c | 1361 ++++++++++++++++++++++++++++
src/backend/commands/copy.c | 2 +-
src/backend/commands/lockcmds.c | 4 +-
src/backend/commands/tablecmds.c | 206 ++++-
src/backend/executor/execMain.c | 75 ++-
src/backend/executor/nodeModifyTable.c | 4 +-
src/backend/nodes/readfuncs.c | 33 +
src/backend/optimizer/util/plancat.c | 13 +
src/backend/parser/analyze.c | 1 +
src/backend/parser/parse_clause.c | 4 +-
src/backend/parser/parse_relation.c | 3 +-
src/backend/parser/parse_utilcmd.c | 6 +
src/backend/utils/adt/ruleutils.c | 78 ++
src/backend/utils/cache/relcache.c | 290 ++++++
src/backend/utils/cache/syscache.c | 12 +
src/bin/pg_dump/common.c | 86 ++
src/bin/pg_dump/pg_dump.c | 98 ++-
src/bin/pg_dump/pg_dump.h | 12 +
src/bin/psql/describe.c | 46 +-
src/include/catalog/indexing.h | 3 +
src/include/catalog/partition.h | 85 ++
src/include/catalog/pg_partition.h | 54 ++
src/include/nodes/execnodes.h | 4 +
src/include/utils/rel.h | 9 +
src/include/utils/syscache.h | 1 +
src/test/regress/expected/sanity_check.out | 1 +
29 files changed, 2519 insertions(+), 36 deletions(-)
create mode 100644 src/include/catalog/pg_partition.h
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index dfc55ec..2e18089 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -226,6 +226,11 @@
</row>
<row>
+ <entry><link linkend="catalog-pg-partition"><structname>pg_partition</structname></link></entry>
+ <entry>information about partitions</entry>
+ </row>
+
+ <row>
<entry><link linkend="catalog-pg-partitioned"><structname>pg_partitioned</structname></link></entry>
<entry>information about partitioned tables, including the partition key</entry>
</row>
@@ -4673,6 +4678,57 @@
</sect1>
+ <sect1 id="catalog-pg-partition">
+ <title><structname>pg_partition</structname></title>
+
+ <indexterm zone="catalog-pg-partition">
+ <primary>pg_partition</primary>
+ </indexterm>
+
+ <para>
+ The catalog <structname>pg_partition</structname> stores partition bounds
+ of tables that are partitions.
+ </para>
+
+ <table>
+ <title><structname>pg_partition</> Columns</title>
+
+ <tgroup cols="4">
+ <thead>
+ <row>
+ <entry>Name</entry>
+ <entry>Type</entry>
+ <entry>References</entry>
+ <entry>Description</entry>
+ </row>
+ </thead>
+
+ <tbody>
+
+ <row>
+ <entry><structfield>partrelid</structfield></entry>
+ <entry><type>oid</type></entry>
+ <entry><literal><link linkend="catalog-pg-class"><structname>pg_class</structname></link>.oid</literal></entry>
+ <entry>The OID of the <structname>pg_class</> entry for this partition</entry>
+ </row>
+
+ <row>
+ <entry><structfield>partbound</structfield></entry>
+ <entry><type>pg_node_tree</type></entry>
+ <entry></entry>
+ <entry>
+ Expression tree (in <function>nodeToString()</function> representation)
+ for partition boundary value. For list partitions, it is simply a list
+ of values. For range partitions, it consists of lower and upper bound
+ values along with their inclusivity.
+ </entry>
+ </row>
+
+ </tbody>
+ </tgroup>
+ </table>
+ </sect1>
+
<sect1 id="catalog-pg-partitioned">
<title><structname>pg_partitioned</structname></title>
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index 4dc6300..ee1be31 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -41,7 +41,7 @@ POSTGRES_BKI_SRCS = $(addprefix $(top_srcdir)/src/include/catalog/,\
pg_foreign_data_wrapper.h pg_foreign_server.h pg_user_mapping.h \
pg_foreign_table.h pg_policy.h pg_replication_origin.h \
pg_default_acl.h pg_init_privs.h pg_seclabel.h pg_shseclabel.h \
- pg_collation.h pg_range.h pg_transform.h pg_partitioned.h\
+ pg_collation.h pg_range.h pg_transform.h pg_partitioned.h pg_partition.h\
toasting.h indexing.h \
)
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 932a48b..2e99661 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -1808,7 +1808,13 @@ heap_drop_with_catalog(Oid relid)
* If a partitioned table, delete the pg_partitioned tuples.
*/
if (rel->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+ {
RemovePartitionKeyByRelId(relid);
+ RelationDropPartitions(relid);
+ }
+
+ if (relid_is_partition(relid))
+ RemovePartitionEntryByRelId(relid);
/*
* Schedule unlinking of the relation's physical files at commit.
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index 3fa4d68..3404823 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -18,19 +18,26 @@
#include "access/heapam.h"
#include "access/htup_details.h"
#include "access/nbtree.h"
+#include "access/sysattr.h"
#include "catalog/dependency.h"
#include "catalog/indexing.h"
#include "catalog/objectaddress.h"
#include "catalog/partition.h"
#include "catalog/pg_collation.h"
+#include "catalog/pg_inherits.h"
+#include "catalog/pg_inherits_fn.h"
#include "catalog/pg_opclass.h"
#include "catalog/pg_partitioned.h"
+#include "catalog/pg_partition.h"
#include "catalog/pg_type.h"
#include "executor/executor.h"
#include "miscadmin.h"
+#include "nodes/makefuncs.h"
#include "nodes/nodeFuncs.h"
+#include "nodes/parsenodes.h"
#include "optimizer/clauses.h"
#include "optimizer/planmain.h"
+#include "optimizer/var.h"
#include "storage/lmgr.h"
#include "utils/array.h"
#include "utils/builtins.h"
@@ -39,12 +46,55 @@
#include "utils/fmgroids.h"
#include "utils/inval.h"
#include "utils/lsyscache.h"
+#include "utils/rel.h"
#include "utils/syscache.h"
+typedef struct translate_var_attno_mutator_context
+{
+ AttrNumber old_attno;
+ AttrNumber new_attno;
+} translate_var_attno_mutator_context;
+
static PartitionKey CopyPartitionKey(PartitionKey fromkey);
static KeyTypeCollInfo *copy_key_type_coll_info(int nkeycols,
KeyTypeCollInfo *tcinfo);
static void free_key_type_coll_info(KeyTypeCollInfo *tcinfo);
+static bool list_overlaps_existing_partition(PartitionKey key,
+ PartitionListSpec *list_spec,
+ PartitionDesc pdesc,
+ Oid *with);
+static bool partition_range_empty(PartitionKey key,
+ PartitionRangeSpec *range_spec);
+static int32 compare_range_keys(PartitionKey key, Datum *val1, Datum *val2);
+static bool range_overlaps_existing_partition(PartitionKey key,
+ PartitionRangeSpec *range_spec,
+ PartitionDesc pdesc,
+ Oid *with);
+static PartitionListBound *make_list_from_spec(PartitionKey key, List *values);
+static PartitionRangeBound *make_range_from_spec(PartitionKey key, List *val,
+ bool inclusive, bool lower);
+static bool partition_range_overlaps(PartitionKey key,
+ PartitionRangeBound *part1_lower,
+ PartitionRangeBound *part1_upper,
+ PartitionRangeBound *part2_lower,
+ PartitionRangeBound *part2_upper);
+
+static List *get_partitions(Oid relid, int lockmode);
+
+static List *get_check_qual_for_list(PartitionKey key, List *values);
+static List *get_check_qual_for_range(PartitionKey key,
+ List *lowervalues,
+ bool lowerinc,
+ List *uppervalues,
+ bool upperinc);
+static Node *translate_var_attno(Node *expr, AttrNumber attno,
+ AttrNumber new_attno);
+static Node *translate_var_attno_mutator(Node *node,
+ translate_var_attno_mutator_context *cxt);
+static List *generate_partition_check_qual(Relation rel);
+
+static PartitionDescNode GetPartitionDescNodeRecurse(Relation rel, int offset);
+static int get_leaf_partition_count(PartitionDescNode pdnode);
/*
* StorePartitionKey
@@ -455,3 +505,1314 @@ FreePartitionKey(PartitionKey key)
free_key_type_coll_info(key->tcinfo);
pfree(key);
}
+
+/*
+ * StorePartitionBound
+ *
+ * Store partition boundary info in pg_partition after checking for its
+ * validity.
+ */
+void
+StorePartitionBound(Oid relid, Oid parentId, Node *bound)
+{
+ Relation pg_partition;
+ Relation parent;
+ PartitionKey key;
+ PartitionDesc pdesc;
+ Oid with;
+ HeapTuple tuple;
+ char *boundString;
+ Datum values[Natts_pg_partition];
+ bool nulls[Natts_pg_partition];
+ ParseState *pstate = make_parsestate(NULL);
+
+ parent = heap_open(parentId, AccessShareLock);
+ key = RelationGetPartitionKey(parent);
+ pdesc = RelationGetPartitionDesc(parent);
+
+ /*
+ * Check if 'bound' happens to overlap with any of parent's existing
+ * partitions.
+ */
+ switch (key->strategy)
+ {
+ case PARTITION_STRAT_LIST:
+ {
+ PartitionListSpec *list;
+
+ Assert(IsA(bound, PartitionListSpec));
+ list = (PartitionListSpec *) bound;
+ if (list_overlaps_existing_partition(key, list, pdesc, &with))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("new partition's list of values overlaps with that"
+ " of partition \"%s\" of \"%s\"",
+ get_rel_name(with), get_rel_name(parentId)),
+ errhint("Please specify a list of values that does not"
+ " overlap with any existing list partition's list."),
+ parser_errposition(pstate, list->location)));
+ break;
+ }
+
+ case PARTITION_STRAT_RANGE:
+ {
+ PartitionRangeSpec *range;
+
+ Assert(IsA(bound, PartitionRangeSpec));
+ range = (PartitionRangeSpec *) bound;
+ if (partition_range_empty(key, range))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("new partition's range contains no element"),
+ errhint("Please specify a non-empty range."),
+ parser_errposition(pstate, range->location)));
+
+ if (range_overlaps_existing_partition(key, range, pdesc, &with))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("new partition's range overlaps with that of"
+ " partition \"%s\" of \"%s\"",
+ get_rel_name(with), get_rel_name(parentId)),
+ errhint("Please specify a range that does not overlap"
+ " with any existing partition's range."),
+ parser_errposition(pstate, range->location)));
+ break;
+ }
+ }
+
+ pg_partition = heap_open(PartitionRelationId, RowExclusiveLock);
+
+ boundString = nodeToString(bound);
+
+ /* Build the tuple - bound can never be null */
+ MemSet(nulls, false, sizeof(nulls));
+
+ values[Anum_pg_partition_partrelid - 1] = ObjectIdGetDatum(relid);
+ values[Anum_pg_partition_partbound - 1] = CStringGetTextDatum(boundString);
+
+ tuple = heap_form_tuple(RelationGetDescr(pg_partition), values, nulls);
+
+ simple_heap_insert(pg_partition, tuple);
+
+ /* Update the indexes on pg_partition */
+ CatalogUpdateIndexes(pg_partition, tuple);
+
+ /*
+ * Invalid the parent's relcache so that the partition is now included in
+ * its partition descriptor.
+ */
+ CacheInvalidateRelcache(parent);
+
+ heap_close(parent, AccessShareLock);
+ heap_close(pg_partition, RowExclusiveLock);
+}
+
+/*
+ * Does new list partition 'bound' overlap any of existing list partitions?
+ */
+static bool
+list_overlaps_existing_partition(PartitionKey key,
+ PartitionListSpec *list_spec,
+ PartitionDesc pdesc,
+ Oid *with)
+{
+ PartitionListBound *new_list;
+ int i;
+
+ if (pdesc->nparts == 0)
+ return false;
+
+ new_list = make_list_from_spec(key, list_spec->values);
+
+ for (i = 0; i < pdesc->nparts; i++)
+ {
+ int j;
+
+ /* Only one of the partitions can contain nulls */
+ if (pdesc->lists[i]->contains_null && new_list->contains_null)
+ return true;
+
+ for (j = 0; j < pdesc->lists[i]->nvalues; j++)
+ {
+ int k;
+
+ for (k = 0; k < new_list->nvalues; k++)
+ {
+ int32 result;
+
+ result = DatumGetInt32(FunctionCall2Coll(&key->partsupfunc[0],
+ key->tcinfo->typcoll[0],
+ pdesc->lists[i]->values[j],
+ new_list->values[k]));
+ if (!result)
+ {
+ *with = pdesc->oids[i];
+ return true;
+ }
+ }
+ }
+ }
+
+ return false;
+}
+
+/*
+ * Is new partition's range empty?
+ */
+static bool
+partition_range_empty(PartitionKey key, PartitionRangeSpec *range_spec)
+{
+ PartitionRangeBound *lower,
+ *upper;
+
+ lower = make_range_from_spec(key, range_spec->lower, range_spec->lowerinc,
+ true);
+ upper = make_range_from_spec(key, range_spec->upper, range_spec->upperinc,
+ false);
+
+ /*
+ * Only either lower or upper is allowed to be unbounded, which if
+ * one of them is, the range is not empty.
+ */
+ if (lower->infinite || upper->infinite)
+ return false;
+
+ /*
+ * If upper > lower, ouright empty or if lower = upper and either of
+ * them is exclusive.
+ */
+ if (compare_range_keys(key, upper->val, lower->val) < 0 ||
+ (compare_range_keys(key, lower->val, upper->val) == 0 &&
+ (!lower->inclusive || !upper->inclusive)))
+ return true;
+
+ return false;
+}
+
+/* Compare range composite keys using logic similar to RowCompareExpr */
+static int32
+compare_range_keys(PartitionKey key, Datum *val1, Datum *val2)
+{
+ int32 result;
+ int i;
+
+ for (i = 0; i < key->partnatts; i++)
+ {
+ result = DatumGetInt32(FunctionCall2Coll(&key->partsupfunc[i],
+ key->tcinfo->typcoll[i],
+ val1[i], val2[i]));
+ if (!result)
+ continue;
+ break;
+ }
+
+ return result;
+}
+
+/*
+ * Does new range partition's range overlap any of existing range partitions?
+ */
+static bool
+range_overlaps_existing_partition(PartitionKey key,
+ PartitionRangeSpec *range_spec,
+ PartitionDesc pdesc,
+ Oid *with)
+{
+ int i;
+ PartitionRangeBound *new_lower,
+ *new_upper;
+
+ if (pdesc->nparts == 0)
+ return false;
+
+ /*
+ * Create internal representation of rangelower and rangeupper from
+ * range_spec.
+ */
+ new_lower = make_range_from_spec(key, range_spec->lower, range_spec->lowerinc,
+ true);
+ new_upper = make_range_from_spec(key, range_spec->upper, range_spec->upperinc,
+ false);
+
+ /* Check with existing partitions for overlap */
+ for (i = 0; i < pdesc->nparts; i++)
+ {
+ if (partition_range_overlaps(key, new_lower, new_upper,
+ pdesc->rangelowers[i],
+ pdesc->rangeuppers[i]))
+ {
+ *with = pdesc->oids[i];
+ return true;
+ }
+ }
+
+ return false;
+}
+
+/*
+ * Make a PartitionListBound from list of values using the information in key
+ */
+static PartitionListBound *
+make_list_from_spec(PartitionKey key, List *values)
+{
+ PartitionListBound *list;
+ ListCell *cell;
+ int i;
+
+ list = (PartitionListBound *) palloc0(sizeof(PartitionListBound));
+ list->nvalues = list_length(values);
+ list->values = (Datum *) palloc(list->nvalues * sizeof(Datum));
+
+ i = 0;
+ foreach (cell, values)
+ {
+ Const *val = lfirst(cell);
+
+ if (val->constisnull)
+ list->contains_null = true;
+ else
+ list->values[i] = datumCopy(val->constvalue,
+ key->tcinfo->typbyval[0],
+ key->tcinfo->typlen[0]);
+ i++;
+ }
+
+ return list;
+}
+
+/*
+ * Make a PartitionRangeBound from arguments using the information in key
+ */
+static PartitionRangeBound *
+make_range_from_spec(PartitionKey key, List *val, bool inclusive, bool lower)
+{
+ PartitionRangeBound *range;
+ ListCell *cell;
+
+ range = (PartitionRangeBound *) palloc0(sizeof(PartitionRangeBound));
+ range->infinite = (val == NIL);
+ range->inclusive = inclusive;
+ range->lower = lower;
+
+ if (val)
+ {
+ int i;
+
+ range->val = (Datum *) palloc0(key->partnatts * sizeof(Datum));
+
+ i = 0;
+ foreach (cell, val)
+ {
+ Const *val = lfirst(cell);
+
+ if (val->constisnull)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("cannot specify NULL in range bound")));
+ else
+ range->val[i] = datumCopy(val->constvalue,
+ key->tcinfo->typbyval[i],
+ key->tcinfo->typlen[i]);
+ i++;
+ }
+ }
+
+ return range;
+}
+
+/* Check two range partitions for overlap */
+static bool
+partition_range_overlaps(PartitionKey key,
+ PartitionRangeBound *part1_lower,
+ PartitionRangeBound *part1_upper,
+ PartitionRangeBound *part2_lower,
+ PartitionRangeBound *part2_upper)
+{
+ if (partition_range_cmp_bounds(key, part1_lower, part2_lower) >= 0 &&
+ partition_range_cmp_bounds(key, part1_lower, part2_upper) <= 0)
+ return true;
+
+ if (partition_range_cmp_bounds(key, part2_lower, part1_lower) >= 0 &&
+ partition_range_cmp_bounds(key, part2_lower, part1_upper) <= 0)
+ return true;
+
+ return false;
+}
+
+/* Returns for two range partition bounds whether, b1 <=, =, >= b2 */
+int32
+partition_range_cmp_bounds(PartitionKey key,
+ PartitionRangeBound *b1,
+ PartitionRangeBound *b2)
+{
+ int32 result;
+
+ /*
+ * First, handle cases involving infinity, which don't require invoking
+ * the comparison proc.
+ */
+ if (b1->infinite && b2->infinite)
+ {
+ /*
+ * Both are infinity, so they are equal unless one is lower and the
+ * other not.
+ */
+ if (b1->lower == b2->lower)
+ return 0;
+ else
+ return b1->lower ? -1 : 1;
+ }
+ else if (b1->infinite)
+ return b1->lower ? -1 : 1;
+ else if (b2->infinite)
+ return b2->lower ? 1 : -1;
+
+ /*
+ * Both boundaries are finite, so compare the held values.
+ */
+ result = compare_range_keys(key, b1->val, b2->val);
+
+ /*
+ * If the comparison is anything other than equal, we're done. If they
+ * compare equal though, we still have to consider whether the boundaries
+ * are inclusive or exclusive.
+ */
+ if (result == 0)
+ {
+ if (!b1->inclusive && !b2->inclusive)
+ {
+ /* both are exclusive */
+ if (b1->lower == b2->lower)
+ return 0;
+ else
+ return b1->lower ? 1 : -1;
+ }
+ else if (!b1->inclusive)
+ return b1->lower ? 1 : -1;
+ else if (!b2->inclusive)
+ return b2->lower ? -1 : 1;
+ else
+ {
+ /*
+ * Both are inclusive and the values held are equal, so they are
+ * equal regardless of whether they are upper or lower boundaries,
+ * or a mix.
+ */
+ return 0;
+ }
+ }
+
+ return result;
+}
+
+/*
+ * RemovePartitionByRelId
+ * Remove a pg_partition entry for a partition.
+ */
+void
+RemovePartitionEntryByRelId(Oid relid)
+{
+ Relation rel;
+ HeapTuple tuple;
+
+ rel = heap_open(PartitionRelationId, RowExclusiveLock);
+ tuple = SearchSysCache1(PARTRELID, ObjectIdGetDatum(relid));
+
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "cache lookup failed for partition entry of relation %u",
+ relid);
+
+ simple_heap_delete(rel, &tuple->t_self);
+
+ /* Update the indexes on pg_partitioned_rel */
+ CatalogUpdateIndexes(rel, tuple);
+
+ ReleaseSysCache(tuple);
+ heap_close(rel, RowExclusiveLock);
+}
+
+/*
+ * does relid have pg_partition entry?
+ */
+bool
+relid_is_partition(Oid relid)
+{
+ return SearchSysCacheExists1(PARTRELID, ObjectIdGetDatum(relid));
+}
+
+/*
+ * Returns inheritance parent of relid by scanning pg_inherits
+ */
+Oid
+get_partition_parent(Oid relid)
+{
+ Form_pg_inherits form;
+ Relation catalogRelation;
+ SysScanDesc scan;
+ ScanKeyData key[2];
+ HeapTuple tuple;
+
+ catalogRelation = heap_open(InheritsRelationId, AccessShareLock);
+
+ ScanKeyInit(&key[0],
+ Anum_pg_inherits_inhrelid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(relid));
+ ScanKeyInit(&key[1],
+ Anum_pg_inherits_inhseqno,
+ BTEqualStrategyNumber, F_INT4EQ,
+ Int32GetDatum(1));
+
+ scan = systable_beginscan(catalogRelation, InheritsRelidSeqnoIndexId, true,
+ NULL, 2, key);
+
+ tuple = systable_getnext(scan);
+ Assert(HeapTupleIsValid(tuple));
+
+ form = (Form_pg_inherits) GETSTRUCT(tuple);
+
+ systable_endscan(scan);
+ heap_close(catalogRelation, AccessShareLock);
+
+ return form->inhparent;
+}
+
+/*
+ * RelationDropPartitions
+ * Find and drop all partitions of relation with OID 'relid'
+ */
+void
+RelationDropPartitions(Oid relid)
+{
+ List *partitionOids;
+ ListCell *cell;
+
+ partitionOids = get_partitions(relid, AccessExclusiveLock);
+
+ foreach (cell, partitionOids)
+ {
+ Oid partitionOid = lfirst_oid(cell);
+ ObjectAddress obj;
+
+ obj.classId = RelationRelationId;
+ obj.objectId = partitionOid;
+ obj.objectSubId = 0;
+
+ performDeletion(&obj, DROP_CASCADE, 0);
+ }
+}
+
+/*
+ * get_partition_info
+ * Return information of partitions of rel
+ *
+ * Returns number of partitions in *nparts.
+ */
+PartitionInfo **
+get_partition_info(Relation rel, int *nparts)
+{
+ int i,
+ n;
+ Relation partrel;
+ List *partoids;
+ ListCell *cell;
+ PartitionInfo **parts;
+ PartitionKey key = RelationGetPartitionKey(rel);
+
+ /* Read info from pg_inherits */
+ partoids = get_partitions(RelationGetRelid(rel), NoLock);
+
+ partrel = heap_open(PartitionRelationId, AccessShareLock);
+
+ n = list_length(partoids);
+ parts = (PartitionInfo **) palloc0(n * sizeof(PartitionInfo *));
+
+ i = 0;
+ foreach(cell, partoids)
+ {
+ Oid partrelid = lfirst_oid(cell);
+ HeapTuple tuple;
+ Datum datum;
+ bool isnull;
+ Node *bound;
+
+ tuple = SearchSysCache1(PARTRELID, partrelid);
+
+ /*
+ * Happens when we have created the pg_inherits entry but not the
+ * pg_partition entry yet.
+ */
+ if (!HeapTupleIsValid(tuple))
+ continue;
+
+ parts[i] = (PartitionInfo *) palloc0(sizeof(PartitionInfo));
+ parts[i]->oid = partrelid;
+
+ datum = fastgetattr(tuple,
+ Anum_pg_partition_partbound,
+ partrel->rd_att,
+ &isnull);
+
+ bound = stringToNode(TextDatumGetCString(datum));
+
+ switch (key->strategy)
+ {
+ case PARTITION_STRAT_LIST:
+ {
+ PartitionListSpec *list_spec;
+
+ Assert(IsA(bound, PartitionListSpec));
+ list_spec = (PartitionListSpec *) bound;
+ parts[i]->list = make_list_from_spec(key, list_spec->values);
+ }
+ break;
+
+ case PARTITION_STRAT_RANGE:
+ {
+ PartitionRangeSpec *range_spec;
+
+ Assert(IsA(bound, PartitionRangeSpec));
+ range_spec = (PartitionRangeSpec *) bound;
+
+ parts[i]->rangelower = make_range_from_spec(key,
+ range_spec->lower,
+ range_spec->lowerinc,
+ true);
+ parts[i]->rangeupper = make_range_from_spec(key,
+ range_spec->upper,
+ range_spec->upperinc,
+ false);
+ }
+ break;
+ }
+
+ ReleaseSysCache(tuple);
+ i++;
+ }
+
+ heap_close(partrel, AccessShareLock);
+
+ *nparts = i;
+ if (*nparts == 0 && parts)
+ {
+ pfree(parts);
+ parts = NULL;
+ }
+
+ return parts;
+}
+
+/*
+ * Wrapper around find_inheritance_children for use by partitioning
+ * related code
+ */
+static List *
+get_partitions(Oid relid, int lockmode)
+{
+ return find_inheritance_children(relid, lockmode);
+}
+
+/*
+ * Helper routine to deep-free a PartitionInfo array
+ */
+void
+free_partition_info(PartitionInfo **p, int num)
+{
+ int i;
+
+ for (i = 0; i < num; i++)
+ {
+ if (p[i]->list)
+ {
+ pfree(p[i]->list->values);
+ pfree(p[i]->list);
+ }
+
+ if (p[i]->rangelower)
+ {
+ if (p[i]->rangelower->val)
+ pfree(p[i]->rangelower->val);
+ pfree(p[i]->rangelower);
+ }
+
+ if (p[i]->rangeupper)
+ {
+ if (p[i]->rangeupper->val)
+ pfree(p[i]->rangeupper->val);
+ pfree(p[i]->rangeupper);
+ }
+
+ pfree(p[i]);
+ }
+
+ pfree(p);
+}
+
+/*
+ * get_check_qual_from_partbound - for direct use by tablecmds.c
+ */
+List *
+get_check_qual_from_partbound(Relation rel, Relation parent, Node *bound)
+{
+ PartitionKey key = RelationGetPartitionKey(parent);
+ List *my_check;
+ int i;
+ ListCell *partexprs_item;
+
+ if (IsA(bound, PartitionListSpec))
+ {
+ PartitionListSpec *list_spec = (PartitionListSpec *) bound;
+
+ my_check = get_check_qual_for_list(key, list_spec->values);
+ }
+ else if (IsA(bound, PartitionRangeSpec))
+ {
+ PartitionRangeSpec *range_spec = (PartitionRangeSpec *) bound;
+
+ my_check = get_check_qual_for_range(key,
+ range_spec->lower,
+ range_spec->lowerinc,
+ range_spec->upper,
+ range_spec->upperinc);
+ }
+
+ /*
+ * Translate vars in the generated expression with the correct attnos.
+ * Note that the vars in my_expr bear attnos dictated by key which carries
+ * attnos of the parent.
+ */
+ partexprs_item = list_head(key->partexprs);
+ for (i = 0; i < key->partnatts; i++)
+ {
+ AttrNumber attno = key->partattrs[i],
+ new_attno;
+ char *attname;
+
+ if (attno != 0)
+ {
+ attname = get_attname(RelationGetRelid(parent), attno);
+ new_attno = get_attnum(RelationGetRelid(rel), attname);
+
+ if (new_attno != attno)
+ my_check = (List *) translate_var_attno((Node *) my_check,
+ attno,
+ new_attno);
+ }
+ else
+ {
+ Node *expr = (Node *) lfirst(partexprs_item);
+ Bitmapset *expr_attrs = NULL;
+ int index;
+
+ pull_varattnos(expr, 1, &expr_attrs);
+ partexprs_item = lnext(partexprs_item);
+
+ index = -1;
+ while ((index = bms_next_member(expr_attrs, index)) > 0)
+ {
+ AttrNumber attno = index + FirstLowInvalidHeapAttributeNumber;
+
+ attname = get_attname(RelationGetRelid(parent), attno);
+ new_attno = get_attnum(RelationGetRelid(rel), attname);
+
+ if (new_attno != attno)
+ my_check = (List *) translate_var_attno((Node *) my_check,
+ attno,
+ new_attno);
+ }
+ }
+ }
+
+ return my_check;
+}
+
+/*
+ * get_check_qual_for_list
+ */
+static List *
+get_check_qual_for_list(PartitionKey key, List *values)
+{
+ ArrayExpr *values_arr = makeNode(ArrayExpr);
+ ScalarArrayOpExpr *arrayop_expr;
+ Node *key_col;
+ Oid operoid;
+ StrategyNumber strategy = BTEqualStrategyNumber;
+
+ /* Btree equality operator of specified opfamily */
+ operoid = get_opfamily_member(key->partopfamily[0],
+ key->tcinfo->typid[0],
+ key->tcinfo->typid[0],
+ strategy);
+
+ if (!OidIsValid(operoid))
+ operoid = get_opfamily_member(key->partopfamily[0],
+ key->partopcintype[0],
+ key->partopcintype[0],
+ strategy);
+
+ /* lexpr */
+ if (key->partattrs[0] != 0)
+ key_col = (Node *) makeVar(1, key->partattrs[0],
+ key->tcinfo->typid[0],
+ key->tcinfo->typmod[0],
+ key->tcinfo->typcoll[0],
+ 0);
+ else
+ key_col = (Node *) linitial(key->partexprs);
+
+ if (key->tcinfo->typid[0] != key->partopcintype[0])
+ {
+ RelabelType *r = makeRelabelType((Expr *) key_col,
+ key->partopcintype[0], -1,
+ get_typcollation(key->partopcintype[0]),
+ COERCE_EXPLICIT_CAST);
+
+ key_col = (Node *) r;
+ }
+
+ /* rexpr */
+ values_arr->array_typeid = get_array_type(key->tcinfo->typid[0]);
+ values_arr->array_collid = key->tcinfo->typcoll[0];
+ values_arr->element_typeid = key->tcinfo->typid[0];
+ values_arr->elements = values;
+ values_arr->multidims = false;
+ values_arr->location = -1;
+
+ /* build the actual predicate expression node */
+ arrayop_expr = makeNode(ScalarArrayOpExpr);
+ arrayop_expr->opno = operoid;
+ arrayop_expr->opfuncid = get_opcode(operoid);
+ arrayop_expr->useOr = true;
+ arrayop_expr->inputcollid = key->tcinfo->typcoll[0];
+ arrayop_expr->args = list_make2(key_col, values_arr);
+ arrayop_expr->location = -1;
+
+ return list_make1(arrayop_expr);
+}
+
+/*
+ * get_check_qual_for_range
+ */
+static List *
+get_check_qual_for_range(PartitionKey key,
+ List *lowervalues,
+ bool lowerinc,
+ List *uppervalues,
+ bool upperinc)
+{
+ List *and_args = NIL;
+ ListCell *cell1,
+ *cell2,
+ *partexprs_item;
+ int i;
+ uint16 strategy;
+
+ /*
+ * Handle the case with only one bound.
+ *
+ * In this case, consider only the first column of the key and generate
+ * appropriate opclause since only the comparison with the first column
+ * would have determined whether a rows went into such a partition.
+ * It's always true that -INF < someval and someval < +INF.
+ */
+ if (lowervalues == NIL || uppervalues == NIL)
+ {
+ List *values;
+ Const *key_val;
+ Node *key_col;
+ Oid operoid;
+ bool islower,
+ inclusive;
+
+ if (lowervalues != NIL)
+ {
+ values = lowervalues;
+ islower = true;
+ inclusive = lowerinc;
+ }
+ else
+ {
+ values = uppervalues;
+ islower = false;
+ inclusive = upperinc;
+ }
+
+ /* lexpr */
+ if (key->partattrs[0] != 0)
+ key_col = (Node *) makeVar(1, key->partattrs[0],
+ key->tcinfo->typid[0],
+ key->tcinfo->typmod[0],
+ key->tcinfo->typcoll[0],
+ 0);
+ else
+ key_col = (Node *) linitial(key->partexprs);
+
+ /* rexpr */
+ key_val = linitial(values);
+
+ if (islower)
+ strategy = inclusive ? BTGreaterEqualStrategyNumber : BTGreaterStrategyNumber;
+ else
+ strategy = inclusive ? BTLessEqualStrategyNumber : BTLessStrategyNumber;
+
+ operoid = get_opfamily_member(key->partopfamily[0],
+ key->tcinfo->typid[0],
+ key->tcinfo->typid[0],
+ strategy);
+
+ if (!OidIsValid(operoid))
+ operoid = get_opfamily_member(key->partopfamily[0],
+ key->partopcintype[0],
+ key->partopcintype[0],
+ strategy);
+
+ if (key->tcinfo->typid[0] != key->partopcintype[0])
+ {
+ RelabelType *r = makeRelabelType((Expr *) key_col,
+ key->partopcintype[0], -1,
+ get_typcollation(key->partopcintype[0]),
+ COERCE_EXPLICIT_CAST);
+
+ key_col = (Node *) r;
+ }
+
+ and_args = lappend(and_args,
+ make_opclause(operoid, BOOLOID,
+ false,
+ (Expr *) key_col,
+ (Expr *) key_val,
+ InvalidOid,
+ key->tcinfo->typcoll[0]));
+
+ return and_args;
+ }
+
+ /* Both bounds constrain values going into the partition. */
+ i = 0;
+ partexprs_item = list_head(key->partexprs);
+ forboth (cell1, lowervalues, cell2, uppervalues)
+ {
+ Node *key_col;
+ Const *lower_val = lfirst(cell1);
+ Const *upper_val = lfirst(cell2);
+ Expr *lower_clause,
+ *upper_clause;
+ Oid eqop,
+ lowerop,
+ upperop;
+ EState *estate;
+ MemoryContext oldcxt;
+ Expr *test_expr;
+ ExprState *test_exprstate;
+ Datum test_result;
+ bool isNull;
+
+ /* lexpr */
+ if (key->partattrs[i] != 0)
+ {
+ key_col = (Node *) makeVar(1, key->partattrs[i],
+ key->tcinfo->typid[i],
+ key->tcinfo->typmod[i],
+ key->tcinfo->typcoll[i],
+ 0);
+ }
+ else
+ {
+ key_col = (Node *) lfirst(partexprs_item);
+ partexprs_item = lnext(partexprs_item);
+ }
+
+ /*
+ * If lower_val = upper_val (= val, say), create a column = val
+ * constraint.
+ */
+ eqop = get_opfamily_member(key->partopfamily[i],
+ key->tcinfo->typid[i],
+ key->tcinfo->typid[i],
+ BTEqualStrategyNumber);
+
+ if (!OidIsValid(eqop))
+ eqop = get_opfamily_member(key->partopfamily[i],
+ key->partopcintype[i],
+ key->partopcintype[i],
+ BTEqualStrategyNumber);
+
+ estate = CreateExecutorState();
+ oldcxt = MemoryContextSwitchTo(estate->es_query_cxt);
+ test_expr = make_opclause(eqop,
+ BOOLOID,
+ false,
+ (Expr *) lower_val,
+ (Expr *) upper_val,
+ InvalidOid,
+ key->tcinfo->typcoll[i]);
+ fix_opfuncids((Node *) test_expr);
+ test_exprstate = ExecInitExpr(test_expr, NULL);
+ test_result = ExecEvalExprSwitchContext(test_exprstate,
+ GetPerTupleExprContext(estate),
+ &isNull, NULL);
+ MemoryContextSwitchTo(oldcxt);
+ FreeExecutorState(estate);
+
+ if (DatumGetBool(test_result))
+ {
+ and_args = lappend(and_args,
+ make_opclause(eqop,
+ BOOLOID,
+ false,
+ (Expr *) key_col,
+ /* could've been upper_val */
+ (Expr *) lower_val,
+ InvalidOid,
+ key->tcinfo->typcoll[i]));
+
+ /* Go to the next column. */
+ }
+ else
+ {
+ /*
+ * For all columns but last, use inclusive strategy (<= or >=).
+ *
+ * For the last column, the bound's inclusivity determines.
+ */
+ strategy = i < key->partnatts - 1 || lowerinc
+ ? BTGreaterEqualStrategyNumber
+ : BTGreaterStrategyNumber;
+ lowerop = get_opfamily_member(key->partopfamily[i],
+ key->tcinfo->typid[i],
+ key->tcinfo->typid[i],
+ strategy);
+ if (!OidIsValid(lowerop))
+ lowerop = get_opfamily_member(key->partopfamily[i],
+ key->partopcintype[i],
+ key->partopcintype[i],
+ strategy);
+
+ if (key->tcinfo->typid[i] != key->partopcintype[i])
+ {
+ RelabelType *r;
+
+ r = makeRelabelType((Expr *) key_col,
+ key->partopcintype[i], -1,
+ get_typcollation(key->partopcintype[i]),
+ COERCE_EXPLICIT_CAST);
+ key_col = (Node *) r;
+ }
+
+ strategy = i < key->partnatts - 1 || upperinc
+ ? BTLessEqualStrategyNumber
+ : BTLessStrategyNumber;
+ upperop = get_opfamily_member(key->partopfamily[i],
+ key->tcinfo->typid[i],
+ key->tcinfo->typid[i],
+ strategy);
+ if (!OidIsValid(upperop))
+ upperop = get_opfamily_member(key->partopfamily[i],
+ key->partopcintype[i],
+ key->partopcintype[i],
+ strategy);
+
+ lower_clause = make_opclause(lowerop,
+ BOOLOID,
+ false,
+ (Expr *) key_col,
+ (Expr *) lower_val,
+ InvalidOid,
+ key->tcinfo->typcoll[i]);
+ upper_clause = make_opclause(upperop,
+ BOOLOID,
+ false,
+ (Expr *) key_col,
+ (Expr *) upper_val,
+ InvalidOid,
+ key->tcinfo->typcoll[i]);
+
+ and_args = lappend(and_args, lower_clause);
+ and_args = lappend(and_args, upper_clause);
+
+ /* No need to constraint further columns. */
+ break;
+ }
+
+ i++;
+ }
+
+ return and_args;
+}
+
+/*
+ * translate_var_attno
+ * Changes Vars with a given attno in the provided expression tree to
+ * Vars with new_attno
+ */
+static Node *
+translate_var_attno(Node *expr, AttrNumber attno, AttrNumber new_attno)
+{
+ translate_var_attno_mutator_context cxt;
+
+ cxt.old_attno = attno;
+ cxt.new_attno = new_attno;
+
+ return expression_tree_mutator(expr, translate_var_attno_mutator, &cxt);
+}
+
+/*
+ * translate_var_attno_mutator
+ */
+static Node *
+translate_var_attno_mutator(Node *node,
+ translate_var_attno_mutator_context *cxt)
+{
+ if (node == NULL)
+ return NULL;
+
+ if (IsA(node, Var) && ((Var *) node)->varattno == cxt->old_attno)
+ {
+ Var *newvar = copyObject(node);
+
+ newvar->varattno = cxt->new_attno;
+
+ return (Node *) newvar;
+ }
+
+ return expression_tree_mutator(node, translate_var_attno_mutator,
+ (void *) cxt);
+}
+
+/*
+ * RelationGetPartitionConstraint
+ * Returns the equivelent CHECK constraint expression for a partition
+ */
+List *
+RelationGetPartitionCheckQual(Relation rel)
+{
+ /* Quick exit */
+ if (!relid_is_partition(RelationGetRelid(rel)))
+ return NULL;
+
+ /* Quick copy */
+ if (rel->rd_partcheck)
+ return copyObject(rel->rd_partcheck);
+
+ /* None of those, generate */
+ return generate_partition_check_qual(rel);
+}
+
+/*
+ * generate_partition_check_qual
+ * Generate partition check quals from rel's pg_partition entry
+ *
+ * Result expression tree is stored CacheMemoryContext to ensure it survives
+ * as long as the relcache entry. But we should be running in a less long-lived
+ * working context. To avoid leaking cache memory if this routine fails partway
+ * routine fails partway through, we build in working memory and then copy
+ * the completed structure into cache memory.
+ */
+static List *
+generate_partition_check_qual(Relation rel)
+{
+ HeapTuple tuple;
+ MemoryContext oldcxt;
+ Datum boundDatum;
+ bool isnull;
+ Node *bound;
+ List *my_check = NIL,
+ *result = NIL;
+ Oid relid = RelationGetRelid(rel);
+ Relation parent = heap_open(get_partition_parent(relid), NoLock);
+
+ /* quick copy */
+ if (rel->rd_partcheck)
+ {
+ heap_close(parent, NoLock);
+ return copyObject(rel->rd_partcheck);
+ }
+
+ tuple = SearchSysCache1(PARTRELID, relid);
+ Assert(HeapTupleIsValid(tuple));
+ boundDatum = SysCacheGetAttr(PARTRELID, tuple,
+ Anum_pg_partition_partbound,
+ &isnull);
+ Assert(!isnull);
+ bound = stringToNode(TextDatumGetCString(boundDatum));
+
+ my_check = get_check_qual_from_partbound(rel, parent, bound);
+
+ /*
+ * If parent is also a partition, create a AND_EXPR BoolExpr with just
+ * created expression and parent's check expression, as its args.
+ */
+ if (relid_is_partition(RelationGetRelid(parent)))
+ {
+ List *parent_check;
+
+ parent_check = generate_partition_check_qual(parent);
+ result = list_concat(parent_check, my_check);
+ }
+ else
+ result = my_check;
+
+ oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
+ rel->rd_partcheck = copyObject(result);
+ MemoryContextSwitchTo(oldcxt);
+
+ ReleaseSysCache(tuple);
+ heap_close(parent, NoLock);
+
+ return result;
+}
+
+/*
+ * RelationGetPartitionDescNode
+ * Recursively form partition node tree rooted at rel's node
+ */
+PartitionDescNode
+RelationGetPartitionDescNode(Relation rel)
+{
+ PartitionDescNode root;
+
+ /*
+ * Recurse to build the tree of partition descriptor nodes with
+ * rel's descriptor as the root of the tree.
+ */
+ root = GetPartitionDescNodeRecurse(rel, 0);
+ root->index = 0;
+ root->num_leaf_partitions = get_leaf_partition_count(root);
+
+ return root;
+}
+
+/*
+ * GetPartitionDescNodeRecurse
+ * Workhorse of RelationGetPartitionDescNode
+ *
+ * offset is the index (0-based) of the first leaf node in the tree, as
+ * as determined and passed by the parent or the left sibling.
+ */
+static PartitionDescNode
+GetPartitionDescNodeRecurse(Relation rel, int offset)
+{
+ PartitionDescNode root,
+ prev;
+ PartitionDesc pdesc = RelationGetPartitionDesc(rel);
+ int i;
+
+ /* First build our own node */
+ root = (PartitionDescNode) palloc0(sizeof(PartitionDescNodeData));
+ root->pdesc = pdesc;
+ root->relid = RelationGetRelid(rel);
+ root->offset = offset;
+ root->downlink = NULL;
+ root->next = NULL;
+
+ /*
+ * Recursively add nodes for partitions that are themselves partitioned,
+ * linking ourselves to the first such node via 'downlink'. Each node
+ * thus created (being on the same level) is linked to the right sibling
+ * via 'next'. For each node, determine the offset to pass and once
+ * created, update its index and num_leaf_partitions.
+ */
+ prev = NULL;
+ for (i = 0; i < pdesc->nparts; i++)
+ {
+ Oid relid = pdesc->oids[i];
+ int offset;
+ Relation rel;
+ PartitionDescNode pdnode;
+
+ if (!relid_is_partitioned(relid))
+ continue;
+
+ rel = heap_open(relid, AccessShareLock);
+
+ /* Offset for the new node */
+ if (prev)
+ /*
+ * Count leaves on the left - prev node has the account of those
+ * on its own left and then there may have been some siblings
+ * in-between that were leaves themselves.
+ */
+ offset = prev->offset + prev->num_leaf_partitions +
+ (i - prev->index - 1);
+ else
+ /*
+ * We're the first child - account of leaf nodes comes from the
+ * parent. Left siblings were all leaves themselves.
+ */
+ offset = root->offset + i;
+
+ pdnode = GetPartitionDescNodeRecurse(rel, offset);
+ pdnode->index = i;
+ pdnode->num_leaf_partitions = get_leaf_partition_count(pdnode);
+
+ heap_close(rel, AccessShareLock);
+
+ /* Found our first child; link to it. */
+ if (root->downlink == NULL)
+ root->downlink = pdnode;
+
+ /* Link new node to the left sibling, if any */
+ if (prev)
+ prev->next = pdnode;
+
+ prev = pdnode;
+ }
+
+ return root;
+}
+
+/*
+ * get_leaf_partition_count
+ * Recursively count the number of leaf partitions in the partition
+ * tree rooted at pdnode
+ */
+static int
+get_leaf_partition_count(PartitionDescNode pdnode)
+{
+ int i;
+ int result = 0;
+ PartitionDescNode node = pdnode->downlink;
+
+ for (i = 0; i < pdnode->pdesc->nparts; i++)
+ {
+ /* Indexes 0..(node->index - 1) are of leaf partitions */
+ if (node && i == node->index)
+ {
+ result += get_leaf_partition_count(node);
+ node = node->next;
+ }
+ else
+ result += 1;
+ }
+
+ return result;
+}
+
+/*
+ * get_leaf_partition_count
+ * Recursively compute the list of OIDs of leaf partitions in the
+ * partition tree rooted at pdnode
+ */
+List *
+get_leaf_partition_oids(PartitionDescNode pdnode)
+{
+ int i;
+ List *result = NIL;
+ Oid *myparts = pdnode->pdesc->oids;
+ int n_myparts = pdnode->pdesc->nparts;
+ PartitionDescNode node = pdnode->downlink;
+
+ for (i = 0; i < n_myparts; i++)
+ {
+ /* Indexes 0..(node->index - 1) are leaf partitions */
+ if (node && i == node->index)
+ {
+ result = list_concat(result, get_leaf_partition_oids(node));
+ node = node->next;
+ }
+ else
+ result = lappend_oid(result, myparts[i]);
+ }
+
+ return result;
+}
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 53b2226..76803a5 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -2464,7 +2464,7 @@ CopyFrom(CopyState cstate)
if (!skip_tuple)
{
/* Check the constraints of the tuple */
- if (cstate->rel->rd_att->constr)
+ if (cstate->rel->rd_att->constr || resultRelInfo->ri_PartitionCheck)
ExecConstraints(resultRelInfo, slot, estate);
if (useHeapMultiInsert)
diff --git a/src/backend/commands/lockcmds.c b/src/backend/commands/lockcmds.c
index 62671a8..5120c93 100644
--- a/src/backend/commands/lockcmds.c
+++ b/src/backend/commands/lockcmds.c
@@ -16,6 +16,7 @@
#include "access/heapam.h"
#include "catalog/namespace.h"
+#include "catalog/partition.h"
#include "catalog/pg_inherits_fn.h"
#include "commands/lockcmds.h"
#include "miscadmin.h"
@@ -63,7 +64,8 @@ LockTableCommand(LockStmt *lockstmt)
RangeVarCallbackForLockTable,
(void *) &lockstmt->mode);
- if (recurse)
+ /* Force inheritance recursion, if partitioned table. */
+ if (recurse || relid_is_partitioned(reloid))
LockTableRecurse(reloid, lockstmt->mode, lockstmt->nowait);
}
}
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index db1ee74..5b83b8d 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -121,6 +121,19 @@ static List *on_commits = NIL;
/*
+ * For AT ATTACH PARTITION <table> FOR VALUES <partition_bound_spec>,
+ *
+ * Struct describing partition check expression corresponding to
+ * <partition_bound_spec> to validate for rows in <table> during
+ * Phase 3 scan.
+ */
+typedef struct PartitionCheck
+{
+ Expr *expr;
+ List *state;
+} PartitionCheck;
+
+/*
* State information for ALTER TABLE
*
* The pending-work queue for an ALTER TABLE is a List of AlteredTableInfo
@@ -163,6 +176,8 @@ typedef struct AlteredTableInfo
Oid newTableSpace; /* new tablespace; 0 means no change */
bool chgPersistence; /* T if SET LOGGED/UNLOGGED is used */
char newrelpersistence; /* if above is true */
+ PartitionCheck *partitionCheck; /* partition check expression for
+ * ATTACH PARTITION validation */
/* Objects to rebuild after completing ALTER TYPE operations */
List *changedConstraintOids; /* OIDs of constraints to rebuild */
List *changedConstraintDefs; /* string definitions of same */
@@ -755,6 +770,14 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
partattrs, partexprs, partopclass);
}
+ /* Process and store partition bound. */
+ if (stmt->partbound)
+ {
+ Oid parentId = linitial_oid(inheritOids);
+
+ StorePartitionBound(relationId, parentId, stmt->partbound);
+ }
+
ObjectAddressSet(address, RelationRelationId, relationId);
/*
@@ -995,6 +1018,13 @@ RangeVarCallbackForDropRelation(const RangeVar *rel, Oid relOid, Oid oldRelOid,
if (!OidIsValid(relOid))
return;
+ if (relid_is_partition(relOid))
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("\"%s\" is a partition of \"%s\"", rel->relname,
+ get_rel_name(get_partition_parent(relOid))),
+ errhint("Use ALTER TABLE DETACH PARTITION to be able to drop it.")));
+
tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relOid));
if (!HeapTupleIsValid(tuple))
return; /* concurrently dropped, so nothing to do */
@@ -1086,7 +1116,8 @@ ExecuteTruncate(TruncateStmt *stmt)
rels = lappend(rels, rel);
relids = lappend_oid(relids, myrelid);
- if (recurse)
+ /* Force inheritance recursion, if partitioned table. */
+ if (recurse || relid_is_partitioned(myrelid))
{
ListCell *child;
List *children;
@@ -1810,8 +1841,12 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
Node *expr;
bool found_whole_row;
- /* ignore if the constraint is non-inheritable */
- if (check[i].ccnoinherit)
+ /*
+ * ignore if the constraint is non-inheritable and this is not
+ * the partition case (constraints of partitioned tables are
+ * always present in their partitions)
+ */
+ if (check[i].ccnoinherit && !is_partition)
continue;
/* Adjust Vars to match new table's column numbering */
@@ -2246,6 +2281,11 @@ renameatt_check(Oid myrelid, Form_pg_class classform, bool recursing)
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot rename column of typed table")));
+ if (relid_is_partition(myrelid) && !recursing)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot rename column of a partition")));
+
/*
* Renaming the columns of sequences or toast tables doesn't actually
* break anything from the system's point of view, since internal
@@ -2472,11 +2512,13 @@ renameatt(RenameStmt *stmt)
return InvalidObjectAddress;
}
+ /* Force inheritance recursion, if partitioned table. */
attnum =
renameatt_internal(relid,
stmt->subname, /* old att name */
stmt->newname, /* new att name */
- interpretInhOption(stmt->relation->inhOpt), /* recursive? */
+ interpretInhOption(stmt->relation->inhOpt) ||
+ relid_is_partitioned(relid), /* recursive? */
false, /* recursing? */
0, /* expected inhcount */
stmt->behavior);
@@ -2624,11 +2666,13 @@ RenameConstraint(RenameStmt *stmt)
}
}
+ /* Force inheritance recursion, if partitioned table. */
return
rename_constraint_internal(relid, typid,
stmt->subname,
stmt->newname,
- stmt->relation ? interpretInhOption(stmt->relation->inhOpt) : false, /* recursive? */
+ stmt->relation ? interpretInhOption(stmt->relation->inhOpt) ||
+ relid_is_partitioned(relid) : false, /* recursive? */
false, /* recursing? */
0 /* expected inhcount */ );
@@ -2870,8 +2914,11 @@ AlterTable(Oid relid, LOCKMODE lockmode, AlterTableStmt *stmt)
CheckTableNotInUse(rel, "ALTER TABLE");
+ /* Force inheritance recursion, if partitioned table */
ATController(stmt,
- rel, stmt->cmds, interpretInhOption(stmt->relation->inhOpt),
+ rel, stmt->cmds,
+ interpretInhOption(stmt->relation->inhOpt) ||
+ relid_is_partitioned(RelationGetRelid(rel)),
lockmode);
}
@@ -3988,7 +4035,7 @@ ATRewriteTables(AlterTableStmt *parsetree, List **wqueue, LOCKMODE lockmode)
* Test the current data within the table against new constraints
* generated by ALTER TABLE commands, but don't rebuild data.
*/
- if (tab->constraints != NIL || tab->new_notnull)
+ if (tab->constraints != NIL || tab->new_notnull || tab->partitionCheck)
ATRewriteTable(tab, InvalidOid, lockmode);
/*
@@ -4132,6 +4179,21 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
}
}
+ /*
+ * Build expression execution states for partition check expression
+ * as well.
+ */
+ if (tab->partitionCheck)
+ {
+ List *expr;
+
+ needscan = true;
+ Assert(tab->partitionCheck->expr);
+ expr = list_make1(tab->partitionCheck->expr);
+ tab->partitionCheck->state = (List *)
+ ExecPrepareExpr((Expr *) expr, estate);
+ }
+
foreach(l, tab->newvals)
{
NewColumnValue *ex = lfirst(l);
@@ -4321,6 +4383,12 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
}
}
+ if (tab->partitionCheck &&
+ !ExecQual(tab->partitionCheck->state, econtext, true))
+ ereport(ERROR,
+ (errcode(ERRCODE_CHECK_VIOLATION),
+ errmsg("table contains a row violating the partition bound specification")));
+
/* Write the tuple out to the new relation */
if (newrel)
heap_insert(newrel, tuple, mycid, hi_options, bistate);
@@ -4518,7 +4586,8 @@ ATSimpleRecursion(List **wqueue, Relation rel,
*/
if (recurse &&
(rel->rd_rel->relkind == RELKIND_RELATION ||
- rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE))
+ rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE ||
+ rel->rd_rel->relkind == RELKIND_PARTITIONED_REL))
{
Oid relid = RelationGetRelid(rel);
ListCell *child;
@@ -4840,6 +4909,11 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
if (recursing)
ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
+ if (relid_is_partition(RelationGetRelid(rel)) && !recursing)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot add column to a partition")));
+
attrdesc = heap_open(AttributeRelationId, RowExclusiveLock);
/*
@@ -5295,6 +5369,7 @@ ATExecDropNotNull(Relation rel, const char *colName, LOCKMODE lockmode)
List *indexoidlist;
ListCell *indexoidscan;
ObjectAddress address;
+ Oid relid;
/*
* lookup the attribute
@@ -5362,6 +5437,28 @@ ATExecDropNotNull(Relation rel, const char *colName, LOCKMODE lockmode)
list_free(indexoidlist);
/*
+ * If rel is partition, throw error if the attribute is not null in
+ * parent
+ */
+ relid = RelationGetRelid(rel);
+ if (relid_is_partition(relid))
+ {
+ Oid parentId = get_partition_parent(relid);
+ Relation parent = heap_open(parentId, AccessShareLock);
+ TupleDesc tupDesc = RelationGetDescr(parent);
+ AttrNumber parent_attnum;
+
+ parent_attnum = get_attnum(parentId, colName);
+ if (tupDesc->attrs[parent_attnum - 1]->attnotnull)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("column \"%s\" is not null in parent",
+ colName),
+ errhint("Please drop not null in the parent instead")));
+ heap_close(parent, AccessShareLock);
+ }
+
+ /*
* Okay, actually perform the catalog change ... if needed
*/
if (((Form_pg_attribute) GETSTRUCT(tuple))->attnotnull)
@@ -5891,6 +5988,11 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
if (recursing)
ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
+ if (relid_is_partition(RelationGetRelid(rel)) && !recursing)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot drop column from a partition")));
+
/*
* get the number of the attribute
*/
@@ -5936,11 +6038,13 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
if (!is_expr)
ereport(ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
- errmsg("cannot drop column named in partition key")));
+ errmsg("cannot drop column named in%s partition key",
+ recursing ? " downstream" : "")));
else
ereport(ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
- errmsg("cannot drop column referenced in partition key expression")));
+ errmsg("cannot drop column referenced in%s partition key expression",
+ recursing ? " downstream" : "")));
}
ReleaseSysCache(tuple);
@@ -6374,8 +6478,9 @@ ATAddCheckConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
/*
* If adding a NO INHERIT constraint, no need to find our children.
+ * However, that applies only to regular inheritance parents.
*/
- if (constr->is_no_inherit)
+ if (constr->is_no_inherit && !relid_is_partitioned(RelationGetRelid(rel)))
return address;
/*
@@ -7936,9 +8041,12 @@ ATExecDropConstraint(Relation rel, const char *constrName,
/*
* Propagate to children as appropriate. Unlike most other ALTER
* routines, we have to do this one level of recursion at a time; we can't
- * use find_all_inheritors to do it in one pass.
+ * use find_all_inheritors to do it in one pass. Note that if the parent
+ * is a partitioned table, we propagate to children (partitions) despite
+ * is_no_inherit_constraint.
*/
- if (!is_no_inherit_constraint)
+ if (!is_no_inherit_constraint ||
+ relid_is_partitioned(RelationGetRelid(rel)))
children = find_inheritance_children(RelationGetRelid(rel), lockmode);
else
children = NIL;
@@ -8068,6 +8176,11 @@ ATPrepAlterColumnType(List **wqueue,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot alter column type of typed table")));
+ if (relid_is_partition(RelationGetRelid(rel)) && !recursing)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot alter column type of a partition")));
+
/* lookup the attribute so we can check inheritance status */
tuple = SearchSysCacheAttName(RelationGetRelid(rel), colName);
if (!HeapTupleIsValid(tuple))
@@ -8098,11 +8211,13 @@ ATPrepAlterColumnType(List **wqueue,
if (!is_expr)
ereport(ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
- errmsg("cannot alter column named in partition key")));
+ errmsg("cannot alter column named in%s partition key",
+ recursing ? " downstream" : "")));
else
ereport(ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
- errmsg("cannot alter column referenced in partition key expression")));
+ errmsg("cannot alter column referenced in%s partition key expression",
+ recursing ? " downstream" : "")));
}
/* Look up the target type */
@@ -10235,6 +10350,11 @@ ATPrepAddInherit(Relation child_rel)
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot change inheritance of typed table")));
+ if (relid_is_partition(RelationGetRelid(child_rel)))
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot change inheritance of a partition")));
+
if (child_rel->rd_rel->relkind == RELKIND_PARTITIONED_REL)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
@@ -10291,6 +10411,11 @@ ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode)
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot inherit from partitioned table \"%s\"", parent->relname)));
+ if (relid_is_partition(RelationGetRelid(parent_rel)))
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot inherit from a partition")));
+
/*
* Prevent circularity by seeing if proposed parent inherits from child.
* (In particular, this disallows making a rel inherit from itself.)
@@ -10677,6 +10802,11 @@ ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
ObjectAddress address;
Relation parent_rel;
+ if (relid_is_partition(RelationGetRelid(rel)))
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot change inheritance of a partition")));
+
/*
* AccessShareLock on the parent is probably enough, seeing that DROP
* TABLE doesn't lock parent tables at all. We need some lock since we'll
@@ -12566,6 +12696,12 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
*/
ATSimplePermissions(attachRel, ATT_TABLE);
+ if (relid_is_partition(RelationGetRelid(attachRel)))
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("\"%s\" is already a partition",
+ RelationGetRelationName(attachRel))));
+
if (attachRel->rd_rel->reloftype)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
@@ -12599,6 +12735,12 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
errmsg("cannot attach a temporary relation of another session as partition ")));
/* If parent has OIDs, attachRel must have OIDs */
+ if (attachRel->rd_rel->reloftype)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot attach a typed table as partition")));
+
+ /* If parent has OIDs then child must have OIDs */
if (rel->rd_rel->relhasoids && !attachRel->rd_rel->relhasoids)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
@@ -12641,6 +12783,31 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
/* OK to create inheritance. Rest of the checks performed there */
CreateInheritance(attachRel, rel);
+ /*
+ * Set up to have the rows in table to be checked for violation of the
+ * partition bound spec in phase 3 scan.
+ */
+ if (!cmd->skip_validate)
+ {
+ AlteredTableInfo *tab;
+ PartitionCheck *check;
+ List *my_check,
+ *parent_check = RelationGetPartitionCheckQual(rel),
+ *check_quals;
+
+ tab = ATGetQueueEntry(wqueue, attachRel);
+ check = (PartitionCheck *) palloc0(sizeof(PartitionCheck));
+ my_check = get_check_qual_from_partbound(attachRel, rel, cmd->bound);
+ check_quals = parent_check ? list_concat(parent_check, my_check)
+ : my_check;
+ check->expr = makeBoolExpr(AND_EXPR, check_quals, -1);
+ tab->partitionCheck = check;
+ }
+
+ /* Store bound into the catalog */
+ StorePartitionBound(RelationGetRelid(attachRel), RelationGetRelid(rel),
+ cmd->bound);
+
ObjectAddressSet(address, RelationRelationId, RelationGetRelid(attachRel));
/* keep our lock until commit */
@@ -12665,6 +12832,15 @@ ATExecDetachPartition(Relation rel, RangeVar *name)
/* All inheritance related checks are performed within the function */
RemoveInheritance(partRel, rel);
+ /* Remove the pg_partition entry */
+ RemovePartitionEntryByRelId(RelationGetRelid(partRel));
+
+ /*
+ * Invalidate the relcache so that the partition is no longer included
+ * in our partition descriptor.
+ */
+ CacheInvalidateRelcache(rel);
+
ObjectAddressSet(address, RelationRelationId, RelationGetRelid(partRel));
/* keep our lock until commit */
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 3c73e81..bbd2187 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -42,6 +42,7 @@
#include "access/transam.h"
#include "access/xact.h"
#include "catalog/namespace.h"
+#include "catalog/partition.h"
#include "commands/matview.h"
#include "commands/trigger.h"
#include "executor/execdebug.h"
@@ -1251,6 +1252,8 @@ InitResultRelInfo(ResultRelInfo *resultRelInfo,
resultRelInfo->ri_ConstraintExprs = NULL;
resultRelInfo->ri_junkFilter = NULL;
resultRelInfo->ri_projectReturning = NULL;
+ resultRelInfo->ri_PartitionCheck = (Expr *)
+ RelationGetPartitionCheckQual(resultRelationDesc);
}
/*
@@ -1692,6 +1695,49 @@ ExecRelCheck(ResultRelInfo *resultRelInfo,
return NULL;
}
+/*
+ * ExecPartitionCheck --- check that tuple meets the partition boundary
+ * specification.
+ *
+ * Note: This is called, *iff* resultRelInfo is the main target table.
+ */
+static bool
+ExecPartitionCheck(ResultRelInfo *resultRelInfo, TupleTableSlot *slot,
+ EState *estate)
+{
+ ExprContext *econtext;
+
+ /*
+ * If first time through, build expression state tree for the partition
+ * check expression. Keep it in the per-query memory context so they'll
+ * survive throughout the query.
+ */
+ if (resultRelInfo->ri_PartitionCheckExpr == NULL)
+ {
+ List *qual = make_ands_implicit(resultRelInfo->ri_PartitionCheck);
+ resultRelInfo->ri_PartitionCheckExpr = (List *)
+ ExecPrepareExpr((Expr *) qual, estate);
+ }
+
+ /*
+ * We will use the EState's per-tuple context for evaluating constraint
+ * expressions (creating it if it's not already there).
+ */
+ econtext = GetPerTupleExprContext(estate);
+
+ /* Arrange for econtext's scan tuple to be the tuple under test */
+ econtext->ecxt_scantuple = slot;
+
+ /*
+ * NOTE: SQL specifies that a NULL result from a constraint expression
+ * is not to be treated as a failure. Therefore, tell ExecQual to
+ * return TRUE for NULL.
+ *
+ * XXX - although, it's unlikely that NULL would result.
+ */
+ return ExecQual(resultRelInfo->ri_PartitionCheckExpr, econtext, true);
+}
+
void
ExecConstraints(ResultRelInfo *resultRelInfo,
TupleTableSlot *slot, EState *estate)
@@ -1703,9 +1749,9 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
Bitmapset *insertedCols;
Bitmapset *updatedCols;
- Assert(constr);
+ Assert(constr || resultRelInfo->ri_PartitionCheck);
- if (constr->has_not_null)
+ if (constr && constr->has_not_null)
{
int natts = tupdesc->natts;
int attrChk;
@@ -1736,7 +1782,7 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
}
}
- if (constr->num_check > 0)
+ if (constr && constr->num_check > 0)
{
const char *failed;
@@ -1760,6 +1806,29 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
errtableconstraint(rel, failed)));
}
}
+
+ if (resultRelInfo->ri_PartitionCheck)
+ {
+ if (!ExecPartitionCheck(resultRelInfo, slot, estate))
+ {
+ char *val_desc;
+
+ insertedCols = GetInsertedColumns(resultRelInfo, estate);
+ updatedCols = GetUpdatedColumns(resultRelInfo, estate);
+ modifiedCols = bms_union(insertedCols, updatedCols);
+ val_desc = ExecBuildSlotValueDescription(RelationGetRelid(rel),
+ slot,
+ tupdesc,
+ modifiedCols,
+ 64);
+ ereport(ERROR,
+ (errcode(ERRCODE_CHECK_VIOLATION),
+ errmsg("new row violates the partition boundary"
+ " specification of \"%s\"",
+ RelationGetRelationName(rel)),
+ val_desc ? errdetail("Failing row contains %s.", val_desc) : 0));
+ }
+ }
}
/*
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 8e2da9c..3ed321e 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -354,7 +354,7 @@ ExecInsert(ModifyTableState *mtstate,
/*
* Check the constraints of the tuple
*/
- if (resultRelationDesc->rd_att->constr)
+ if (resultRelationDesc->rd_att->constr || resultRelInfo->ri_PartitionCheck)
ExecConstraints(resultRelInfo, slot, estate);
if (onconflict != ONCONFLICT_NONE && resultRelInfo->ri_NumIndices > 0)
@@ -907,7 +907,7 @@ lreplace:;
/*
* Check the constraints of the tuple
*/
- if (resultRelationDesc->rd_att->constr)
+ if (resultRelationDesc->rd_att->constr || resultRelInfo->ri_PartitionCheck)
ExecConstraints(resultRelInfo, slot, estate);
/*
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 4565981..1b95dfb 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -2248,6 +2248,35 @@ _readExtensibleNode(void)
}
/*
+ * _readPartitionListSpec
+ */
+static PartitionListSpec *
+_readPartitionListSpec(void)
+{
+ READ_LOCALS(PartitionListSpec);
+
+ READ_NODE_FIELD(values);
+
+ READ_DONE();
+}
+
+/*
+ * _readPartitionRangeSpec
+ */
+static PartitionRangeSpec *
+_readPartitionRangeSpec(void)
+{
+ READ_LOCALS(PartitionRangeSpec);
+
+ READ_BOOL_FIELD(lowerinc);
+ READ_NODE_FIELD(lower);
+ READ_BOOL_FIELD(upperinc);
+ READ_NODE_FIELD(upper);
+
+ READ_DONE();
+}
+
+/*
* parseNodeString
*
* Given a character string representing a node tree, parseNodeString creates
@@ -2477,6 +2506,10 @@ parseNodeString(void)
return_value = _readAlternativeSubPlan();
else if (MATCH("EXTENSIBLENODE", 14))
return_value = _readExtensibleNode();
+ else if (MATCH("PARTITIONLISTVALUES", 19))
+ return_value = _readPartitionListSpec();
+ else if (MATCH("PARTITIONRANGE", 14))
+ return_value = _readPartitionRangeSpec();
else
{
elog(ERROR, "badly formatted node string \"%.32s\"...", token);
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index 149bd62..9314074 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -27,6 +27,7 @@
#include "catalog/catalog.h"
#include "catalog/dependency.h"
#include "catalog/heap.h"
+#include "catalog/partition.h"
#include "catalog/pg_am.h"
#include "foreign/fdwapi.h"
#include "miscadmin.h"
@@ -1127,6 +1128,7 @@ get_relation_constraints(PlannerInfo *root,
Index varno = rel->relid;
Relation relation;
TupleConstr *constr;
+ List *pcqual;
/*
* We assume the relation has already been safely locked.
@@ -1206,6 +1208,17 @@ get_relation_constraints(PlannerInfo *root,
}
}
+ /* Append partition check quals, if any */
+ pcqual = RelationGetPartitionCheckQual(relation);
+ if (pcqual)
+ {
+ /* Fix Vars to have the desired varno */
+ if (varno != 1)
+ ChangeVarNodes((Node *) pcqual, 1, varno, 0);
+
+ result = list_concat(result, pcqual);
+ }
+
heap_close(relation, NoLock);
return result;
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 29c8c4e..b4fca37 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -25,6 +25,7 @@
#include "postgres.h"
#include "access/sysattr.h"
+#include "catalog/partition.h"
#include "catalog/pg_type.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
diff --git a/src/backend/parser/parse_clause.c b/src/backend/parser/parse_clause.c
index 307e383..5124dbf 100644
--- a/src/backend/parser/parse_clause.c
+++ b/src/backend/parser/parse_clause.c
@@ -202,8 +202,8 @@ setTargetTable(ParseState *pstate, RangeVar *relation,
pstate->p_target_rangetblentry = rte;
/*
- * Override specified inheritance option, if relation is a partitioned
- * table and it's a target of INSERT (ie, alsoSource == false).
+ * For UPDATE/DELETE, force inheritance recursion, if partitioned
+ * table.
*/
if (alsoSource)
rte->inh |= relid_is_partitioned(rte->relid);
diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c
index fd4c9bf..c42a30c 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -1218,8 +1218,7 @@ addRangeTableEntry(ParseState *pstate,
* The initial default on access checks is always check-for-READ-access,
* which is the right thing for all except target tables.
*
- * Note: we override the specified inheritance option, if relation is a
- * partitioned table.
+ * Note: We force inheritance recursion, if partitioned table.
*/
rte->lateral = false;
rte->inh = inh || relid_is_partitioned(RelationGetRelid(rel));
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 8fc3e50..96f24f0 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -32,6 +32,7 @@
#include "catalog/dependency.h"
#include "catalog/heap.h"
#include "catalog/index.h"
+#include "catalog/partition.h"
#include "catalog/namespace.h"
#include "catalog/partition.h"
#include "catalog/pg_am.h"
@@ -3105,6 +3106,11 @@ transformPartitionOf(CreateStmtContext *cxt, Node *bound)
if (parentRel->rd_rel->relhasoids)
cxt->hasoids = true;
+ /*
+ * Create ColumnDefs correponding to parent's attributes as "inherited"
+ * attributes. Also, set is_for_partition whose function is similar to
+ * is_from_type for MergeAttributes()'s purposes.
+ */
tupdesc = RelationGetDescr(parentRel);
for (i = 0; i < tupdesc->natts; i++)
{
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 37f5b5c..65c2f64 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -8224,6 +8224,84 @@ get_rule_expr(Node *node, deparse_context *context,
}
break;
+ case T_PartitionListSpec:
+ {
+ PartitionListSpec *list_spec = (PartitionListSpec *) node;
+ ListCell *cell;
+ char *sep;
+
+ appendStringInfoString(buf, "FOR VALUES");
+
+ appendStringInfoString(buf, " IN (");
+ sep = "";
+ foreach (cell, list_spec->values)
+ {
+ Const *val = lfirst(cell);
+
+ appendStringInfoString(buf, sep);
+ get_const_expr(val, context, -1);
+ sep = ", ";
+ }
+
+ appendStringInfoString(buf, ")");
+ }
+ break;
+
+ case T_PartitionRangeSpec:
+ {
+ PartitionRangeSpec *range_spec = (PartitionRangeSpec *) node;
+ ListCell *cell;
+ char *sep;
+
+ appendStringInfoString(buf, "FOR VALUES");
+
+ appendStringInfoString(buf, " START");
+ if (!range_spec->lower)
+ appendStringInfoString(buf, " UNBOUNDED");
+ else
+ {
+ appendStringInfoString(buf, " (");
+
+ sep = "";
+ foreach (cell, range_spec->lower)
+ {
+ Const *val = lfirst(cell);
+
+ appendStringInfoString(buf, sep);
+ get_const_expr(val, context, -1);
+ sep = ", ";
+ }
+ appendStringInfoString(buf, ")");
+
+ if (!range_spec->lowerinc)
+ appendStringInfoString(buf, " EXCLUSIVE");
+ }
+
+ appendStringInfoString(buf, " END");
+
+ if (!range_spec->upper)
+ appendStringInfoString(buf, " UNBOUNDED");
+ else
+ {
+ appendStringInfoString(buf, " (");
+
+ sep = "";
+ foreach (cell, range_spec->upper)
+ {
+ Const *val = lfirst(cell);
+
+ appendStringInfoString(buf, sep);
+ get_const_expr(val, context, -1);
+ sep = ", ";
+ }
+ appendStringInfoString(buf, ")");
+
+ if (range_spec->upperinc)
+ appendStringInfoString(buf, " INCLUSIVE");
+ }
+ }
+ break;
+
case T_List:
{
char *sep;
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 3787441..94bc883 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -71,6 +71,7 @@
#include "storage/smgr.h"
#include "utils/array.h"
#include "utils/builtins.h"
+#include "utils/datum.h"
#include "utils/fmgroids.h"
#include "utils/inval.h"
#include "utils/lsyscache.h"
@@ -279,6 +280,19 @@ static OpClassCacheEnt *LookupOpclassInfo(Oid operatorClassOid,
StrategyNumber numSupport);
static void RelationCacheInitFileRemoveInDir(const char *tblspcpath);
static void unlink_initfile(const char *initfilename);
+static void RelationBuildPartitionDesc(Relation relation);
+static int32 range_partition_cmp_upper(const void *a, const void *b,
+ void *arg);
+static PartitionListBound *copyPartitionListBound(PartitionListBound *src,
+ PartitionKey key);
+static PartitionRangeBound *copyPartitionRangeBound(PartitionRangeBound *src,
+ PartitionKey key);
+static bool equalPartitionDescs(PartitionKey key, PartitionDesc pdesc1,
+ PartitionDesc pdesc2);
+static bool equalListBounds(PartitionKey key, PartitionListBound *lb1,
+ PartitionListBound *lb2);
+static bool equalRangeBounds(PartitionKey key, PartitionRangeBound *rb1,
+ PartitionRangeBound *rb2);
/*
@@ -928,6 +942,86 @@ equalRSDesc(RowSecurityDesc *rsdesc1, RowSecurityDesc *rsdesc2)
}
/*
+ * equalPartitionDescs
+ * Compare two partition descriptors for logical equality
+ */
+static bool
+equalPartitionDescs(PartitionKey key, PartitionDesc pdesc1,
+ PartitionDesc pdesc2)
+{
+ int i;
+
+ if (pdesc1 != NULL)
+ {
+ if (pdesc2 == NULL)
+ return false;
+ if (pdesc1->nparts != pdesc2->nparts)
+ return false;
+
+ Assert(key != NULL || pdesc1->nparts == 0);
+
+ for (i = 0; i < pdesc1->nparts; i++)
+ {
+ if (pdesc1->oids[i] != pdesc2->oids[i])
+ return false;
+
+ if (pdesc1->lists &&
+ !equalListBounds(key, pdesc1->lists[i], pdesc2->lists[i]))
+ return false;
+
+ if (pdesc1->rangelowers &&
+ (!equalRangeBounds(key, pdesc1->rangelowers[i],
+ pdesc2->rangelowers[i]) ||
+ !equalRangeBounds(key, pdesc1->rangeuppers[i],
+ pdesc2->rangeuppers[i])))
+ return false;
+ }
+ }
+ else if (pdesc2 != NULL)
+ return false;
+
+ return true;
+}
+
+/* Are two listbounds equal (contain same values)? */
+static bool
+equalListBounds(PartitionKey key, PartitionListBound *lb1,
+ PartitionListBound *lb2)
+{
+ int i,
+ j;
+
+ if (lb1->nvalues != lb2->nvalues)
+ return false;
+
+ if (lb1->contains_null != lb2->contains_null)
+ return false;
+
+ for (i = 0; i < lb1->nvalues; i++)
+ for (j = 0; j < lb2->nvalues; j++)
+ /* Non-zero result means not equal */
+ if (DatumGetInt32(FunctionCall2Coll(&key->partsupfunc[0],
+ key->tcinfo->typcoll[0],
+ lb1->values[i],
+ lb2->values[j])))
+ return false;
+
+ return true;
+}
+
+/*
+ * Are two rangebounds equal?
+ *
+ * It's clear in this case that both are either lower bounds or upper bounds.
+ */
+static bool
+equalRangeBounds(PartitionKey key, PartitionRangeBound *rb1,
+ PartitionRangeBound *rb2)
+{
+ return partition_range_cmp_bounds(key, rb1, rb2) == 0;
+}
+
+/*
* RelationBuildDesc
*
* Build a relation descriptor. The caller must hold at least
@@ -1057,9 +1151,16 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
/* if it's a partitioned table, initialize key info */
if (relation->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+ {
RelationBuildPartitionKey(relation);
+ RelationBuildPartitionDesc(relation);
+ }
else
+ {
relation->rd_partkey = NULL;
+ relation->rd_partdesc = NULL;
+ relation->rd_pdcxt = NULL;
+ }
/*
* if it's an index, initialize index-related information
@@ -1403,6 +1504,164 @@ IndexSupportInitialize(oidvector *indclass,
}
/*
+ * RelationBuildPartitionDesc
+ * Form relation's partition descriptor from information in pg_partition
+ * and store it in the relcache
+ *
+ * Not flushed from the cache unless changed.
+ */
+static void
+RelationBuildPartitionDesc(Relation relation)
+{
+ int i,
+ nparts;
+ PartitionKey key = RelationGetPartitionKey(relation);
+ PartitionDesc result;
+ PartitionInfo **parts;
+ MemoryContext oldcxt;
+
+ /* Our pg_partitioned entry not up yet. */
+ if (key == NULL)
+ return;
+
+ /* Read partitions from the catalog */
+ parts = get_partition_info(relation, &nparts);
+
+ relation->rd_pdcxt = AllocSetContextCreate(CacheMemoryContext,
+ RelationGetRelationName(relation),
+ ALLOCSET_DEFAULT_MINSIZE,
+ ALLOCSET_DEFAULT_INITSIZE,
+ ALLOCSET_DEFAULT_MAXSIZE);
+
+ oldcxt = MemoryContextSwitchTo(relation->rd_pdcxt);
+ result = (PartitionDescData *) palloc0(sizeof(PartitionDescData));
+
+ result->nparts = nparts;
+ if (parts)
+ {
+ result->oids = (Oid *) palloc0(nparts * sizeof(Oid));
+
+ switch (key->strategy)
+ {
+ case PARTITION_STRAT_LIST:
+ result->lists = (PartitionListBound **) palloc0(nparts *
+ sizeof(PartitionListBound *));
+ break;
+
+ case PARTITION_STRAT_RANGE:
+ result->rangelowers = (PartitionRangeBound **) palloc0(nparts *
+ sizeof(PartitionRangeBound *));
+ result->rangeuppers = (PartitionRangeBound **) palloc0(nparts *
+ sizeof(PartitionRangeBound *));
+
+ /* Sort on range->upper */
+ qsort_arg(parts, nparts, sizeof(PartitionInfo *),
+ range_partition_cmp_upper, key);
+ break;
+ }
+
+ for (i = 0; i < nparts; i++)
+ {
+ result->oids[i] = parts[i]->oid;
+
+ if (parts[i]->list)
+ result->lists[i] = copyPartitionListBound(parts[i]->list, key);
+ else
+ {
+ result->rangelowers[i] = copyPartitionRangeBound(parts[i]->rangelower, key);
+ result->rangeuppers[i] = copyPartitionRangeBound(parts[i]->rangeupper, key);
+ }
+ }
+
+ /* Clean up */
+ free_partition_info(parts, nparts);
+ }
+
+ MemoryContextSwitchTo(oldcxt);
+ relation->rd_partdesc = result;
+}
+
+/*
+ * range_partition_cmp_upper
+ * Compare two range partition PartitionInfos using their rangeupper
+ * bounds
+ *
+ * Used as a callback to qsort_arg for sorting range partitions.
+ */
+static int32
+range_partition_cmp_upper(const void *a, const void *b, void *arg)
+{
+ int32 result;
+ int i;
+ PartitionKey key = (PartitionKey) arg;
+ PartitionRangeBound *b1 = (*(const PartitionInfo **) a)->rangeupper,
+ *b2 = (*(const PartitionInfo **) b)->rangeupper;
+
+ if (b1->infinite || b2->infinite)
+ return b1->infinite ? 1 : -1;
+
+ for (i = 0; i < key->partnatts; i++)
+ {
+ result = DatumGetInt32(FunctionCall2Coll(&key->partsupfunc[i],
+ key->tcinfo->typcoll[i],
+ b1->val[i], b2->val[i]));
+ if (!result)
+ continue;
+
+ break;
+ }
+
+ return result;
+}
+
+/*
+ * Helper routine to copy a list partition bound struct
+ */
+static PartitionListBound *
+copyPartitionListBound(PartitionListBound *src, PartitionKey key)
+{
+ int i;
+ PartitionListBound *result;
+
+ result = (PartitionListBound *) palloc0(sizeof(PartitionListBound));
+ result->nvalues = src->nvalues;
+ result->contains_null = src->contains_null;
+ result->values = (Datum *) palloc0(src->nvalues * sizeof(Datum));
+
+ for (i = 0; i < src->nvalues; i++)
+ result->values[i] = datumCopy(src->values[i],
+ key->tcinfo->typbyval[0],
+ key->tcinfo->typlen[0]);
+ return result;
+}
+
+/*
+ * Helper routine to copy a range partition bound struct
+ */
+static PartitionRangeBound *
+copyPartitionRangeBound(PartitionRangeBound *src, PartitionKey key)
+{
+ int i;
+ PartitionRangeBound *result;
+
+ result = (PartitionRangeBound *) palloc0(sizeof(PartitionRangeBound));
+ result->infinite = src->infinite;
+ result->inclusive = src->inclusive;
+ result->lower = src->lower;
+
+ if (src->val)
+ {
+ result->val = (Datum *) palloc0(key->partnatts * sizeof(Datum));
+ for (i = 0; i < key->partnatts; i++)
+ result->val[i] = datumCopy(src->val[i],
+ key->tcinfo->typbyval[i],
+ key->tcinfo->typlen[i]);
+ }
+
+ return result;
+}
+
+/*
* LookupOpclassInfo
*
* This routine maintains a per-opclass cache of the information needed
@@ -2059,6 +2318,10 @@ RelationDestroyRelation(Relation relation, bool remember_tupdesc)
if (relation->rd_rsdesc)
MemoryContextDelete(relation->rd_rsdesc->rscxt);
FreePartitionKey(relation->rd_partkey);
+ if (relation->rd_pdcxt)
+ MemoryContextDelete(relation->rd_pdcxt);
+ if (relation->rd_partcheck)
+ pfree(relation->rd_partcheck);
if (relation->rd_fdwroutine)
pfree(relation->rd_fdwroutine);
pfree(relation);
@@ -2222,6 +2485,7 @@ RelationClearRelation(Relation relation, bool rebuild)
bool keep_tupdesc;
bool keep_rules;
bool keep_policies;
+ bool keep_partdesc;
/* Build temporary entry, but don't link it into hashtable */
newrel = RelationBuildDesc(save_relid, false);
@@ -2252,6 +2516,9 @@ RelationClearRelation(Relation relation, bool rebuild)
keep_tupdesc = equalTupleDescs(relation->rd_att, newrel->rd_att);
keep_rules = equalRuleLocks(relation->rd_rules, newrel->rd_rules);
keep_policies = equalRSDesc(relation->rd_rsdesc, newrel->rd_rsdesc);
+ keep_partdesc = equalPartitionDescs(relation->rd_partkey,
+ relation->rd_partdesc,
+ newrel->rd_partdesc);
/*
* Perform swapping of the relcache entry contents. Within this
@@ -2307,6 +2574,13 @@ RelationClearRelation(Relation relation, bool rebuild)
/* pgstat_info must be preserved */
SWAPFIELD(struct PgStat_TableStatus *, pgstat_info);
+ /* preserve old partdesc if no logical change */
+ if (keep_partdesc)
+ {
+ SWAPFIELD(PartitionDesc, rd_partdesc);
+ SWAPFIELD(MemoryContext, rd_pdcxt);
+ }
+
#undef SWAPFIELD
/* And now we can throw away the temporary entry */
@@ -3533,6 +3807,20 @@ RelationCacheInitializePhase3(void)
restart = true;
}
+ /*
+ * Reload partition key and descriptor for a partitioned table.
+ */
+ if (relation->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+ {
+ RelationBuildPartitionKey(relation);
+ Assert(relation->rd_partkey != NULL);
+
+ RelationBuildPartitionDesc(relation);
+ Assert(relation->rd_partdesc != NULL);
+
+ restart = true;
+ }
+
/* Release hold on the relation */
RelationDecrementReferenceCount(relation);
@@ -5057,6 +5345,8 @@ load_relcache_init_file(bool shared)
rel->trigdesc = NULL;
rel->rd_rsdesc = NULL;
rel->rd_partkey = NULL;
+ rel->rd_partdesc = NULL;
+ rel->rd_partcheck = NULL;
rel->rd_indexprs = NIL;
rel->rd_indpred = NIL;
rel->rd_exclops = NULL;
diff --git a/src/backend/utils/cache/syscache.c b/src/backend/utils/cache/syscache.c
index a6563fe..4a5783a 100644
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -49,6 +49,7 @@
#include "catalog/pg_operator.h"
#include "catalog/pg_opfamily.h"
#include "catalog/pg_partitioned.h"
+#include "catalog/pg_partition.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_range.h"
#include "catalog/pg_rewrite.h"
@@ -569,6 +570,17 @@ static const struct cachedesc cacheinfo[] = {
},
8
},
+ {PartitionRelationId, /* PARTRELID */
+ PartitionRelidIndexId,
+ 1,
+ {
+ Anum_pg_partition_partrelid,
+ 0,
+ 0,
+ 0
+ },
+ 128
+ },
{PartitionedRelationId, /* PARTEDRELID */
PartitionedRelidIndexId,
1,
diff --git a/src/bin/pg_dump/common.c b/src/bin/pg_dump/common.c
index 1cbb987..c8e56bd 100644
--- a/src/bin/pg_dump/common.c
+++ b/src/bin/pg_dump/common.c
@@ -68,6 +68,8 @@ static int numextmembers;
static void flagInhTables(TableInfo *tbinfo, int numTables,
InhInfo *inhinfo, int numInherits);
+static void flagPartitions(TableInfo *tblinfo, int numTables,
+ PartInfo *partinfo, int numPartitions);
static void flagInhAttrs(DumpOptions *dopt, TableInfo *tblinfo, int numTables);
static DumpableObject **buildIndexArray(void *objArray, int numObjs,
Size objSize);
@@ -75,6 +77,8 @@ static int DOCatalogIdCompare(const void *p1, const void *p2);
static int ExtensionMemberIdCompare(const void *p1, const void *p2);
static void findParentsByOid(TableInfo *self,
InhInfo *inhinfo, int numInherits);
+static void findPartitionParentByOid(TableInfo *self, PartInfo *partinfo,
+ int numPartitions);
static int strInArray(const char *pattern, char **arr, int arr_size);
@@ -93,8 +97,10 @@ getSchemaData(Archive *fout, int *numTablesPtr)
NamespaceInfo *nspinfo;
ExtensionInfo *extinfo;
InhInfo *inhinfo;
+ PartInfo *partinfo;
int numAggregates;
int numInherits;
+ int numPartitions;
int numRules;
int numProcLangs;
int numCasts;
@@ -232,6 +238,10 @@ getSchemaData(Archive *fout, int *numTablesPtr)
inhinfo = getInherits(fout, &numInherits);
if (g_verbose)
+ write_msg(NULL, "reading partition information\n");
+ partinfo = getPartitions(fout, &numPartitions);
+
+ if (g_verbose)
write_msg(NULL, "reading event triggers\n");
getEventTriggers(fout, &numEventTriggers);
@@ -245,6 +255,11 @@ getSchemaData(Archive *fout, int *numTablesPtr)
write_msg(NULL, "finding inheritance relationships\n");
flagInhTables(tblinfo, numTables, inhinfo, numInherits);
+ /* Link tables to partition parents, mark parents as interesting */
+ if (g_verbose)
+ write_msg(NULL, "finding partition relationships\n");
+ flagPartitions(tblinfo, numTables, partinfo, numPartitions);
+
if (g_verbose)
write_msg(NULL, "reading column info for interesting tables\n");
getTableAttrs(fout, tblinfo, numTables);
@@ -319,6 +334,43 @@ flagInhTables(TableInfo *tblinfo, int numTables,
}
}
+/* flagPartitions -
+ * Fill in parent link fields of every target table that is partition,
+ * and mark parents of partitions as interesting
+ *
+ * modifies tblinfo
+ */
+static void
+flagPartitions(TableInfo *tblinfo, int numTables,
+ PartInfo *partinfo, int numPartitions)
+{
+ int i;
+
+ for (i = 0; i < numTables; i++)
+ {
+ /* Some kinds are never partitions */
+ if (tblinfo[i].relkind == RELKIND_SEQUENCE ||
+ tblinfo[i].relkind == RELKIND_VIEW ||
+ tblinfo[i].relkind == RELKIND_MATVIEW)
+ continue;
+
+ /* Don't bother computing anything for non-target tables, either */
+ if (!tblinfo[i].dobj.dump)
+ continue;
+
+ /* Find the parent TableInfo and save */
+ findPartitionParentByOid(&tblinfo[i], partinfo, numPartitions);
+
+ /* Mark the parent as interesting for getTableAttrs */
+ if (tblinfo[i].partitionOf)
+ {
+ tblinfo[i].partitionOf->interesting = true;
+ addObjectDependency(&tblinfo[i].dobj,
+ tblinfo[i].partitionOf->dobj.dumpId);
+ }
+ }
+}
+
/* flagInhAttrs -
* for each dumpable table in tblinfo, flag its inherited attributes
*
@@ -920,6 +972,40 @@ findParentsByOid(TableInfo *self,
}
/*
+ * findPartitionParentByOid
+ * find a partition's parent in tblinfo[]
+ */
+static void
+findPartitionParentByOid(TableInfo *self, PartInfo *partinfo,
+ int numPartitions)
+{
+ Oid oid = self->dobj.catId.oid;
+ int i;
+
+ for (i = 0; i < numPartitions; i++)
+ {
+ if (partinfo[i].partrelid == oid)
+ {
+ TableInfo *parent;
+
+ parent = findTableByOid(partinfo[i].partparent);
+ if (parent == NULL)
+ {
+ write_msg(NULL, "failed sanity check, parent OID %u of table \"%s\" (OID %u) not found\n",
+ partinfo[i].partparent,
+ self->dobj.name,
+ oid);
+ exit_nicely(1);
+ }
+ self->partitionOf = parent;
+
+ /* While we're at it, also save the partdef */
+ self->partitiondef = partinfo[i].partdef;
+ }
+ }
+}
+
+/*
* parseOidArray
* parse a string of numbers delimited by spaces into a character array
*
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 16d8d33..476d0ca 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -6111,6 +6111,63 @@ getInherits(Archive *fout, int *numInherits)
}
/*
+ * getPartitions
+ * read all the partition inheritance and partition bound information
+ * from the system catalogs return them in the PartInfo* structure
+ *
+ * numPartitions is set to the number of pairs read in
+ */
+PartInfo *
+getPartitions(Archive *fout, int *numPartitions)
+{
+ PGresult *res;
+ int ntups;
+ int i;
+ PQExpBuffer query = createPQExpBuffer();
+ PartInfo *partinfo;
+
+ int i_partrelid;
+ int i_partparent;
+ int i_partbound;
+
+ /* Make sure we are in proper schema */
+ selectSourceSchema(fout, "pg_catalog");
+
+ /* find all the inheritance information */
+
+ appendPQExpBufferStr(query,
+ "SELECT partrelid, inhparent AS partparent, "
+ " pg_get_expr(partbound, partrelid) AS partbound "
+ "FROM pg_partition, pg_inherits "
+ "WHERE partrelid = inhrelid");
+
+ res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
+
+ ntups = PQntuples(res);
+
+ *numPartitions = ntups;
+
+ partinfo = (PartInfo *) pg_malloc(ntups * sizeof(PartInfo));
+
+ i_partrelid = PQfnumber(res, "partrelid");
+ i_partparent = PQfnumber(res, "partparent");
+ i_partbound = PQfnumber(res, "partbound");
+
+ for (i = 0; i < ntups; i++)
+ {
+ partinfo[i].partrelid = atooid(PQgetvalue(res, i, i_partrelid));
+ partinfo[i].partparent = atooid(PQgetvalue(res, i, i_partparent));
+ partinfo[i].partdef = pg_strdup(PQgetvalue(res, i, i_partbound));
+ }
+
+ PQclear(res);
+
+ destroyPQExpBuffer(query);
+
+ return partinfo;
+}
+
+/*
* getIndexes
* get information about every index on a dumpable table
*
@@ -15288,6 +15345,17 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
if (tbinfo->reloftype && !dopt->binary_upgrade)
appendPQExpBuffer(q, " OF %s", tbinfo->reloftype);
+ if (tbinfo->partitionOf && !dopt->binary_upgrade)
+ {
+ TableInfo *parentRel = tbinfo->partitionOf;
+
+ appendPQExpBuffer(q, " PARTITION OF ");
+ if (parentRel->dobj.namespace != tbinfo->dobj.namespace)
+ appendPQExpBuffer(q, "%s.",
+ fmtId(parentRel->dobj.namespace->dobj.name));
+ appendPQExpBufferStr(q, fmtId(parentRel->dobj.name));
+ }
+
if (tbinfo->relkind != RELKIND_MATVIEW)
{
/* Dump the attributes */
@@ -15316,8 +15384,11 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
(!tbinfo->inhNotNull[j] ||
dopt->binary_upgrade));
- /* Skip column if fully defined by reloftype */
- if (tbinfo->reloftype &&
+ /*
+ * Skip column if fully defined by reloftype or the
+ * partition parent.
+ */
+ if ((tbinfo->reloftype || tbinfo->partitionOf) &&
!has_default && !has_notnull && !dopt->binary_upgrade)
continue;
@@ -15346,7 +15417,8 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
}
/* Attribute type */
- if (tbinfo->reloftype && !dopt->binary_upgrade)
+ if ((tbinfo->reloftype || tbinfo->partitionOf) &&
+ !dopt->binary_upgrade)
{
appendPQExpBufferStr(q, " WITH OPTIONS");
}
@@ -15411,15 +15483,22 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
if (actual_atts)
appendPQExpBufferStr(q, "\n)");
- else if (!(tbinfo->reloftype && !dopt->binary_upgrade))
+ else if (!((tbinfo->reloftype || tbinfo->partitionOf) &&
+ !dopt->binary_upgrade))
{
/*
* We must have a parenthesized attribute list, even though
- * empty, when not using the OF TYPE syntax.
+ * empty, when not using the OF TYPE or PARTITION OF syntax.
*/
appendPQExpBufferStr(q, " (\n)");
}
+ if (tbinfo->partitiondef && !dopt->binary_upgrade)
+ {
+ appendPQExpBufferStr(q, "\n");
+ appendPQExpBufferStr(q, tbinfo->partitiondef);
+ }
+
if (numParents > 0 && !dopt->binary_upgrade)
{
appendPQExpBufferStr(q, "\nINHERITS (");
@@ -15589,6 +15668,15 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
tbinfo->reloftype);
}
+ if (tbinfo->partitionOf)
+ {
+ appendPQExpBufferStr(q, "\n-- For binary upgrade, set up partitions this way.\n");
+ appendPQExpBuffer(q, "ALTER TABLE ONLY %s ATTACH PARTITION %s %s;\n",
+ fmtId(tbinfo->partitionOf->dobj.name),
+ tbinfo->dobj.name,
+ tbinfo->partitiondef);
+ }
+
appendPQExpBufferStr(q, "\n-- For binary upgrade, set heap's relfrozenxid and relminmxid\n");
appendPQExpBuffer(q, "UPDATE pg_catalog.pg_class\n"
"SET relfrozenxid = '%u', relminmxid = '%u'\n"
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 0292859..760067a 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -320,6 +320,8 @@ typedef struct _tableInfo
struct _tableDataInfo *dataObj; /* TableDataInfo, if dumping its data */
int numTriggers; /* number of triggers for table */
struct _triggerInfo *triggers; /* array of TriggerInfo structs */
+ struct _tableInfo *partitionOf; /* TableInfo for the partition parent */
+ char *partitiondef; /* partition key definition */
} TableInfo;
typedef struct _attrDefInfo
@@ -460,6 +462,15 @@ typedef struct _inhInfo
Oid inhparent; /* OID of its parent */
} InhInfo;
+/* PartInfo isn't a DumpableObject, just temporary state */
+typedef struct _partInfo
+{
+ Oid partrelid; /* OID of a partition */
+ Oid partparent; /* OID of its parent */
+ char *partdef; /* partition bound definition */
+} PartInfo;
+
+
typedef struct _prsInfo
{
DumpableObject dobj;
@@ -626,6 +637,7 @@ extern ConvInfo *getConversions(Archive *fout, int *numConversions);
extern TableInfo *getTables(Archive *fout, int *numTables);
extern void getOwnedSeqs(Archive *fout, TableInfo tblinfo[], int numTables);
extern InhInfo *getInherits(Archive *fout, int *numInherits);
+extern PartInfo *getPartitions(Archive *fout, int *numPartitions);
extern void getIndexes(Archive *fout, TableInfo tblinfo[], int numTables);
extern void getConstraints(Archive *fout, TableInfo tblinfo[], int numTables);
extern RuleInfo *getRules(Archive *fout, int *numRules);
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 71d6cdc..28972c2 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -1758,6 +1758,34 @@ describeOneTableDetails(const char *schemaname,
}
/* Make footers */
+ if (pset.sversion >= 90600)
+ {
+ /* Get the partition information */
+ PGresult *result;
+ char *parent_name;
+ char *partdef;
+
+ printfPQExpBuffer(&buf,
+ "SELECT inhparent::pg_catalog.regclass, pg_get_expr(partbound, partrelid)"
+ " FROM pg_catalog.pg_partition p"
+ " JOIN pg_catalog.pg_inherits i"
+ " ON p.partrelid = i.inhrelid"
+ " WHERE p.partrelid = '%s';", oid);
+ result = PSQLexec(buf.data);
+ if (!result)
+ goto error_return;
+
+ if (PQntuples(result) > 0)
+ {
+ parent_name = PQgetvalue(result, 0, 0);
+ partdef = PQgetvalue(result, 0, 1);
+ printfPQExpBuffer(&tmpbuf, _("Partition Of: %s %s"), parent_name,
+ partdef);
+ printTableAddFooter(&cont, tmpbuf.data);
+ PQclear(result);
+ }
+ }
+
if (pset.sversion >= 90600 && tableinfo.relkind == 'P')
{
/* Get the partition key information */
@@ -2519,8 +2547,12 @@ describeOneTableDetails(const char *schemaname,
PQclear(result);
}
- /* print inherited tables */
- printfPQExpBuffer(&buf, "SELECT c.oid::pg_catalog.regclass FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i WHERE c.oid=i.inhparent AND i.inhrelid = '%s' ORDER BY inhseqno;", oid);
+ /* print inherited tables (exclude, if parent is a partitioned table) */
+ printfPQExpBuffer(&buf,
+ "SELECT c.oid::pg_catalog.regclass"
+ " FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i"
+ " WHERE c.oid=i.inhparent AND i.inhrelid = '%s'"
+ " AND c.relkind != 'P' ORDER BY inhseqno;", oid);
result = PSQLexec(buf.data);
if (!result)
@@ -2549,9 +2581,15 @@ describeOneTableDetails(const char *schemaname,
PQclear(result);
}
- /* print child tables */
+ /* print child tables (exclude, if parent is a partitioned table) */
if (pset.sversion >= 80300)
- printfPQExpBuffer(&buf, "SELECT c.oid::pg_catalog.regclass FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i WHERE c.oid=i.inhrelid AND i.inhparent = '%s' ORDER BY c.oid::pg_catalog.regclass::pg_catalog.text;", oid);
+ printfPQExpBuffer(&buf,
+ "SELECT c.oid::pg_catalog.regclass"
+ " FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i"
+ " WHERE c.oid=i.inhrelid AND"
+ " i.inhparent = '%s' AND"
+ " EXISTS (SELECT 1 FROM pg_class c WHERE c.oid = '%s' AND c.relkind != 'P')"
+ " ORDER BY c.oid::pg_catalog.regclass::pg_catalog.text;", oid, oid);
else
printfPQExpBuffer(&buf, "SELECT c.oid::pg_catalog.regclass FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i WHERE c.oid=i.inhrelid AND i.inhparent = '%s' ORDER BY c.relname;", oid);
diff --git a/src/include/catalog/indexing.h b/src/include/catalog/indexing.h
index cadb741..73786c0 100644
--- a/src/include/catalog/indexing.h
+++ b/src/include/catalog/indexing.h
@@ -322,6 +322,9 @@ DECLARE_UNIQUE_INDEX(pg_replication_origin_roname_index, 6002, on pg_replication
DECLARE_UNIQUE_INDEX(pg_partitioned_partedrelid_index, 3351, on pg_partitioned using btree(partedrelid oid_ops));
#define PartitionedRelidIndexId 3351
+DECLARE_UNIQUE_INDEX(pg_partition_partrelid_index, 3354, on pg_partition using btree(partrelid oid_ops));
+#define PartitionRelidIndexId 3354
+
/* last step of initialization script: build the indexes declared above */
BUILD_INDICES
diff --git a/src/include/catalog/partition.h b/src/include/catalog/partition.h
index b3f3a1b..c1120e3 100644
--- a/src/include/catalog/partition.h
+++ b/src/include/catalog/partition.h
@@ -43,6 +43,76 @@ typedef struct PartitionKeyData
typedef struct PartitionKeyData *PartitionKey;
+/* Internal representation of a list partition bound */
+typedef struct PartitionListBound
+{
+ bool contains_null;
+ int nvalues; /* number of values in the following array */
+ Datum *values; /* values contained in the list */
+} PartitionListBound;
+
+/* Internal representation of a range partition bound */
+typedef struct
+{
+ Datum *val; /* composite bound value, if any */
+ bool infinite; /* bound is +/- infinity */
+ bool inclusive; /* bound is inclusive (vs exclusive) */
+ bool lower; /* this is the lower (vs upper) bound */
+} PartitionRangeBound;
+
+/*
+ * Information about a single partition
+ */
+typedef struct PartitionInfo
+{
+ Oid oid; /* partition OID */
+ PartitionListBound *list; /* list partition bound */
+ PartitionRangeBound *rangelower; /* range partition lower bound */
+ PartitionRangeBound *rangeupper; /* range partition upper bound */
+} PartitionInfo;
+
+/*
+ * Information about partitions of a partitioned table.
+ */
+typedef struct PartitionDescData
+{
+ int nparts; /* number of partitions */
+ Oid *oids; /* OIDs of the partitions */
+ PartitionListBound **lists; /* list bounds */
+ PartitionRangeBound **rangelowers; /* range lower bounds */
+ PartitionRangeBound **rangeuppers; /* range upper bounds */
+} PartitionDescData;
+
+typedef struct PartitionDescData *PartitionDesc;
+
+/*
+ * Partition tree node
+ *
+ * pkinfo PartitionKey executor state
+ * pdesc info about immediate partitions (see
+ PartitionDescData)
+ * index index within 0..(parent_pdesc->nparts - 1)
+ * num_leaf_partitions number of leaf partitions in the partition
+ * tree rooted at this node
+ * offset index (0-based) of the first leaf partition
+ * in the partition tree rooted at this node
+ * downlink link to the leftmost child node
+ * next link to the right sibling node on a given level
+ */
+typedef struct PartitionDescNodeData
+{
+ PartitionDesc pdesc;
+ Oid relid;
+ int index;
+ int offset;
+ int num_leaf_partitions;
+
+ struct PartitionDescNodeData *downlink;
+ struct PartitionDescNodeData *next;
+} PartitionDescNodeData;
+
+typedef struct PartitionDescNodeData *PartitionDescNode;
+
extern void StorePartitionKey(Relation rel,
char strategy,
int16 partnatts,
@@ -54,4 +124,19 @@ extern void RelationBuildPartitionKey(Relation relation);
extern void FreePartitionKey(PartitionKey key);
extern bool relid_is_partitioned(Oid relid);
+extern void StorePartitionBound(Oid relid, Oid parentId, Node *bound);
+extern void RemovePartitionEntryByRelId(Oid relid);
+extern bool relid_is_partition(Oid relid);
+extern Oid get_partition_parent(Oid relid);
+extern void RelationDropPartitions(Oid relid);
+extern PartitionInfo **get_partition_info(Relation rel, int *nparts);
+extern void free_partition_info(PartitionInfo **p, int num);
+extern int32 partition_range_cmp_bounds(PartitionKey key,
+ PartitionRangeBound *b1,
+ PartitionRangeBound *b2);
+extern List *RelationGetPartitionCheckQual(Relation rel);
+extern List *get_check_qual_from_partbound(Relation rel, Relation parent,
+ Node *bound);
+extern List *get_leaf_partition_oids(PartitionDescNode pdnode);
+extern PartitionDescNode RelationGetPartitionDescNode(Relation rel);
#endif /* PARTITION_H */
diff --git a/src/include/catalog/pg_partition.h b/src/include/catalog/pg_partition.h
new file mode 100644
index 0000000..d31a6b7
--- /dev/null
+++ b/src/include/catalog/pg_partition.h
@@ -0,0 +1,54 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_partition.h
+ * definition of the system "partition" relation (pg_partition)
+ * along with the relation's initial contents.
+ *
+ *
+ * Portions Copyright (c) 1996-2015, PostgreSQL Global Development Group
+ *
+ * $PostgreSQL: pgsql/src/include/catalog/pg_partition.h $
+ *
+ * NOTES
+ * the genbki.sh script reads this file and generates .bki
+ * information from the DATA() statements.
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_PARTITION_H
+#define PG_PARTITION_H
+
+#include "catalog/genbki.h"
+
+/* ----------------
+ * pg_partitioned_rel definition. cpp turns this into
+ * typedef struct FormData_pg_partitioned_rel
+ * ----------------
+ */
+#define PartitionRelationId 3353
+
+CATALOG(pg_partition,3353) BKI_WITHOUT_OIDS
+{
+ Oid partrelid; /* partition relation OID */
+
+#ifdef CATALOG_VARLEN /* variable-length fields start here */
+ pg_node_tree partbound; /* node representation of partition bound */
+#endif
+} FormData_pg_partition;
+
+/* ----------------
+ * Form_pg_partition corresponds to a pointer to a tuple with
+ * the format of pg_partition relation.
+ * ----------------
+ */
+typedef FormData_pg_partition *Form_pg_partition;
+
+/* ----------------
+ * compiler constants for pg_partition
+ * ----------------
+ */
+#define Natts_pg_partition 2
+#define Anum_pg_partition_partrelid 1
+#define Anum_pg_partition_partbound 2
+
+#endif /* PG_PARTITION_H */
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index e7fd7bd..64af12c 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -319,6 +319,8 @@ typedef struct JunkFilter
* projectReturning for computing a RETURNING list
* onConflictSetProj for computing ON CONFLICT DO UPDATE SET
* onConflictSetWhere list of ON CONFLICT DO UPDATE exprs (qual)
+ * PartitionCheck partition check expression
+ * PartitionCheckExpr partition check expression state
* ----------------
*/
typedef struct ResultRelInfo
@@ -343,6 +345,8 @@ typedef struct ResultRelInfo
ProjectionInfo *ri_projectReturning;
ProjectionInfo *ri_onConflictSetProj;
List *ri_onConflictSetWhere;
+ Expr *ri_PartitionCheck;
+ List *ri_PartitionCheckExpr;
} ResultRelInfo;
/* ----------------
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index e05d122..7121390 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -95,6 +95,9 @@ typedef struct RelationData
bool rd_fkeyvalid; /* true if list has been computed */
struct PartitionKeyData *rd_partkey; /* partition key, or NULL */
+ MemoryContext rd_pdcxt; /* private context for partdesc */
+ struct PartitionDescData *rd_partdesc; /* partitions, or NULL */
+ List *rd_partcheck; /* partition CHECK quals */
/* data managed by RelationGetIndexList: */
List *rd_indexlist; /* list of OIDs of indexes on relation */
@@ -540,6 +543,12 @@ typedef struct ViewOptions
*/
#define RelationGetPartitionKey(relation) ((relation)->rd_partkey)
+/*
+ * RelationGetPartitionDesc
+ * Returns partition descriptor for a relation.
+ */
+#define RelationGetPartitionDesc(relation) ((relation)->rd_partdesc)
+
/* routines in utils/cache/relcache.c */
extern void RelationIncrementReferenceCount(Relation rel);
extern void RelationDecrementReferenceCount(Relation rel);
diff --git a/src/include/utils/syscache.h b/src/include/utils/syscache.h
index e727842..4e493e0 100644
--- a/src/include/utils/syscache.h
+++ b/src/include/utils/syscache.h
@@ -72,6 +72,7 @@ enum SysCacheIdentifier
OPEROID,
OPFAMILYAMNAMENSP,
OPFAMILYOID,
+ PARTRELID,
PARTEDRELID,
PROCNAMEARGSNSP,
PROCOID,
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index b1e473d..57bde9d 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -118,6 +118,7 @@ pg_namespace|t
pg_opclass|t
pg_operator|t
pg_opfamily|t
+pg_partition|t
pg_partitioned|t
pg_pltemplate|t
pg_policy|t
--
1.7.1
[text/x-diff] 0006-Introduce-tuple-routing-for-partitioned-tables-v7.patch (37.9K, ../../81371428-bb4b-1e33-5ad6-8c5c51b52cb7@lab.ntt.co.jp/7-0006-Introduce-tuple-routing-for-partitioned-tables-v7.patch)
download | inline diff:
From f2d3149b370a27d812ad7f830fdd1e1e9a5088d3 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Fri, 18 Mar 2016 19:48:49 +0900
Subject: [PATCH 6/6] Introduce tuple routing for partitioned tables.
A tuple inserted into a partitioned table using insert or copy is now
routed to the appropriate leaf partition, recursively.
State required for the same is built in advance by BeginCopy() and
ExecInitModifyTable(). It consists of N ResultRelInfos and pointers
to TupleConversionMap structs, where N is the number of leaf partitions
in the multi-level partition tree. One needs only to get a pointer to the
root PartitionDescNode node for the tree. A tuple conversion map is only
allocated if the tuple descriptor of a leaf partition differs from that
of the root table. Partition check expression is not initialized for
leaf partition result relations, as they are redundant in this case.
Whether or not to build one is now determined by new parameter
load_partition_check of InitResultRelInfo().
CopyFrom() or ExecInsert() passes the root node (set up initially, as
just mentioned) along with the tuple slot to get_partition_for_tuple()
which traverses the tree to determine the appropriate leaf partition for
the tuple. It returns the index (0..N) of the leaf partition. Caller
then switches the result relation (ResultRelInfo) appropriately and
performs the tuple conversion, if required, before following up with
the row triggers, constraints, actual heap and index insertion.
* Notes about foreign partitions:
Foreign partitions require special consideration as a suitable insert plan
needs to be created in advance. So, make_modifytable() invokes
PlanForeignModify() for every leaf partition that is a foreign table.
Resulting plans are stored in ModifyTable.fdwPrivLists. Subsequently,
ExecInitModifyTable() initializes ForeignModifys for each FDW partition
using the information in fdwPrivLists. Similarly, BeginCopy() sets up
CopyState.partition_fdw_priv_lists (process in this case is more dramatic
than in ModifyTable's) and later CopyFrom() initializes ForeignModifys.
* Following requires further consideration:
Both CopyFrom() and ExecInsert() switch to the partition before invoking
BR triggers and back after AR trigger are processed. That ignores parent's
triggers completely. Note that those triggers are not required to be present
in each partition. OTOH, constraints are fine (both check and not null),
since parent's constraints are present in partitions.
---
src/backend/catalog/partition.c | 352 +++++++++++++++++++++++++++++++
src/backend/commands/copy.c | 198 +++++++++++++++++-
src/backend/commands/tablecmds.c | 1 +
src/backend/executor/execMain.c | 46 ++++-
src/backend/executor/nodeModifyTable.c | 116 ++++++++++
src/backend/optimizer/plan/createplan.c | 59 +++++
src/backend/optimizer/util/plancat.c | 13 ++
src/backend/parser/analyze.c | 8 +
src/include/catalog/partition.h | 25 +++
src/include/executor/executor.h | 6 +
src/include/nodes/execnodes.h | 10 +
src/include/optimizer/plancat.h | 1 +
12 files changed, 828 insertions(+), 7 deletions(-)
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index 3404823..69fc362 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -96,6 +96,21 @@ static List *generate_partition_check_qual(Relation rel);
static PartitionDescNode GetPartitionDescNodeRecurse(Relation rel, int offset);
static int get_leaf_partition_count(PartitionDescNode pdnode);
+static PartitionKeyExecInfo *BuildPartitionKeyExecInfo(Relation rel);
+static void FormPartitionKeyDatum(PartitionKeyExecInfo *pkinfo,
+ TupleTableSlot *slot,
+ EState *estate,
+ Datum *values,
+ bool *isnull);
+static int list_partition_for_tuple(PartitionKey key, PartitionDesc pdesc,
+ Datum value, bool isnull);
+static int range_partition_for_tuple(PartitionKey key, PartitionDesc pdesc,
+ Datum *values);
+static int range_partition_bsearch(PartitionKey key, PartitionDesc pdesc,
+ Datum *values);
+static bool rightof(PartitionKey key, Datum *values, PartitionRangeBound *bound);
+static bool leftof(PartitionKey key, Datum *values, PartitionRangeBound *bound);
+
/*
* StorePartitionKey
* Store the partition keys of rel into pg_partitioned catalog
@@ -978,6 +993,34 @@ get_partition_parent(Oid relid)
}
/*
+ * get_leaf_partitions
+ * Returns a list of all leaf-level partitions of relation with OID
+ * 'relid'.
+ */
+List *
+get_leaf_partitions(Oid relid, int lockmode)
+{
+ List *partitions,
+ *result = NIL;
+ ListCell *lc;
+
+ partitions = get_partitions(relid, lockmode);
+
+ foreach(lc, partitions)
+ {
+ Oid myoid = lfirst_oid(lc);
+
+ if (relid_is_partitioned(myoid))
+ result = list_concat(result,
+ get_leaf_partitions(myoid, lockmode));
+
+ result = lappend_oid(result, myoid);
+ }
+
+ return result;
+}
+
+/*
* RelationDropPartitions
* Find and drop all partitions of relation with OID 'relid'
*/
@@ -1699,6 +1742,7 @@ GetPartitionDescNodeRecurse(Relation rel, int offset)
/* First build our own node */
root = (PartitionDescNode) palloc0(sizeof(PartitionDescNodeData));
+ root->pkinfo = BuildPartitionKeyExecInfo(rel);
root->pdesc = pdesc;
root->relid = RelationGetRelid(rel);
root->offset = offset;
@@ -1816,3 +1860,311 @@ get_leaf_partition_oids(PartitionDescNode pdnode)
return result;
}
+
+/* ----------------
+ * BuildPartitionKeyExecInfo
+ * Construct a list of PartitionKeyExecInfo records for an open
+ * relation
+ *
+ * PartitionKeyExecInfo stores the information about the partition key
+ * that's needed when inserting tuples into a partitioned table; especially,
+ * partition key expression state if there are any expression columns in
+ * the partition key. Normally we build a PartitionKeyExecInfo for a
+ * partitioned table just once per command, and then use it for (potentially)
+ * many tuples.
+ * ----------------
+ */
+static PartitionKeyExecInfo *
+BuildPartitionKeyExecInfo(Relation rel)
+{
+ PartitionKeyExecInfo *pkinfo;
+
+ pkinfo = (PartitionKeyExecInfo *) palloc0(sizeof(PartitionKeyExecInfo));
+ pkinfo->pi_Key = CopyPartitionKey(rel->rd_partkey);
+ pkinfo->pi_ExpressionState = NIL;
+
+ return pkinfo;
+}
+
+/*
+ * get_partition_for_tuple
+ * Recursively finds the "leaf" partition for tuple (slot)
+ *
+ * Returns -1 if no partition is found and sets *failed_at to the OID of
+ * the partitioned table whose partition was not found.
+ */
+int
+get_partition_for_tuple(PartitionDescNode pdnode,
+ TupleTableSlot *slot,
+ EState *estate,
+ Oid *failed_at)
+{
+ PartitionKeyExecInfo *pkinfo = pdnode->pkinfo;
+ PartitionDescNode node;
+ Datum values[PARTITION_MAX_KEYS];
+ bool isnull[PARTITION_MAX_KEYS];
+ int i;
+ int index;
+
+ /* Guard against stack overflow due to overly deep partition tree */
+ check_stack_depth();
+
+ if (pdnode->pdesc->nparts == 0)
+ {
+ *failed_at = pdnode->relid;
+ return -1;
+ }
+
+ /* Extract partition key from tuple */
+ Assert(GetPerTupleExprContext(estate)->ecxt_scantuple == slot);
+ FormPartitionKeyDatum(pkinfo, slot, estate, values, isnull);
+
+ /* Disallow nulls, if range partition key */
+ for (i = 0; i < pkinfo->pi_Key->partnatts; i++)
+ if (isnull[i] && pkinfo->pi_Key->strategy == PARTITION_STRAT_RANGE)
+ ereport(ERROR,
+ (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+ errmsg("range partition key contains null")));
+
+ switch (pkinfo->pi_Key->strategy)
+ {
+ case PARTITION_STRAT_LIST:
+ index = list_partition_for_tuple(pkinfo->pi_Key, pdnode->pdesc,
+ values[0], isnull[0]);
+ break;
+
+ case PARTITION_STRAT_RANGE:
+ index = range_partition_for_tuple(pkinfo->pi_Key, pdnode->pdesc,
+ values);
+ break;
+ }
+
+ /* Don't recurse, if no partition exists for the key at this level... */
+ if (index < 0)
+ {
+ *failed_at = pdnode->relid;
+ return index;
+ }
+
+ /* ... or, if the index'th partition is a leaf partition. */
+ if (!relid_is_partitioned(pdnode->pdesc->oids[index]))
+ {
+ PartitionDescNode prev;
+
+ /* Patiently determine own leaf partition index */
+ prev = node = pdnode->downlink;
+ if (node && node->index < index)
+ {
+ while (node)
+ {
+ if (node->index > index)
+ {
+ node = prev;
+ break;
+ }
+
+ prev = node;
+ node = node->next;
+ }
+
+ if (!node)
+ node = prev;
+
+ /*
+ * Account for siblings on left that are partition trees and those
+ * that are leaf partitions themsleves.
+ */
+ return node->offset + node->num_leaf_partitions +
+ (index - node->index - 1);
+ }
+ else
+ /* Only leaf partitions in pdnode->offset..index */
+ return pdnode->offset + index;
+
+ }
+
+ /* Recurse, by locating the index'th partition's node */
+ node = pdnode->downlink;
+ while (node->next != NULL && node->index != index)
+ node = node->next;
+ Assert (node != NULL);
+
+ return get_partition_for_tuple(node, slot, estate, failed_at);
+}
+
+/*
+ * FormPartitionKeyDatum
+ * Construct values[] and isnull[] arrays for partition key columns
+ */
+static void
+FormPartitionKeyDatum(PartitionKeyExecInfo *pkinfo,
+ TupleTableSlot *slot,
+ EState *estate,
+ Datum *values,
+ bool *isnull)
+{
+ ListCell *partexpr_item;
+ int i;
+
+ if (pkinfo->pi_Key->partexprs != NIL && pkinfo->pi_ExpressionState == NIL)
+ {
+ /* First time through, set up expression evaluation state */
+ pkinfo->pi_ExpressionState = (List *)
+ ExecPrepareExpr((Expr *) pkinfo->pi_Key->partexprs,
+ estate);
+ /* Check caller has set up context correctly */
+ Assert(GetPerTupleExprContext(estate)->ecxt_scantuple == slot);
+ }
+
+ partexpr_item = list_head(pkinfo->pi_ExpressionState);
+ for (i = 0; i < pkinfo->pi_Key->partnatts; i++)
+ {
+ AttrNumber keycol = pkinfo->pi_Key->partattrs[i];
+ Datum pkDatum;
+ bool isNull;
+
+ if (keycol != 0)
+ {
+ /* Plain column; get the value directly from the heap tuple */
+ pkDatum = slot_getattr(slot, keycol, &isNull);
+ }
+ else
+ {
+ /* Expression; need to evaluate it */
+ if (partexpr_item == NULL)
+ elog(ERROR, "wrong number of partition key expressions");
+ pkDatum = ExecEvalExprSwitchContext((ExprState *) lfirst(partexpr_item),
+ GetPerTupleExprContext(estate),
+ &isNull,
+ NULL);
+ partexpr_item = lnext(partexpr_item);
+ }
+ values[i] = pkDatum;
+ isnull[i] = isNull;
+ }
+
+ if (partexpr_item != NULL)
+ elog(ERROR, "wrong number of partition key expressions");
+}
+
+/*
+ * list_partition_for_tuple
+ * Find the list partition for a tuple
+ *
+ * Returns -1 if none found.
+ */
+static int
+list_partition_for_tuple(PartitionKey key, PartitionDesc pdesc,
+ Datum value, bool isnull)
+{
+ int i;
+
+ Assert(pdesc->nparts > 0);
+
+ for (i = 0; i < pdesc->nparts; i++)
+ {
+ int j;
+
+ if (isnull)
+ {
+ if (pdesc->lists[i]->contains_null)
+ return i;
+
+ continue;
+ }
+
+ for (j = 0; j < pdesc->lists[i]->nvalues; j++)
+ {
+ int32 cmpval;
+
+ cmpval = DatumGetInt32(FunctionCall2Coll(&key->partsupfunc[0],
+ key->tcinfo->typcoll[0],
+ pdesc->lists[i]->values[j],
+ value));
+ if (!cmpval)
+ return i;
+ }
+ }
+
+ return -1;
+}
+
+/*
+ * range_partition_for_tuple
+ * Search the range partition for a range key ('values')
+ *
+ * Returns -1 if none found.
+ */
+static int
+range_partition_for_tuple(PartitionKey key, PartitionDesc pdesc, Datum *values)
+{
+ Assert(pdesc->nparts > 0);
+
+ return range_partition_bsearch(key, pdesc, values);
+}
+
+/*
+ * range_partition_bsearch
+ * Workhorse of range_partition_for_tuple
+ */
+static int
+range_partition_bsearch(PartitionKey key, PartitionDesc pdesc,
+ Datum *values)
+{
+ int low, high;
+
+ /* Good ol' bsearch */
+ low = 0;
+ high = pdesc->nparts - 1;
+ while (low <= high)
+ {
+ int idx = (low + high) / 2;
+
+ if (pdesc->rangeuppers[idx]->infinite)
+ {
+ if (rightof(key, values, pdesc->rangelowers[idx]))
+ return idx;
+
+ break;
+ }
+ else if (leftof(key, values, pdesc->rangeuppers[idx]))
+ {
+ if (pdesc->rangelowers[idx]->infinite)
+ return idx;
+
+ if (rightof(key, values, pdesc->rangelowers[idx]))
+ return idx;
+
+ high = idx - 1;
+ continue;
+ }
+
+ low = idx + 1;
+ }
+
+ return -1;
+}
+
+/* Does range key lie to the right of partition bound */
+static bool
+rightof(PartitionKey key, Datum *values, PartitionRangeBound *bound)
+{
+ int32 cmpval = compare_range_keys(key, values, bound->val);
+
+ if (!cmpval)
+ return bound->lower ? bound->inclusive : !bound->inclusive;
+
+ return cmpval > 0;
+}
+
+/* Does range key lie to the left of partition bound */
+static bool
+leftof(PartitionKey key, Datum *values, PartitionRangeBound *bound)
+{
+ int32 cmpval = compare_range_keys(key, values, bound->val);
+
+ if (!cmpval)
+ return !bound->lower ? bound->inclusive : !bound->inclusive;
+
+ return cmpval < 0;
+}
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 76803a5..0e2c61a 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -30,6 +30,7 @@
#include "commands/defrem.h"
#include "commands/trigger.h"
#include "executor/executor.h"
+#include "foreign/fdwapi.h"
#include "libpq/libpq.h"
#include "libpq/pqformat.h"
#include "mb/pg_wchar.h"
@@ -161,6 +162,11 @@ typedef struct CopyStateData
ExprState **defexprs; /* array of default att expressions */
bool volatile_defexprs; /* is any of defexprs volatile? */
List *range_table;
+ PartitionDescNode pdnode; /* partition descriptor node tree */
+ ResultRelInfo *partitions;
+ TupleConversionMap **partition_tupconv_maps;
+ List *partition_fdw_priv_lists;
+ int num_partitions;
/*
* These variables are used to reduce overhead in textual COPY FROM.
@@ -1364,6 +1370,87 @@ BeginCopy(bool is_from,
(errcode(ERRCODE_UNDEFINED_COLUMN),
errmsg("table \"%s\" does not have OIDs",
RelationGetRelationName(cstate->rel))));
+
+ /*
+ * Initialize state for CopyFrom tuple routing. Watch out for
+ * any foreign partitions.
+ */
+ if (is_from && rel->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+ {
+ List *leaf_part_oids;
+ ListCell *cell;
+ int i;
+ int num_leaf_parts;
+ ResultRelInfo *leaf_rel_rri;
+ PlannerInfo *root = makeNode(PlannerInfo); /* mostly dummy */
+ Query *parse = makeNode(Query); /* ditto */
+ ModifyTable *plan = makeNode(ModifyTable); /* ditto */
+ RangeTblEntry *fdw_rte = makeNode(RangeTblEntry); /* ditto */
+ List *fdw_private_lists = NIL;
+
+ cstate->pdnode = RelationGetPartitionDescNode(rel);
+ leaf_part_oids = get_leaf_partition_oids(cstate->pdnode);
+ num_leaf_parts = list_length(leaf_part_oids);
+
+ cstate->num_partitions = num_leaf_parts;
+ cstate->partitions = (ResultRelInfo *)
+ palloc0(num_leaf_parts * sizeof(ResultRelInfo));
+ cstate->partition_tupconv_maps = (TupleConversionMap **)
+ palloc0(num_leaf_parts * sizeof(TupleConversionMap *));
+
+ /* For use below, iff a partition found to be a foreign table */
+ plan->operation = CMD_INSERT;
+ plan->plans = list_make1(makeNode(Result));
+ fdw_rte->rtekind = RTE_RELATION;
+ fdw_rte->relkind = RELKIND_FOREIGN_TABLE;
+ parse->rtable = list_make1(fdw_rte);
+ root->parse = parse;
+
+ leaf_rel_rri = cstate->partitions;
+ i = 0;
+ foreach(cell, leaf_part_oids)
+ {
+ Relation leaf_rel;
+
+ leaf_rel = heap_open(lfirst_oid(cell), RowExclusiveLock);
+ InitResultRelInfo(leaf_rel_rri,
+ leaf_rel,
+ 1, /* dummy */
+ false, /* no need for partition check */
+ 0);
+
+ /* Open partition indices */
+ ExecOpenIndices(leaf_rel_rri, false);
+
+ /* Special dance for foreign tables */
+ if (leaf_rel_rri->ri_FdwRoutine)
+ {
+ List *fdw_private;
+
+ fdw_rte->relid = RelationGetRelid(leaf_rel);
+ fdw_private = leaf_rel_rri->ri_FdwRoutine->PlanForeignModify(root,
+ plan,
+ 1,
+ 0);
+ fdw_private_lists = lappend(fdw_private_lists, fdw_private);
+ }
+
+ if (!equalTupleDescs(tupDesc, RelationGetDescr(leaf_rel)))
+ cstate->partition_tupconv_maps[i] =
+ convert_tuples_by_name(tupDesc,
+ RelationGetDescr(leaf_rel),
+ gettext_noop("could not convert row type"));
+
+ leaf_rel_rri++;
+ i++;
+ }
+
+ cstate->partition_fdw_priv_lists = fdw_private_lists;
+ pfree(fdw_rte);
+ pfree(plan);
+ pfree(parse);
+ pfree(root);
+ }
}
else
{
@@ -1659,6 +1746,8 @@ ClosePipeToProgram(CopyState cstate)
static void
EndCopy(CopyState cstate)
{
+ int i;
+
if (cstate->is_program)
{
ClosePipeToProgram(cstate);
@@ -1672,6 +1761,23 @@ EndCopy(CopyState cstate)
cstate->filename)));
}
+ /* Close all partitions and indices thereof */
+ for (i = 0; i < cstate->num_partitions; i++)
+ {
+ ResultRelInfo *resultRelInfo = cstate->partitions + i;
+
+ ExecCloseIndices(resultRelInfo);
+ heap_close(resultRelInfo->ri_RelationDesc, NoLock);
+
+ /* XXX - EState not handy here to pass to EndForeignModify() */
+ if (resultRelInfo->ri_FdwRoutine &&
+ resultRelInfo->ri_FdwRoutine->EndForeignModify != NULL)
+ resultRelInfo->ri_FdwRoutine->EndForeignModify(NULL, resultRelInfo);
+
+ if (cstate->partition_tupconv_maps[i])
+ pfree(cstate->partition_tupconv_maps[i]);
+ }
+
MemoryContextDelete(cstate->copycontext);
pfree(cstate);
}
@@ -2216,6 +2322,7 @@ CopyFrom(CopyState cstate)
Datum *values;
bool *nulls;
ResultRelInfo *resultRelInfo;
+ ResultRelInfo *saved_resultRelInfo = NULL;
EState *estate = CreateExecutorState(); /* for ExecConstraints() */
ExprContext *econtext;
TupleTableSlot *myslot;
@@ -2236,7 +2343,8 @@ CopyFrom(CopyState cstate)
Assert(cstate->rel);
- if (cstate->rel->rd_rel->relkind != RELKIND_RELATION)
+ if (cstate->rel->rd_rel->relkind != RELKIND_RELATION &&
+ cstate->rel->rd_rel->relkind != RELKIND_PARTITIONED_REL)
{
if (cstate->rel->rd_rel->relkind == RELKIND_VIEW)
ereport(ERROR,
@@ -2344,6 +2452,7 @@ CopyFrom(CopyState cstate)
InitResultRelInfo(resultRelInfo,
cstate->rel,
1, /* dummy rangetable index */
+ true, /* do load partition check expression */
0);
ExecOpenIndices(resultRelInfo, false);
@@ -2371,6 +2480,7 @@ CopyFrom(CopyState cstate)
if ((resultRelInfo->ri_TrigDesc != NULL &&
(resultRelInfo->ri_TrigDesc->trig_insert_before_row ||
resultRelInfo->ri_TrigDesc->trig_insert_instead_row)) ||
+ cstate->pdnode != NULL ||
cstate->volatile_defexprs)
{
useHeapMultiInsert = false;
@@ -2392,10 +2502,46 @@ CopyFrom(CopyState cstate)
*/
ExecBSInsertTriggers(estate, resultRelInfo);
+ /* Initialize FDW partition insert plans */
+ if (cstate->pdnode)
+ {
+ int i,
+ j;
+ List *fdw_private_lists = cstate->partition_fdw_priv_lists;
+ ModifyTableState *mtstate = makeNode(ModifyTableState);
+ ResultRelInfo *leaf_part_rri;
+
+ /* Mostly dummy containing enough state for BeginForeignModify */
+ mtstate->ps.state = estate;
+ mtstate->operation = CMD_INSERT;
+
+ j = 0;
+ leaf_part_rri = cstate->partitions;
+ for (i = 0; i < cstate->num_partitions; i++)
+ {
+ if (leaf_part_rri->ri_FdwRoutine)
+ {
+ List *fdw_private;
+
+ Assert(fdw_private_lists);
+ fdw_private = list_nth(fdw_private_lists, j++);
+ leaf_part_rri->ri_FdwRoutine->BeginForeignModify(mtstate,
+ leaf_part_rri,
+ fdw_private,
+ 0, 0);
+ }
+ leaf_part_rri++;
+ }
+ }
+
values = (Datum *) palloc(tupDesc->natts * sizeof(Datum));
nulls = (bool *) palloc(tupDesc->natts * sizeof(bool));
- bistate = GetBulkInsertState();
+ if (useHeapMultiInsert)
+ bistate = GetBulkInsertState();
+ else
+ bistate = NULL;
+
econtext = GetPerTupleExprContext(estate);
/* Set up callback to identify error line number */
@@ -2447,6 +2593,31 @@ CopyFrom(CopyState cstate)
slot = myslot;
ExecStoreTuple(tuple, slot, InvalidBuffer, false);
+ /* Determine the partition */
+ saved_resultRelInfo = resultRelInfo;
+ if (cstate->pdnode)
+ {
+ int i_leaf_partition;
+ TupleConversionMap *map;
+
+ econtext->ecxt_scantuple = slot;
+ i_leaf_partition = ExecFindPartition(resultRelInfo,
+ cstate->pdnode,
+ slot,
+ estate);
+ Assert(i_leaf_partition >= 0 &&
+ i_leaf_partition < cstate->num_partitions);
+
+ resultRelInfo = cstate->partitions + i_leaf_partition;
+ estate->es_result_relation_info = resultRelInfo;
+
+ map = cstate->partition_tupconv_maps[i_leaf_partition];
+ if (map)
+ tuple = do_convert_tuple(tuple, map);
+
+ tuple->t_tableOid = RelationGetRelid(resultRelInfo->ri_RelationDesc);
+ }
+
skip_tuple = false;
/* BEFORE ROW INSERT Triggers */
@@ -2467,7 +2638,16 @@ CopyFrom(CopyState cstate)
if (cstate->rel->rd_att->constr || resultRelInfo->ri_PartitionCheck)
ExecConstraints(resultRelInfo, slot, estate);
- if (useHeapMultiInsert)
+ if (resultRelInfo->ri_FdwRoutine)
+ {
+ resultRelInfo->ri_FdwRoutine->ExecForeignInsert(estate,
+ resultRelInfo,
+ slot,
+ NULL);
+ /* AFTER ROW INSERT Triggers */
+ ExecARInsertTriggers(estate, resultRelInfo, tuple, NIL);
+ }
+ else if (useHeapMultiInsert)
{
/* Add this tuple to the tuple buffer */
if (nBufferedTuples == 0)
@@ -2497,7 +2677,8 @@ CopyFrom(CopyState cstate)
List *recheckIndexes = NIL;
/* OK, store the tuple and create index entries for it */
- heap_insert(cstate->rel, tuple, mycid, hi_options, bistate);
+ heap_insert(resultRelInfo->ri_RelationDesc,
+ tuple, mycid, hi_options, bistate);
if (resultRelInfo->ri_NumIndices > 0)
recheckIndexes = ExecInsertIndexTuples(slot, &(tuple->t_self),
@@ -2517,6 +2698,12 @@ CopyFrom(CopyState cstate)
* tuples inserted by an INSERT command.
*/
processed++;
+
+ if (saved_resultRelInfo)
+ {
+ resultRelInfo = saved_resultRelInfo;
+ estate->es_result_relation_info = resultRelInfo;
+ }
}
}
@@ -2530,7 +2717,8 @@ CopyFrom(CopyState cstate)
/* Done, clean up */
error_context_stack = errcallback.previous;
- FreeBulkInsertState(bistate);
+ if (bistate)
+ FreeBulkInsertState(bistate);
MemoryContextSwitchTo(oldcontext);
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5b83b8d..5911659 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -1237,6 +1237,7 @@ ExecuteTruncate(TruncateStmt *stmt)
InitResultRelInfo(resultRelInfo,
rel,
0, /* dummy rangetable index */
+ false,
0);
resultRelInfo++;
}
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index bbd2187..0d86e14 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -826,6 +826,7 @@ InitPlan(QueryDesc *queryDesc, int eflags)
InitResultRelInfo(resultRelInfo,
resultRelation,
resultRelationIndex,
+ true,
estate->es_instrument);
resultRelInfo++;
}
@@ -1215,6 +1216,7 @@ void
InitResultRelInfo(ResultRelInfo *resultRelInfo,
Relation resultRelationDesc,
Index resultRelationIndex,
+ bool load_partition_check,
int instrument_options)
{
MemSet(resultRelInfo, 0, sizeof(ResultRelInfo));
@@ -1252,8 +1254,9 @@ InitResultRelInfo(ResultRelInfo *resultRelInfo,
resultRelInfo->ri_ConstraintExprs = NULL;
resultRelInfo->ri_junkFilter = NULL;
resultRelInfo->ri_projectReturning = NULL;
- resultRelInfo->ri_PartitionCheck = (Expr *)
- RelationGetPartitionCheckQual(resultRelationDesc);
+ if (load_partition_check)
+ resultRelInfo->ri_PartitionCheck = (Expr *)
+ RelationGetPartitionCheckQual(resultRelationDesc);
}
/*
@@ -1316,6 +1319,7 @@ ExecGetTriggerResultRel(EState *estate, Oid relid)
InitResultRelInfo(rInfo,
rel,
0, /* dummy rangetable index */
+ true,
estate->es_instrument);
estate->es_trig_target_relations =
lappend(estate->es_trig_target_relations, rInfo);
@@ -2996,3 +3000,41 @@ EvalPlanQualEnd(EPQState *epqstate)
epqstate->planstate = NULL;
epqstate->origslot = NULL;
}
+
+int
+ExecFindPartition(ResultRelInfo *resultRelInfo, PartitionDescNode pdnode,
+ TupleTableSlot *slot, EState *estate)
+{
+ int i_leaf_partition;
+ Oid failed_at;
+
+ i_leaf_partition = get_partition_for_tuple(pdnode, slot, estate,
+ &failed_at);
+
+ if (i_leaf_partition < 0)
+ {
+ Relation rel = resultRelInfo->ri_RelationDesc;
+ char *val_desc;
+ Bitmapset *insertedCols,
+ *updatedCols,
+ *modifiedCols;
+ TupleDesc tupDesc = RelationGetDescr(rel);
+
+ insertedCols = GetInsertedColumns(resultRelInfo, estate);
+ updatedCols = GetUpdatedColumns(resultRelInfo, estate);
+ modifiedCols = bms_union(insertedCols, updatedCols);
+ val_desc = ExecBuildSlotValueDescription(RelationGetRelid(rel),
+ slot,
+ tupDesc,
+ modifiedCols,
+ 64);
+ Assert(OidIsValid(failed_at));
+ ereport(ERROR,
+ (errcode(ERRCODE_CHECK_VIOLATION),
+ errmsg("no partition of relation \"%s\" found for row",
+ get_rel_name(failed_at)),
+ val_desc ? errdetail("Failing row contains %s.", val_desc) : 0));
+ }
+
+ return i_leaf_partition;
+}
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 3ed321e..7fc2c19 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -243,6 +243,7 @@ ExecInsert(ModifyTableState *mtstate,
{
HeapTuple tuple;
ResultRelInfo *resultRelInfo;
+ ResultRelInfo *saved_resultRelInfo = NULL;
Relation resultRelationDesc;
Oid newId;
List *recheckIndexes = NIL;
@@ -257,6 +258,31 @@ ExecInsert(ModifyTableState *mtstate,
* get information on the (current) result relation
*/
resultRelInfo = estate->es_result_relation_info;
+
+ saved_resultRelInfo = resultRelInfo;
+
+ if (mtstate->mt_partition_node)
+ {
+ int i_leaf_partition;
+ ExprContext *econtext = GetPerTupleExprContext(estate);
+ TupleConversionMap *map;
+
+ econtext->ecxt_scantuple = slot;
+ i_leaf_partition = ExecFindPartition(resultRelInfo,
+ mtstate->mt_partition_node,
+ slot,
+ estate);
+ Assert(i_leaf_partition >= 0 &&
+ i_leaf_partition < mtstate->mt_num_partitions);
+
+ resultRelInfo = mtstate->mt_partitions + i_leaf_partition;
+ estate->es_result_relation_info = resultRelInfo;
+
+ map = mtstate->mt_partition_tupconv_maps[i_leaf_partition];
+ if (map)
+ tuple = do_convert_tuple(tuple, map);
+ }
+
resultRelationDesc = resultRelInfo->ri_RelationDesc;
/*
@@ -496,6 +522,12 @@ ExecInsert(ModifyTableState *mtstate,
list_free(recheckIndexes);
+ if (saved_resultRelInfo)
+ {
+ resultRelInfo = saved_resultRelInfo;
+ estate->es_result_relation_info = resultRelInfo;
+ }
+
/*
* Check any WITH CHECK OPTION constraints from parent views. We are
* required to do this after testing all constraints and uniqueness
@@ -1550,6 +1582,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
Plan *subplan;
ListCell *l;
int i;
+ Relation rel;
/* check for unsupported flags */
Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));
@@ -1640,6 +1673,72 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
estate->es_result_relation_info = saved_resultRelInfo;
+ /* Build state for INSERT tuple routing */
+ rel = mtstate->resultRelInfo->ri_RelationDesc;
+ if (operation == CMD_INSERT &&
+ rel->rd_rel->relkind == RELKIND_PARTITIONED_REL)
+ {
+ int i,
+ j,
+ num_leaf_parts;
+ List *leaf_part_oids;
+ ListCell *cell;
+ ResultRelInfo *leaf_rel_rri;
+
+ mtstate->mt_partition_node = RelationGetPartitionDescNode(rel);
+ leaf_part_oids = get_leaf_partition_oids(mtstate->mt_partition_node);
+ num_leaf_parts = list_length(leaf_part_oids);
+
+ mtstate->mt_num_partitions = num_leaf_parts;
+ mtstate->mt_partitions = (ResultRelInfo *)
+ palloc0(num_leaf_parts * sizeof(ResultRelInfo));
+ mtstate->mt_partition_tupconv_maps = (TupleConversionMap **)
+ palloc0(num_leaf_parts * sizeof(TupleConversionMap *));
+
+ leaf_rel_rri = mtstate->mt_partitions;
+ i = j = 0;
+ foreach(cell, leaf_part_oids)
+ {
+ Relation leaf_rel;
+
+ leaf_rel = heap_open(lfirst_oid(cell), RowExclusiveLock);
+ InitResultRelInfo(leaf_rel_rri,
+ leaf_rel,
+ 1, /* dummy */
+ false, /* no need for partition checks */
+ eflags);
+
+ /* Open partition indices (note: ON CONFLICT unsupported)*/
+ if (leaf_rel_rri->ri_RelationDesc->rd_rel->relhasindex &&
+ operation != CMD_DELETE &&
+ leaf_rel_rri->ri_IndexRelationDescs == NULL)
+ ExecOpenIndices(leaf_rel_rri, false);
+
+ if (leaf_rel_rri->ri_FdwRoutine)
+ {
+ /* As many fdw_private's in fdwPrivLists as FDW partitions */
+ List *fdw_private = (List *) list_nth(node->fdwPrivLists, j);
+
+ leaf_rel_rri->ri_FdwRoutine->BeginForeignModify(mtstate,
+ leaf_rel_rri,
+ fdw_private,
+ 0,
+ eflags);
+ j++;
+ }
+
+ if (!equalTupleDescs(RelationGetDescr(rel),
+ RelationGetDescr(leaf_rel)))
+ mtstate->mt_partition_tupconv_maps[i] =
+ convert_tuples_by_name(RelationGetDescr(rel),
+ RelationGetDescr(leaf_rel),
+ gettext_noop("could not convert row type"));
+
+ leaf_rel_rri++;
+ i++;
+ }
+ }
+
/*
* Initialize any WITH CHECK OPTION constraints if needed.
*/
@@ -1957,6 +2056,23 @@ ExecEndModifyTable(ModifyTableState *node)
resultRelInfo);
}
+ /* Close all partitions and indices thereof */
+ for (i = 0; i < node->mt_num_partitions; i++)
+ {
+ ResultRelInfo *resultRelInfo = node->mt_partitions + i;
+
+ ExecCloseIndices(resultRelInfo);
+ heap_close(resultRelInfo->ri_RelationDesc, NoLock);
+
+ if (resultRelInfo->ri_FdwRoutine &&
+ resultRelInfo->ri_FdwRoutine->EndForeignModify != NULL)
+ resultRelInfo->ri_FdwRoutine->EndForeignModify(node->ps.state,
+ resultRelInfo);
+
+ if (node->mt_partition_tupconv_maps[i])
+ pfree(node->mt_partition_tupconv_maps[i]);
+ }
+
/*
* Free the exprcontext
*/
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index 58bfd49..9e5b60e 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -6149,6 +6149,65 @@ make_modifytable(PlannerInfo *root,
node->fdwPrivLists = fdw_private_list;
node->fdwDirectModifyPlans = direct_modify_plans;
+ /* Collect insert plans for all FDW-managed partitions */
+ if (node->operation == CMD_INSERT)
+ {
+ RangeTblEntry *rte,
+ **saved_simple_rte_array;
+ List *partition_oids;
+
+ Assert(list_length(resultRelations) == 1);
+ rte = rt_fetch(linitial_int(resultRelations), root->parse->rtable);
+ Assert(rte->rtekind == RTE_RELATION);
+
+ if (!relid_is_partitioned(rte->relid))
+ return node;
+
+ partition_oids = get_leaf_partitions(rte->relid, NoLock);
+
+ /* Discard any previous content which is useless anyway */
+ fdw_private_list = NIL;
+
+ /* To force FDW driver fetch the intended RTE */
+ saved_simple_rte_array = root->simple_rte_array;
+ root->simple_rte_array = (RangeTblEntry **)
+ palloc0(2 * sizeof(RangeTblEntry *));
+ foreach(lc, partition_oids)
+ {
+ Oid myoid = lfirst_oid(lc);
+ FdwRoutine *fdwroutine;
+ List *fdw_private;
+
+ if (!oid_is_foreign_table(myoid))
+ continue;
+
+ fdwroutine = GetFdwRoutineByRelId(myoid);
+ if (fdwroutine && fdwroutine->PlanForeignModify)
+ {
+ RangeTblEntry *fdw_rte;
+
+ fdw_rte = copyObject(rte);
+ fdw_rte->relid = myoid;
+ fdw_rte->relkind = RELKIND_FOREIGN_TABLE;
+
+ /* Assumes PlanForeignModify() uses planner_rt_fetch(). */
+ root->simple_rte_array[1] = fdw_rte;
+
+ fdw_private = fdwroutine->PlanForeignModify(root, node, 1, 0);
+ pfree(fdw_rte);
+ }
+ else
+ fdw_private = NIL;
+
+ fdw_private_list = lappend(fdw_private_list, fdw_private);
+ }
+
+ pfree(root->simple_rte_array);
+ root->simple_rte_array = saved_simple_rte_array;
+
+ node->fdwPrivLists = fdw_private_list;
+ }
+
return node;
}
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index 9314074..95aca08 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -1690,3 +1690,16 @@ has_row_triggers(PlannerInfo *root, Index rti, CmdType event)
heap_close(relation, NoLock);
return result;
}
+
+bool
+oid_is_foreign_table(Oid relid)
+{
+ Relation rel;
+ char relkind;
+
+ rel = heap_open(relid, NoLock);
+ relkind = rel->rd_rel->relkind;
+ heap_close(rel, NoLock);
+
+ return relkind == RELKIND_FOREIGN_TABLE;
+}
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index b4fca37..6996666 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -777,8 +777,16 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
/* Process ON CONFLICT, if any. */
if (stmt->onConflictClause)
+ {
+ /* Bail out if target relation is partitioned table */
+ if (relid_is_partitioned(pstate->p_target_rangetblentry->relid))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("ON CONFLICT clause is not supported with partitioned tables")));
+
qry->onConflict = transformOnConflictClause(pstate,
stmt->onConflictClause);
+ }
/*
* If we have a RETURNING clause, we need to add the target relation to
diff --git a/src/include/catalog/partition.h b/src/include/catalog/partition.h
index c1120e3..98e5dc0 100644
--- a/src/include/catalog/partition.h
+++ b/src/include/catalog/partition.h
@@ -14,6 +14,8 @@
#define PARTITION_H
#include "fmgr.h"
+#include "executor/tuptable.h"
+#include "nodes/execnodes.h"
#include "parser/parse_node.h"
#include "utils/relcache.h"
@@ -86,6 +88,22 @@ typedef struct PartitionDescData
typedef struct PartitionDescData *PartitionDesc;
/*
+ * PartitionKeyExecInfo
+ *
+ * This struct holds the information needed to extract partition
+ * column values from a heap tuple.
+ *
+ * Key copy of the rd_partkey of rel
+ * ExpressionState exec state for expressions, or NIL if none
+ */
+typedef struct PartitionKeyExecInfo
+{
+ NodeTag type;
+ PartitionKey pi_Key;
+ List *pi_ExpressionState; /* list of ExprState */
+} PartitionKeyExecInfo;
+
+/*
* Partition tree node
*
* pkinfo PartitionKey executor state
@@ -101,6 +119,7 @@ typedef struct PartitionDescData *PartitionDesc;
*/
typedef struct PartitionDescNodeData
{
+ PartitionKeyExecInfo *pkinfo;
PartitionDesc pdesc;
Oid relid;
int index;
@@ -128,6 +147,7 @@ extern void StorePartitionBound(Oid relid, Oid parentId, Node *bound);
extern void RemovePartitionEntryByRelId(Oid relid);
extern bool relid_is_partition(Oid relid);
extern Oid get_partition_parent(Oid relid);
+extern List *get_leaf_partitions(Oid relid, int lockmode);
extern void RelationDropPartitions(Oid relid);
extern PartitionInfo **get_partition_info(Relation rel, int *nparts);
extern void free_partition_info(PartitionInfo **p, int num);
@@ -139,4 +159,9 @@ extern List *get_check_qual_from_partbound(Relation rel, Relation parent,
Node *bound);
extern List *get_leaf_partition_oids(PartitionDescNode pdnode);
extern PartitionDescNode RelationGetPartitionDescNode(Relation rel);
+
+extern int get_partition_for_tuple(PartitionDescNode pdnode,
+ TupleTableSlot *slot,
+ EState *estate,
+ Oid *failed_at);
#endif /* PARTITION_H */
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index 39521ed..c3b0f6d 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -14,6 +14,7 @@
#ifndef EXECUTOR_H
#define EXECUTOR_H
+#include "catalog/partition.h"
#include "executor/execdesc.h"
#include "nodes/parsenodes.h"
@@ -188,6 +189,7 @@ extern void CheckValidResultRel(Relation resultRel, CmdType operation);
extern void InitResultRelInfo(ResultRelInfo *resultRelInfo,
Relation resultRelationDesc,
Index resultRelationIndex,
+ bool load_partition_check,
int instrument_options);
extern ResultRelInfo *ExecGetTriggerResultRel(EState *estate, Oid relid);
extern bool ExecContextForcesOids(PlanState *planstate, bool *hasoids);
@@ -211,6 +213,10 @@ extern void EvalPlanQualSetPlan(EPQState *epqstate,
extern void EvalPlanQualSetTuple(EPQState *epqstate, Index rti,
HeapTuple tuple);
extern HeapTuple EvalPlanQualGetTuple(EPQState *epqstate, Index rti);
+extern int ExecFindPartition(ResultRelInfo *resultRelInfo,
+ PartitionDescNode pdnode,
+ TupleTableSlot *slot,
+ EState *estate);
#define EvalPlanQualSetSlot(epqstate, slot) ((epqstate)->origslot = (slot))
extern void EvalPlanQualFetchRowMarks(EPQState *epqstate);
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 64af12c..e360616 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -16,6 +16,7 @@
#include "access/genam.h"
#include "access/heapam.h"
+#include "access/tupconvert.h"
#include "executor/instrument.h"
#include "lib/pairingheap.h"
#include "nodes/params.h"
@@ -1140,6 +1141,15 @@ typedef struct ModifyTableState
* tlist */
TupleTableSlot *mt_conflproj; /* CONFLICT ... SET ... projection
* target */
+ struct PartitionDescNodeData *mt_partition_node;
+ /* Partition descriptor node tree */
+ ResultRelInfo *mt_partitions; /* Per leaf partition target
+ * relations */
+ TupleConversionMap **mt_partition_tupconv_maps;
+ /* Per leaf partition
+ * tuple conversion map */
+ int mt_num_partitions; /* Number of leaf partition target
+ * relations in the above array */
} ModifyTableState;
/* ----------------
diff --git a/src/include/optimizer/plancat.h b/src/include/optimizer/plancat.h
index 125274e..fac606c 100644
--- a/src/include/optimizer/plancat.h
+++ b/src/include/optimizer/plancat.h
@@ -56,5 +56,6 @@ extern Selectivity join_selectivity(PlannerInfo *root,
SpecialJoinInfo *sjinfo);
extern bool has_row_triggers(PlannerInfo *root, Index rti, CmdType event);
+extern bool oid_is_foreign_table(Oid relid);
#endif /* PLANCAT_H */
--
1.7.1
^ permalink raw reply [nested|flat] 161+ messages in thread
* Re: Declarative partitioning
@ 2016-07-04 12:31 Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
0 siblings, 1 reply; 161+ messages in thread
From: Ashutosh Bapat @ 2016-07-04 12:31 UTC (permalink / raw)
To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; +Cc: Ildar Musin <i.musin@postgrespro.ru>; Tom Lane <tgl@sss.pgh.pa.us>; pgsql-hackers
Hi Amit,
I observed that the ChangeVarNodes call at line 1229 in
get_relation_constraints() (code below) changes the varno in the cached
copy of partition key expression, which is undesirable. The reason for this
seems to be that the RelationGetPartitionCheckQual() returns the copy of
partition key expression directly from the cache. This is mostly because
get_check_qual_for_range() directly works on cached copy of partition key
expressions, which it should never.
1223 /* Append partition check quals, if any */
1224 pcqual = RelationGetPartitionCheckQual(relation);
1225 if (pcqual)
1226 {
1227 /* Fix Vars to have the desired varno */
1228 if (varno != 1)
1229 ChangeVarNodes((Node *) pcqual, 1, varno, 0);
1230
1231 result = list_concat(result, pcqual);
1232 }
Because of this, the first time through the partition key expressions are
valid, but then onwards they are restamped with the varno of the first
partition.
Please add testcases to your patch to catch such types of issues.
On Mon, Jun 27, 2016 at 3:56 PM, Amit Langote <Langote_Amit_f8@lab.ntt.co.jp
> wrote:
>
> Hi Ashutosh,
>
> On 2016/06/24 23:08, Ashutosh Bapat wrote:
> > Hi Amit,
> > I tried creating 2-level partitioned table and tried to create simple
> table
> > using CTAS from the partitioned table. It gives a cache lookup error.
> > Here's the test
> > CREATE TABLE pt1_l (a int, b varchar, c int) PARTITION BY RANGE(a);
> > CREATE TABLE pt1_l_p1 PARTITION OF pt1_l FOR VALUES START (1) END (250)
> > INCLUSIVE PARTITION BY RANGE(b);
> > CREATE TABLE pt1_l_p2 PARTITION OF pt1_l FOR VALUES START (251) END (500)
> > INCLUSIVE PARTITION BY RANGE(((a+c)/2));
> > CREATE TABLE pt1_l_p3 PARTITION OF pt1_l FOR VALUES START (501) END (600)
> > INCLUSIVE PARTITION BY RANGE(c);
> > CREATE TABLE pt1_l_p1_p1 PARTITION OF pt1_l_p1 FOR VALUES START
> ('000001')
> > END ('000125') INCLUSIVE;
> > CREATE TABLE pt1_l_p1_p2 PARTITION OF pt1_l_p1 FOR VALUES START
> ('000126')
> > END ('000250') INCLUSIVE;
> > CREATE TABLE pt1_l_p2_p1 PARTITION OF pt1_l_p2 FOR VALUES START (251) END
> > (375) INCLUSIVE;
> > CREATE TABLE pt1_l_p2_p2 PARTITION OF pt1_l_p2 FOR VALUES START (376) END
> > (500) INCLUSIVE;
> > CREATE TABLE pt1_l_p3_p1 PARTITION OF pt1_l_p3 FOR VALUES START (501) END
> > (550) INCLUSIVE;
> > CREATE TABLE pt1_l_p3_p2 PARTITION OF pt1_l_p3 FOR VALUES START (551) END
> > (600) INCLUSIVE;
> > INSERT INTO pt1_l SELECT i, to_char(i, 'FM000000'), i FROM
> > generate_series(1, 600, 2) i;
> > CREATE TABLE upt1_l AS SELECT * FROM pt1_l;
> >
> > The last statement gives error "ERROR: cache lookup failed for function
> > 0". Let me know if this problem is reproducible.
>
> Thanks for the test case. I can reproduce the error. It has to do with
> partition key column (b) being of type varchar whereas the default
> operator family for the type being text_ops and there not being an
> =(varchar, varchar) operator in text_ops.
>
> When creating a plan for SELECT * FROM pt1_l, which is an Append plan,
> partition check quals are generated internally to be used for constraint
> exclusion - such as, b >= '000001' AND b < '000125'. Individual OpExpr's
> are generated by using information from PartitionKey of the parent
> (PartitionKey) and pg_partition entry of the partition. When choosing the
> operator to use for =, >=, <, etc., opfamily and typid of corresponding
> columns are referred. As mentioned above, in this case, they happened to
> be text_ops and varchar, respectively, for column b. There doesn't exist
> an operator =(varchar, varchar) in text_ops, so InvalidOid is returned by
> get_opfamily_member which goes unchecked until the error in question
> occurs.
>
> I have tried to fix this in attached updated patch by using operator class
> input type for operator resolution in cases where column type didn't help.
>
> Thanks,
> Amit
>
--
Best Wishes,
Ashutosh Bapat
EnterpriseDB Corporation
The Postgres Database Company
^ permalink raw reply [nested|flat] 161+ messages in thread
* Re: Declarative partitioning
@ 2016-07-05 04:48 Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
parent: Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
0 siblings, 1 reply; 161+ messages in thread
From: Amit Langote @ 2016-07-05 04:48 UTC (permalink / raw)
To: Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; +Cc: Ildar Musin <i.musin@postgrespro.ru>; Tom Lane <tgl@sss.pgh.pa.us>; pgsql-hackers
On 2016/07/04 21:31, Ashutosh Bapat wrote:
> Hi Amit,
> I observed that the ChangeVarNodes call at line 1229 in
> get_relation_constraints() (code below) changes the varno in the cached
> copy of partition key expression, which is undesirable. The reason for this
> seems to be that the RelationGetPartitionCheckQual() returns the copy of
> partition key expression directly from the cache. This is mostly because
> get_check_qual_for_range() directly works on cached copy of partition key
> expressions, which it should never.
Yes, a copyObject() on key->partexprs items seems necessary. Will fix that.
> 1223 /* Append partition check quals, if any */
> 1224 pcqual = RelationGetPartitionCheckQual(relation);
> 1225 if (pcqual)
> 1226 {
> 1227 /* Fix Vars to have the desired varno */
> 1228 if (varno != 1)
> 1229 ChangeVarNodes((Node *) pcqual, 1, varno, 0);
> 1230
> 1231 result = list_concat(result, pcqual);
> 1232 }
>
> Because of this, the first time through the partition key expressions are
> valid, but then onwards they are restamped with the varno of the first
> partition.
>
> Please add testcases to your patch to catch such types of issues.
I will integrate tests into the patch(es) and add some more.
Thanks,
Amit
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 161+ messages in thread
* Re: Declarative partitioning
@ 2016-07-19 13:53 Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
0 siblings, 1 reply; 161+ messages in thread
From: Ashutosh Bapat @ 2016-07-19 13:53 UTC (permalink / raw)
To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; +Cc: Ildar Musin <i.musin@postgrespro.ru>; Tom Lane <tgl@sss.pgh.pa.us>; pgsql-hackers
I am seeing following warning with this set of patches.
gram.y:4734:24: warning: assignment from incompatible pointer type [enabled
by default]
On Tue, Jul 5, 2016 at 10:18 AM, Amit Langote <Langote_Amit_f8@lab.ntt.co.jp
> wrote:
> On 2016/07/04 21:31, Ashutosh Bapat wrote:
> > Hi Amit,
> > I observed that the ChangeVarNodes call at line 1229 in
> > get_relation_constraints() (code below) changes the varno in the cached
> > copy of partition key expression, which is undesirable. The reason for
> this
> > seems to be that the RelationGetPartitionCheckQual() returns the copy of
> > partition key expression directly from the cache. This is mostly because
> > get_check_qual_for_range() directly works on cached copy of partition key
> > expressions, which it should never.
>
> Yes, a copyObject() on key->partexprs items seems necessary. Will fix that.
>
> > 1223 /* Append partition check quals, if any */
> > 1224 pcqual = RelationGetPartitionCheckQual(relation);
> > 1225 if (pcqual)
> > 1226 {
> > 1227 /* Fix Vars to have the desired varno */
> > 1228 if (varno != 1)
> > 1229 ChangeVarNodes((Node *) pcqual, 1, varno, 0);
> > 1230
> > 1231 result = list_concat(result, pcqual);
> > 1232 }
> >
> > Because of this, the first time through the partition key expressions are
> > valid, but then onwards they are restamped with the varno of the first
> > partition.
> >
> > Please add testcases to your patch to catch such types of issues.
>
> I will integrate tests into the patch(es) and add some more.
>
> Thanks,
> Amit
>
>
>
--
Best Wishes,
Ashutosh Bapat
EnterpriseDB Corporation
The Postgres Database Company
^ permalink raw reply [nested|flat] 161+ messages in thread
* Re: Declarative partitioning
@ 2016-07-21 04:40 Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
parent: Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
0 siblings, 1 reply; 161+ messages in thread
From: Amit Langote @ 2016-07-21 04:40 UTC (permalink / raw)
To: Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; +Cc: Ildar Musin <i.musin@postgrespro.ru>; Tom Lane <tgl@sss.pgh.pa.us>; pgsql-hackers
On 2016/07/19 22:53, Ashutosh Bapat wrote:
> I am seeing following warning with this set of patches.
> gram.y:4734:24: warning: assignment from incompatible pointer type [enabled
> by default]
Thanks, will fix. Was a copy-paste error.
Thanks,
Amit
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 161+ messages in thread
* Re: Declarative partitioning
@ 2016-08-05 10:53 Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
0 siblings, 1 reply; 161+ messages in thread
From: Ashutosh Bapat @ 2016-08-05 10:53 UTC (permalink / raw)
To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; +Cc: Ildar Musin <i.musin@postgrespro.ru>; Tom Lane <tgl@sss.pgh.pa.us>; pgsql-hackers
The lists for list partitioned tables are stored as they are specified by
the user. While searching for a partition to route tuple to, we compare it
with every list value of every partition. We might do something better
similar to what's been done to range partitions. The list of values for a
given partition can be stored in ascending/descending sorted order. Thus a
binary search can be used to check whether given row's partition key column
has same value as one in the list. The partitions can then be stored in the
ascending/descending order of the least/greatest values of corresponding
partitions. We might be able to eliminate search in a given partition if
its lowest value is higher than the given value or its higher value is
lower than the given value.
On Thu, Jul 21, 2016 at 10:10 AM, Amit Langote <
Langote_Amit_f8@lab.ntt.co.jp> wrote:
> On 2016/07/19 22:53, Ashutosh Bapat wrote:
> > I am seeing following warning with this set of patches.
> > gram.y:4734:24: warning: assignment from incompatible pointer type
> [enabled
> > by default]
>
>
> Thanks, will fix. Was a copy-paste error.
>
> Thanks,
> Amit
>
>
>
--
Best Wishes,
Ashutosh Bapat
EnterpriseDB Corporation
The Postgres Database Company
^ permalink raw reply [nested|flat] 161+ messages in thread
* Re: Declarative partitioning
@ 2016-08-05 11:51 Robert Haas <robertmhaas@gmail.com>
parent: Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
0 siblings, 1 reply; 161+ messages in thread
From: Robert Haas @ 2016-08-05 11:51 UTC (permalink / raw)
To: Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; +Cc: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; Ildar Musin <i.musin@postgrespro.ru>; Tom Lane <tgl@sss.pgh.pa.us>; pgsql-hackers
On Fri, Aug 5, 2016 at 6:53 AM, Ashutosh Bapat
<ashutosh.bapat@enterprisedb.com> wrote:
> The lists for list partitioned tables are stored as they are specified by
> the user. While searching for a partition to route tuple to, we compare it
> with every list value of every partition. We might do something better
> similar to what's been done to range partitions. The list of values for a
> given partition can be stored in ascending/descending sorted order. Thus a
> binary search can be used to check whether given row's partition key column
> has same value as one in the list. The partitions can then be stored in the
> ascending/descending order of the least/greatest values of corresponding
> partitions.
+1. Here as with range partitions, we must be sure to know which
opclass should be used for the comparisons.
> We might be able to eliminate search in a given partition if its
> lowest value is higher than the given value or its higher value is lower
> than the given value.
I don't think I understand this part.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 161+ messages in thread
* Re: Declarative partitioning
@ 2016-08-05 12:10 Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
parent: Robert Haas <robertmhaas@gmail.com>
0 siblings, 1 reply; 161+ messages in thread
From: Ashutosh Bapat @ 2016-08-05 12:10 UTC (permalink / raw)
To: Robert Haas <robertmhaas@gmail.com>; +Cc: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; Ildar Musin <i.musin@postgrespro.ru>; Tom Lane <tgl@sss.pgh.pa.us>; pgsql-hackers
On Fri, Aug 5, 2016 at 5:21 PM, Robert Haas <robertmhaas@gmail.com> wrote:
> On Fri, Aug 5, 2016 at 6:53 AM, Ashutosh Bapat
> <ashutosh.bapat@enterprisedb.com> wrote:
> > The lists for list partitioned tables are stored as they are specified by
> > the user. While searching for a partition to route tuple to, we compare
> it
> > with every list value of every partition. We might do something better
> > similar to what's been done to range partitions. The list of values for a
> > given partition can be stored in ascending/descending sorted order. Thus
> a
> > binary search can be used to check whether given row's partition key
> column
> > has same value as one in the list. The partitions can then be stored in
> the
> > ascending/descending order of the least/greatest values of corresponding
> > partitions.
>
> +1. Here as with range partitions, we must be sure to know which
> opclass should be used for the comparisons.
>
> > We might be able to eliminate search in a given partition if its
> > lowest value is higher than the given value or its higher value is lower
> > than the given value.
>
> I don't think I understand this part.
>
Consider lists ('e', 'i', 'f'), ('h', 'd','m') and ('l', 'b', 'a') for a
list partitioned tables. I am suggesting that we arrange them as
('a','b','l'), ('d', 'h', 'm') and ('e', 'f', 'i'). If the given row
(either for comparison or for inserting) has value 'c', we will search for
it in ('a','b','l') but will be eliminate other two partitions since the
second's partition's lowest value is higher than 'c' and lowest values of
rest of the partitions are higher than that of the second partition.
Without this order among the partitions, we will have to compare lowest
values of all the partitions.
--
Best Wishes,
Ashutosh Bapat
EnterpriseDB Corporation
The Postgres Database Company
^ permalink raw reply [nested|flat] 161+ messages in thread
* Re: Declarative partitioning
@ 2016-08-05 12:15 Robert Haas <robertmhaas@gmail.com>
parent: Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
0 siblings, 1 reply; 161+ messages in thread
From: Robert Haas @ 2016-08-05 12:15 UTC (permalink / raw)
To: Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; +Cc: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; Ildar Musin <i.musin@postgrespro.ru>; Tom Lane <tgl@sss.pgh.pa.us>; pgsql-hackers
On Fri, Aug 5, 2016 at 8:10 AM, Ashutosh Bapat
<ashutosh.bapat@enterprisedb.com> wrote:
> On Fri, Aug 5, 2016 at 5:21 PM, Robert Haas <robertmhaas@gmail.com> wrote:
>> On Fri, Aug 5, 2016 at 6:53 AM, Ashutosh Bapat
>> <ashutosh.bapat@enterprisedb.com> wrote:
>> > The lists for list partitioned tables are stored as they are specified
>> > by
>> > the user. While searching for a partition to route tuple to, we compare
>> > it
>> > with every list value of every partition. We might do something better
>> > similar to what's been done to range partitions. The list of values for
>> > a
>> > given partition can be stored in ascending/descending sorted order. Thus
>> > a
>> > binary search can be used to check whether given row's partition key
>> > column
>> > has same value as one in the list. The partitions can then be stored in
>> > the
>> > ascending/descending order of the least/greatest values of corresponding
>> > partitions.
>>
>> +1. Here as with range partitions, we must be sure to know which
>> opclass should be used for the comparisons.
>>
>> > We might be able to eliminate search in a given partition if its
>> > lowest value is higher than the given value or its higher value is lower
>> > than the given value.
>>
>> I don't think I understand this part.
>
> Consider lists ('e', 'i', 'f'), ('h', 'd','m') and ('l', 'b', 'a') for a
> list partitioned tables. I am suggesting that we arrange them as
> ('a','b','l'), ('d', 'h', 'm') and ('e', 'f', 'i'). If the given row (either
> for comparison or for inserting) has value 'c', we will search for it in
> ('a','b','l') but will be eliminate other two partitions since the second's
> partition's lowest value is higher than 'c' and lowest values of rest of the
> partitions are higher than that of the second partition. Without this order
> among the partitions, we will have to compare lowest values of all the
> partitions.
I would think that for that case what we'd want to do is create two
lists. One looks like this:
[ 'a', 'b', 'd', 'e', f', 'h', 'i', 'l', 'm' ]
The other looks like this:
[3, 3, 2, 1, 1, 2, 1, 1, 3, 2]
Given a particular value, bsearch the first list. If the value is not
found, it's not in any partition. If it is found, then go look up the
same array index in the second list; that's the containing partition.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 161+ messages in thread
* Re: Declarative partitioning
@ 2016-08-05 12:38 Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
parent: Robert Haas <robertmhaas@gmail.com>
0 siblings, 1 reply; 161+ messages in thread
From: Ashutosh Bapat @ 2016-08-05 12:38 UTC (permalink / raw)
To: Robert Haas <robertmhaas@gmail.com>; +Cc: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; Ildar Musin <i.musin@postgrespro.ru>; Tom Lane <tgl@sss.pgh.pa.us>; pgsql-hackers
>
> >
> > Consider lists ('e', 'i', 'f'), ('h', 'd','m') and ('l', 'b', 'a') for a
> > list partitioned tables. I am suggesting that we arrange them as
> > ('a','b','l'), ('d', 'h', 'm') and ('e', 'f', 'i'). If the given row
> (either
> > for comparison or for inserting) has value 'c', we will search for it in
> > ('a','b','l') but will be eliminate other two partitions since the
> second's
> > partition's lowest value is higher than 'c' and lowest values of rest of
> the
> > partitions are higher than that of the second partition. Without this
> order
> > among the partitions, we will have to compare lowest values of all the
> > partitions.
>
> I would think that for that case what we'd want to do is create two
> lists. One looks like this:
>
> [ 'a', 'b', 'd', 'e', f', 'h', 'i', 'l', 'm' ]
>
> The other looks like this:
>
> [3, 3, 2, 1, 1, 2, 1, 1, 3, 2]
>
small correction; there's an extra 1 here. Every partition in the example
has only three values.
>
> Given a particular value, bsearch the first list. If the value is not
> found, it's not in any partition. If it is found, then go look up the
> same array index in the second list; that's the containing partition.
>
+1, if we could do it. It will need a change in the way Amit's patch stores
partitioning scheme in PartitionDesc.
This way specifications {('e', 'i', 'f'), ('h', 'd','m') and ('l', 'b',
'a')} and {('h', 'd','m') , ('e', 'i', 'f'), and ('l', 'b', 'a')} which are
essentially same, are represented in different ways. It makes matching
partitions for partition-wise join a bit tedius. We have to make sure that
the first array matches for both the joining relations and then make sure
that all the values belonging to the same partition for one table also
belong to the same partition in the other table. Some more complex logic
for matching subsets of lists for partition-wise join.
At least for straight forward partitioned table matching it helps to have
both these array look same independent of the user specification. From that
point of view, the partition be ordered by their lowest or highest list
values and the second array is the index in the ordered set. For both the
specifications above, the list will look like
[ 'a', 'b', 'd', 'e', f', 'h', 'i', 'l', 'm' ]
[1, 1, 2, 3, 3, 2, 3, 1, 2]
--
Best Wishes,
Ashutosh Bapat
EnterpriseDB Corporation
The Postgres Database Company
^ permalink raw reply [nested|flat] 161+ messages in thread
* Re: Declarative partitioning
@ 2016-08-08 05:40 Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
parent: Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
0 siblings, 2 replies; 161+ messages in thread
From: Amit Langote @ 2016-08-08 05:40 UTC (permalink / raw)
To: Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; Robert Haas <robertmhaas@gmail.com>; +Cc: Ildar Musin <i.musin@postgrespro.ru>; Tom Lane <tgl@sss.pgh.pa.us>; pgsql-hackers
On 2016/08/05 21:38, Ashutosh Bapat wrote:
>>> Consider lists ('e', 'i', 'f'), ('h', 'd','m') and ('l', 'b', 'a') for a
>>> list partitioned tables. I am suggesting that we arrange them as
>>> ('a','b','l'), ('d', 'h', 'm') and ('e', 'f', 'i'). If the given row
>> (either
>>> for comparison or for inserting) has value 'c', we will search for it in
>>> ('a','b','l') but will be eliminate other two partitions since the
>> second's
>>> partition's lowest value is higher than 'c' and lowest values of rest of
>> the
>>> partitions are higher than that of the second partition. Without this
>> order
>>> among the partitions, we will have to compare lowest values of all the
>>> partitions.
>>
>> I would think that for that case what we'd want to do is create two
>> lists. One looks like this:
>>
>> [ 'a', 'b', 'd', 'e', f', 'h', 'i', 'l', 'm' ]
>>
>> The other looks like this:
>>
>> [3, 3, 2, 1, 1, 2, 1, 1, 3, 2]
>>
>
> small correction; there's an extra 1 here. Every partition in the example
> has only three values.
>
>
>>
>> Given a particular value, bsearch the first list. If the value is not
>> found, it's not in any partition. If it is found, then go look up the
>> same array index in the second list; that's the containing partition.
>>
>
> +1, if we could do it. It will need a change in the way Amit's patch stores
> partitioning scheme in PartitionDesc.
Okay, I will try to implement this in the next version of the patch.
One thing that comes to mind is what if a user wants to apply hash
operator class equality to list partitioned key by specifying a hash
operator class for the corresponding column. In that case, we would not
have the ordering procedure with an hash operator class, hence any
ordering based optimization becomes impossible to implement. The current
patch rejects a column for partition key if its type does not have a btree
operator class for both range and list methods, so this issue doesn't
exist, however it could be seen as a limitation.
> This way specifications {('e', 'i', 'f'), ('h', 'd','m') and ('l', 'b',
> 'a')} and {('h', 'd','m') , ('e', 'i', 'f'), and ('l', 'b', 'a')} which are
> essentially same, are represented in different ways. It makes matching
> partitions for partition-wise join a bit tedius. We have to make sure that
> the first array matches for both the joining relations and then make sure
> that all the values belonging to the same partition for one table also
> belong to the same partition in the other table. Some more complex logic
> for matching subsets of lists for partition-wise join.
So, we have 3 choices for the internal representation of list partitions:
Choice 1 (the current approach): Load them in the same order as they are
found in the partition catalog:
Table 1: p1 {'b', 'f'}, p2 {'c', 'd'}, p3 {'a', 'e'}
Table 2: p1 {'c', 'd'}, p2 {'a', 'e'}, p3 {'b', 'f'}
In this case, mismatch on the first list would make the two tables
incompatibly partitioned, whereas they really aren't incompatible.
Choice 2: Representation with 2 arrays:
Table 1: ['a', 'b', 'c', 'd', 'e', 'f'], [3, 1, 2, 2, 3, 1]
Table 2: ['a', 'b', 'c', 'd', 'e', 'f'], [2, 3, 1, 1, 2, 3]
It still doesn't help the case of pairwise joins because it's hard to tell
which value belongs to which partition (the 2nd array carries the original
partition numbers). Although it might still work for tuple-routing.
Choice 3: Order all lists' elements for each list individually and then
order the lists themselves on their first values:
Table 1: p3 {'a', 'e'}, p2 {'b', 'f'}, p1 {'c', 'd'}
Table 2: p2 {'a', 'e'}, p1 {'b', 'f'}, p3 {'c', 'd'}
This representation makes pairing partitions for pairwise joining
convenient but for tuple-routing we still need to visit each partition in
the worst case.
> At least for straight forward partitioned table matching it helps to have
> both these array look same independent of the user specification. From that
> point of view, the partition be ordered by their lowest or highest list
> values and the second array is the index in the ordered set. For both the
> specifications above, the list will look like
>
> [ 'a', 'b', 'd', 'e', f', 'h', 'i', 'l', 'm' ]
> [1, 1, 2, 3, 3, 2, 3, 1, 2]
IIUC, this seems like a combination of 2 and 3 above:
So, we have the following list partitions (as read from the catalog)
Table 1: p1 {'b', 'f'}, p2 {'c', 'd'}, p3 {'a', 'e'}
Table 2: p1 {'c', 'd'}, p2 {'a', 'e'}, p3 {'b', 'f'}
By applying the method of 3:
Table 1: p3 {'a', 'e'}, p2 {'b', 'f'}, p1 {'c', 'd'}
Table 2: p2 {'a', 'e'}, p1 {'b', 'f'}, p3 {'c', 'd'}
Then applying 2:
Table 1: ['a', 'b', 'c', 'd', 'e', 'f'], [1, 2, 3, 3, 1, 2], [3, 1, 2]
Table 2: ['a', 'b', 'c', 'd', 'e', 'f'], [1, 2, 3, 3, 1, 2], [2, 3, 1]
This is user-specification independent representation wherein the
partition numbers in the 2nd array are based on canonical representation
(ordered lists). To check pairwise join compatibility, simply compare the
first two arrays. As to which partitions (think OIDs, RTEs whatever) pair
with each other, simply pair corresponding elements of the 3rd array which
are original partitions numbers (or OIDs). Also when routing a tuple, we
find partition number in the array 2 and then look up the array 3 to get
the actual partition to insert the tuple.
Neither of these representations make the logic of checking pairwise-join
compatibility and pairing a subset of partitions (on either side) any
easier, although I haven't given it a good thought yet.
Thanks,
Amit
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 161+ messages in thread
* Re: Declarative partitioning
@ 2016-08-08 06:40 Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
1 sibling, 0 replies; 161+ messages in thread
From: Ashutosh Bapat @ 2016-08-08 06:40 UTC (permalink / raw)
To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; +Cc: Robert Haas <robertmhaas@gmail.com>; Ildar Musin <i.musin@postgrespro.ru>; Tom Lane <tgl@sss.pgh.pa.us>; pgsql-hackers
> IIUC, this seems like a combination of 2 and 3 above:
>
> So, we have the following list partitions (as read from the catalog)
>
> Table 1: p1 {'b', 'f'}, p2 {'c', 'd'}, p3 {'a', 'e'}
> Table 2: p1 {'c', 'd'}, p2 {'a', 'e'}, p3 {'b', 'f'}
>
> By applying the method of 3:
>
> Table 1: p3 {'a', 'e'}, p2 {'b', 'f'}, p1 {'c', 'd'}
> Table 2: p2 {'a', 'e'}, p1 {'b', 'f'}, p3 {'c', 'd'}
>
> Then applying 2:
>
> Table 1: ['a', 'b', 'c', 'd', 'e', 'f'], [1, 2, 3, 3, 1, 2], [3, 1, 2]
> Table 2: ['a', 'b', 'c', 'd', 'e', 'f'], [1, 2, 3, 3, 1, 2], [2, 3, 1]
>
We should arrange the OID arrays to follow canonical order. Assuming that
OIDs of p1, pt2, and p3 of table 1 are t1o1, t1o2 and t1o3 respectively,
and those of p1, p2 and p3 of table 2 are t2o1, t2o2, t2o3 resp. the last
arrays should be [t1o3, t1o2, t1o1] and [t2o2, t2o1, t2o3].Thus the last
arrays from both representation give the OIDs of children that should be
joined pair-wise. IOW, OID array should just follow the canonical order
instead of specification order. AFAIU, your patch arranges the range
partition OIDs in the canonical order and not specification order.
>
> This is user-specification independent representation wherein the
> partition numbers in the 2nd array are based on canonical representation
> (ordered lists). To check pairwise join compatibility, simply compare the
> first two arrays. As to which partitions (think OIDs, RTEs whatever) pair
> with each other, simply pair corresponding elements of the 3rd array which
> are original partitions numbers (or OIDs). Also when routing a tuple, we
> find partition number in the array 2 and then look up the array 3 to get
> the actual partition to insert the tuple.
>
> Neither of these representations make the logic of checking pairwise-join
> compatibility and pairing a subset of partitions (on either side) any
> easier, although I haven't given it a good thought yet.
>
> With the small change suggested above, it should be easy to check
partition-wise join compatibilty for simplest case. I agree that for
generic case it will be difficult. E.g. Table 1#: p3 {'a', 'e'}, p4{'a#',
'l'}, p2 {'b', 'f'}, p1 {'c', 'd'} being (INNER) joined with Table 2: p2
{'a', 'e'}, p1 {'b', 'f'}, p3 {'c', 'd'} assuming 'a'< 'a#' < 'b'; in this
case, we should be able to match p3-p2, p2-p1, p1-p3 for partition-wise
join, even though canonical representations of both partitions differ,
because of an extra partition in between.
--
Best Wishes,
Ashutosh Bapat
EnterpriseDB Corporation
The Postgres Database Company
^ permalink raw reply [nested|flat] 161+ messages in thread
* Re: Declarative partitioning
@ 2016-08-08 21:02 Robert Haas <robertmhaas@gmail.com>
parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
1 sibling, 1 reply; 161+ messages in thread
From: Robert Haas @ 2016-08-08 21:02 UTC (permalink / raw)
To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; +Cc: Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; Ildar Musin <i.musin@postgrespro.ru>; Tom Lane <tgl@sss.pgh.pa.us>; pgsql-hackers
On Mon, Aug 8, 2016 at 1:40 AM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:
>> +1, if we could do it. It will need a change in the way Amit's patch stores
>> partitioning scheme in PartitionDesc.
>
> Okay, I will try to implement this in the next version of the patch.
>
> One thing that comes to mind is what if a user wants to apply hash
> operator class equality to list partitioned key by specifying a hash
> operator class for the corresponding column. In that case, we would not
> have the ordering procedure with an hash operator class, hence any
> ordering based optimization becomes impossible to implement. The current
> patch rejects a column for partition key if its type does not have a btree
> operator class for both range and list methods, so this issue doesn't
> exist, however it could be seen as a limitation.
Yes, I think you should expect careful scrutiny of that issue. It
seems clear to me that range partitioning requires a btree opclass,
that hash partitioning requires a hash opclass, and that list
partitioning requires at least one of those things. It would probably
be reasonable to pick one or the other and insist that list
partitioning always requires exactly that, but I can't see how it's
reasonable to insist that you must have both types of opclass for any
type of partitioning.
>> This way specifications {('e', 'i', 'f'), ('h', 'd','m') and ('l', 'b',
>> 'a')} and {('h', 'd','m') , ('e', 'i', 'f'), and ('l', 'b', 'a')} which are
>> essentially same, are represented in different ways. It makes matching
>> partitions for partition-wise join a bit tedius. We have to make sure that
>> the first array matches for both the joining relations and then make sure
>> that all the values belonging to the same partition for one table also
>> belong to the same partition in the other table. Some more complex logic
>> for matching subsets of lists for partition-wise join.
>
> So, we have 3 choices for the internal representation of list partitions:
>
> Choice 1 (the current approach): Load them in the same order as they are
> found in the partition catalog:
>
> Table 1: p1 {'b', 'f'}, p2 {'c', 'd'}, p3 {'a', 'e'}
> Table 2: p1 {'c', 'd'}, p2 {'a', 'e'}, p3 {'b', 'f'}
>
> In this case, mismatch on the first list would make the two tables
> incompatibly partitioned, whereas they really aren't incompatible.
Such a limitation seems clearly unacceptable. We absolutely must be
able to match up compatible partitioning schemes without getting
confused by little details like the order of the partitions.
> Choice 2: Representation with 2 arrays:
>
> Table 1: ['a', 'b', 'c', 'd', 'e', 'f'], [3, 1, 2, 2, 3, 1]
> Table 2: ['a', 'b', 'c', 'd', 'e', 'f'], [2, 3, 1, 1, 2, 3]
>
> It still doesn't help the case of pairwise joins because it's hard to tell
> which value belongs to which partition (the 2nd array carries the original
> partition numbers). Although it might still work for tuple-routing.
It's very good for tuple routing. It can also be used to match up
partitions for pairwise joins. Compare the first arrays. If they are
unequal, stop. Else, compare the second arrays, incrementally
building a mapping between them and returning false if the mapping
turns out to be non-bijective. For example, in this case, we look at
index 0 and decide that 3 -> 2. We look at index 1 and decide 1 -> 3.
We look at index 2 and decide 2 -> 1. We look at index 4 and find
that we already have a mapping for 2, but it's compatible because we
need 2 -> 1 and that's what is already there. Similarly for the
remaining entries. This is really a pretty easy loop to write and it
should run very quickly.
> Choice 3: Order all lists' elements for each list individually and then
> order the lists themselves on their first values:
>
> Table 1: p3 {'a', 'e'}, p2 {'b', 'f'}, p1 {'c', 'd'}
> Table 2: p2 {'a', 'e'}, p1 {'b', 'f'}, p3 {'c', 'd'}
>
> This representation makes pairing partitions for pairwise joining
> convenient but for tuple-routing we still need to visit each partition in
> the worst case.
I think this is clearly not good enough for tuple routing. If the
algorithm I proposed above turns out to be too slow for matching
partitions, then we could keep both this representation and the
previous one. We are not limited to just one. But I don't think
that's likely to be the case.
Also, note that all of this presupposes we're doing range
partitioning, or perhaps list partitioning with a btree opclass. For
partitioning based on a hash opclass, you'd organize the data based on
the hash values rather than range comparisons.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 161+ messages in thread
* Re: Declarative partitioning
@ 2016-08-09 03:47 Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
parent: Robert Haas <robertmhaas@gmail.com>
0 siblings, 1 reply; 161+ messages in thread
From: Amit Langote @ 2016-08-09 03:47 UTC (permalink / raw)
To: Robert Haas <robertmhaas@gmail.com>; +Cc: Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; Ildar Musin <i.musin@postgrespro.ru>; Tom Lane <tgl@sss.pgh.pa.us>; pgsql-hackers
On 2016/08/09 6:02, Robert Haas wrote:
> On Mon, Aug 8, 2016 at 1:40 AM, Amit Langote
> <Langote_Amit_f8@lab.ntt.co.jp> wrote:
>>> +1, if we could do it. It will need a change in the way Amit's patch stores
>>> partitioning scheme in PartitionDesc.
>>
>> Okay, I will try to implement this in the next version of the patch.
>>
>> One thing that comes to mind is what if a user wants to apply hash
>> operator class equality to list partitioned key by specifying a hash
>> operator class for the corresponding column. In that case, we would not
>> have the ordering procedure with an hash operator class, hence any
>> ordering based optimization becomes impossible to implement. The current
>> patch rejects a column for partition key if its type does not have a btree
>> operator class for both range and list methods, so this issue doesn't
>> exist, however it could be seen as a limitation.
>
> Yes, I think you should expect careful scrutiny of that issue. It
> seems clear to me that range partitioning requires a btree opclass,
> that hash partitioning requires a hash opclass, and that list
> partitioning requires at least one of those things. It would probably
> be reasonable to pick one or the other and insist that list
> partitioning always requires exactly that, but I can't see how it's
> reasonable to insist that you must have both types of opclass for any
> type of partitioning.
So because we intend to implement optimizations for list partition
metadata that presuppose existence of corresponding btree operator class,
we should just always require user to specify one (or error out if user
doesn't specify and a default one doesn't exist). That way, we explicitly
do not support specify hash equality operator for list partitioning.
>> So, we have 3 choices for the internal representation of list partitions:
>>
>> Choice 1 (the current approach): Load them in the same order as they are
>> found in the partition catalog:
>>
>> Table 1: p1 {'b', 'f'}, p2 {'c', 'd'}, p3 {'a', 'e'}
>> Table 2: p1 {'c', 'd'}, p2 {'a', 'e'}, p3 {'b', 'f'}
>>
>> In this case, mismatch on the first list would make the two tables
>> incompatibly partitioned, whereas they really aren't incompatible.
>
> Such a limitation seems clearly unacceptable. We absolutely must be
> able to match up compatible partitioning schemes without getting
> confused by little details like the order of the partitions.
Agreed. Will change my patch to adopt the below method.
>> Choice 2: Representation with 2 arrays:
>>
>> Table 1: ['a', 'b', 'c', 'd', 'e', 'f'], [3, 1, 2, 2, 3, 1]
>> Table 2: ['a', 'b', 'c', 'd', 'e', 'f'], [2, 3, 1, 1, 2, 3]
>>
>> It still doesn't help the case of pairwise joins because it's hard to tell
>> which value belongs to which partition (the 2nd array carries the original
>> partition numbers). Although it might still work for tuple-routing.
>
> It's very good for tuple routing. It can also be used to match up
> partitions for pairwise joins. Compare the first arrays. If they are
> unequal, stop. Else, compare the second arrays, incrementally
> building a mapping between them and returning false if the mapping
> turns out to be non-bijective. For example, in this case, we look at
> index 0 and decide that 3 -> 2. We look at index 1 and decide 1 -> 3.
> We look at index 2 and decide 2 -> 1. We look at index 4 and find
> that we already have a mapping for 2, but it's compatible because we
> need 2 -> 1 and that's what is already there. Similarly for the
> remaining entries. This is really a pretty easy loop to write and it
> should run very quickly.
I see, it does make sense to try to implement this way.
>> Choice 3: Order all lists' elements for each list individually and then
>> order the lists themselves on their first values:
>>
>> Table 1: p3 {'a', 'e'}, p2 {'b', 'f'}, p1 {'c', 'd'}
>> Table 2: p2 {'a', 'e'}, p1 {'b', 'f'}, p3 {'c', 'd'}
>>
>> This representation makes pairing partitions for pairwise joining
>> convenient but for tuple-routing we still need to visit each partition in
>> the worst case.
>
> I think this is clearly not good enough for tuple routing. If the
> algorithm I proposed above turns out to be too slow for matching
> partitions, then we could keep both this representation and the
> previous one. We are not limited to just one. But I don't think
> that's likely to be the case.
I agree. Let's see how the option 2 turns out.
> Also, note that all of this presupposes we're doing range
> partitioning, or perhaps list partitioning with a btree opclass. For
> partitioning based on a hash opclass, you'd organize the data based on
> the hash values rather than range comparisons.
Yes, the current patch does not implement hash partitioning, although I
have to think about how to support the hash case when designing the
internal data structures.
By the way, I am planning to start a new thread with the latest set of
patches which I will post in a day or two. I have tried to implement all
the bug fixes and improvements that have been suggested on this thread so
far. Thanks to all those who reviewed and gave their comments. Please
check this page to get a link to the new thread:
https://commitfest.postgresql.org/10/611/
Thanks,
Amit
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 161+ messages in thread
* Re: Declarative partitioning
@ 2016-08-09 07:18 Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
parent: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
0 siblings, 1 reply; 161+ messages in thread
From: Ashutosh Bapat @ 2016-08-09 07:18 UTC (permalink / raw)
To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; +Cc: Robert Haas <robertmhaas@gmail.com>; Ildar Musin <i.musin@postgrespro.ru>; Tom Lane <tgl@sss.pgh.pa.us>; pgsql-hackers
What strikes me odd about these patches is RelOptInfo has remained
unmodified. For a base partitioned table, I would expect it to be marked as
partitioned may be indicating the partitioning scheme. Instead of that, I
see that the code directly deals with PartitionDesc, PartitionKey which are
part of Relation. It uses other structures like PartitionKeyExecInfo. I
don't have any specific observation as to why we need such information in
RelOptInfo, but lack of it makes me uncomfortable. It could be because
inheritance code does not require any mark in RelOptInfo and your patch
re-uses inheritance code. I am not sure.
On Tue, Aug 9, 2016 at 9:17 AM, Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
wrote:
> On 2016/08/09 6:02, Robert Haas wrote:
> > On Mon, Aug 8, 2016 at 1:40 AM, Amit Langote
> > <Langote_Amit_f8@lab.ntt.co.jp> wrote:
> >>> +1, if we could do it. It will need a change in the way Amit's patch
> stores
> >>> partitioning scheme in PartitionDesc.
> >>
> >> Okay, I will try to implement this in the next version of the patch.
> >>
> >> One thing that comes to mind is what if a user wants to apply hash
> >> operator class equality to list partitioned key by specifying a hash
> >> operator class for the corresponding column. In that case, we would not
> >> have the ordering procedure with an hash operator class, hence any
> >> ordering based optimization becomes impossible to implement. The
> current
> >> patch rejects a column for partition key if its type does not have a
> btree
> >> operator class for both range and list methods, so this issue doesn't
> >> exist, however it could be seen as a limitation.
> >
> > Yes, I think you should expect careful scrutiny of that issue. It
> > seems clear to me that range partitioning requires a btree opclass,
> > that hash partitioning requires a hash opclass, and that list
> > partitioning requires at least one of those things. It would probably
> > be reasonable to pick one or the other and insist that list
> > partitioning always requires exactly that, but I can't see how it's
> > reasonable to insist that you must have both types of opclass for any
> > type of partitioning.
>
> So because we intend to implement optimizations for list partition
> metadata that presuppose existence of corresponding btree operator class,
> we should just always require user to specify one (or error out if user
> doesn't specify and a default one doesn't exist). That way, we explicitly
> do not support specify hash equality operator for list partitioning.
>
> >> So, we have 3 choices for the internal representation of list
> partitions:
> >>
> >> Choice 1 (the current approach): Load them in the same order as they
> are
> >> found in the partition catalog:
> >>
> >> Table 1: p1 {'b', 'f'}, p2 {'c', 'd'}, p3 {'a', 'e'}
> >> Table 2: p1 {'c', 'd'}, p2 {'a', 'e'}, p3 {'b', 'f'}
> >>
> >> In this case, mismatch on the first list would make the two tables
> >> incompatibly partitioned, whereas they really aren't incompatible.
> >
> > Such a limitation seems clearly unacceptable. We absolutely must be
> > able to match up compatible partitioning schemes without getting
> > confused by little details like the order of the partitions.
>
> Agreed. Will change my patch to adopt the below method.
>
> >> Choice 2: Representation with 2 arrays:
> >>
> >> Table 1: ['a', 'b', 'c', 'd', 'e', 'f'], [3, 1, 2, 2, 3, 1]
> >> Table 2: ['a', 'b', 'c', 'd', 'e', 'f'], [2, 3, 1, 1, 2, 3]
> >>
> >> It still doesn't help the case of pairwise joins because it's hard to
> tell
> >> which value belongs to which partition (the 2nd array carries the
> original
> >> partition numbers). Although it might still work for tuple-routing.
> >
> > It's very good for tuple routing. It can also be used to match up
> > partitions for pairwise joins. Compare the first arrays. If they are
> > unequal, stop. Else, compare the second arrays, incrementally
> > building a mapping between them and returning false if the mapping
> > turns out to be non-bijective. For example, in this case, we look at
> > index 0 and decide that 3 -> 2. We look at index 1 and decide 1 -> 3.
> > We look at index 2 and decide 2 -> 1. We look at index 4 and find
> > that we already have a mapping for 2, but it's compatible because we
> > need 2 -> 1 and that's what is already there. Similarly for the
> > remaining entries. This is really a pretty easy loop to write and it
> > should run very quickly.
>
> I see, it does make sense to try to implement this way.
>
> >> Choice 3: Order all lists' elements for each list individually and then
> >> order the lists themselves on their first values:
> >>
> >> Table 1: p3 {'a', 'e'}, p2 {'b', 'f'}, p1 {'c', 'd'}
> >> Table 2: p2 {'a', 'e'}, p1 {'b', 'f'}, p3 {'c', 'd'}
> >>
> >> This representation makes pairing partitions for pairwise joining
> >> convenient but for tuple-routing we still need to visit each partition
> in
> >> the worst case.
> >
> > I think this is clearly not good enough for tuple routing. If the
> > algorithm I proposed above turns out to be too slow for matching
> > partitions, then we could keep both this representation and the
> > previous one. We are not limited to just one. But I don't think
> > that's likely to be the case.
>
> I agree. Let's see how the option 2 turns out.
>
> > Also, note that all of this presupposes we're doing range
> > partitioning, or perhaps list partitioning with a btree opclass. For
> > partitioning based on a hash opclass, you'd organize the data based on
> > the hash values rather than range comparisons.
>
> Yes, the current patch does not implement hash partitioning, although I
> have to think about how to support the hash case when designing the
> internal data structures.
>
>
> By the way, I am planning to start a new thread with the latest set of
> patches which I will post in a day or two. I have tried to implement all
> the bug fixes and improvements that have been suggested on this thread so
> far. Thanks to all those who reviewed and gave their comments. Please
> check this page to get a link to the new thread:
> https://commitfest.postgresql.org/10/611/
>
> Thanks,
> Amit
>
>
>
--
Best Wishes,
Ashutosh Bapat
EnterpriseDB Corporation
The Postgres Database Company
^ permalink raw reply [nested|flat] 161+ messages in thread
* Re: Declarative partitioning
@ 2016-08-10 10:18 Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
parent: Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
0 siblings, 1 reply; 161+ messages in thread
From: Ashutosh Bapat @ 2016-08-10 10:18 UTC (permalink / raw)
To: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>; +Cc: Robert Haas <robertmhaas@gmail.com>; Ildar Musin <i.musin@postgrespro.ru>; Tom Lane <tgl@sss.pgh.pa.us>; pgsql-hackers
FOR VALUE clause of a partition does not allow a constant expression like
(10000/5 -1). It gives syntax error
regression=# create table pt1_p1 partition of pt1 for values start (0) end
((10000/5) - 1);
ERROR: syntax error at or near "("
LINE 1: ...pt1_p1 partition of pt1 for values start (0) end ((10000/5) ...
Shouldn't we allow constant expressions here?
If this has been already discussed, please forgive me and point out the
relevant mail chain.
On Tue, Aug 9, 2016 at 12:48 PM, Ashutosh Bapat <
ashutosh.bapat@enterprisedb.com> wrote:
> What strikes me odd about these patches is RelOptInfo has remained
> unmodified. For a base partitioned table, I would expect it to be marked as
> partitioned may be indicating the partitioning scheme. Instead of that, I
> see that the code directly deals with PartitionDesc, PartitionKey which are
> part of Relation. It uses other structures like PartitionKeyExecInfo. I
> don't have any specific observation as to why we need such information in
> RelOptInfo, but lack of it makes me uncomfortable. It could be because
> inheritance code does not require any mark in RelOptInfo and your patch
> re-uses inheritance code. I am not sure.
>
> On Tue, Aug 9, 2016 at 9:17 AM, Amit Langote <
> Langote_Amit_f8@lab.ntt.co.jp> wrote:
>
>> On 2016/08/09 6:02, Robert Haas wrote:
>> > On Mon, Aug 8, 2016 at 1:40 AM, Amit Langote
>> > <Langote_Amit_f8@lab.ntt.co.jp> wrote:
>> >>> +1, if we could do it. It will need a change in the way Amit's patch
>> stores
>> >>> partitioning scheme in PartitionDesc.
>> >>
>> >> Okay, I will try to implement this in the next version of the patch.
>> >>
>> >> One thing that comes to mind is what if a user wants to apply hash
>> >> operator class equality to list partitioned key by specifying a hash
>> >> operator class for the corresponding column. In that case, we would
>> not
>> >> have the ordering procedure with an hash operator class, hence any
>> >> ordering based optimization becomes impossible to implement. The
>> current
>> >> patch rejects a column for partition key if its type does not have a
>> btree
>> >> operator class for both range and list methods, so this issue doesn't
>> >> exist, however it could be seen as a limitation.
>> >
>> > Yes, I think you should expect careful scrutiny of that issue. It
>> > seems clear to me that range partitioning requires a btree opclass,
>> > that hash partitioning requires a hash opclass, and that list
>> > partitioning requires at least one of those things. It would probably
>> > be reasonable to pick one or the other and insist that list
>> > partitioning always requires exactly that, but I can't see how it's
>> > reasonable to insist that you must have both types of opclass for any
>> > type of partitioning.
>>
>> So because we intend to implement optimizations for list partition
>> metadata that presuppose existence of corresponding btree operator class,
>> we should just always require user to specify one (or error out if user
>> doesn't specify and a default one doesn't exist). That way, we explicitly
>> do not support specify hash equality operator for list partitioning.
>>
>> >> So, we have 3 choices for the internal representation of list
>> partitions:
>> >>
>> >> Choice 1 (the current approach): Load them in the same order as they
>> are
>> >> found in the partition catalog:
>> >>
>> >> Table 1: p1 {'b', 'f'}, p2 {'c', 'd'}, p3 {'a', 'e'}
>> >> Table 2: p1 {'c', 'd'}, p2 {'a', 'e'}, p3 {'b', 'f'}
>> >>
>> >> In this case, mismatch on the first list would make the two tables
>> >> incompatibly partitioned, whereas they really aren't incompatible.
>> >
>> > Such a limitation seems clearly unacceptable. We absolutely must be
>> > able to match up compatible partitioning schemes without getting
>> > confused by little details like the order of the partitions.
>>
>> Agreed. Will change my patch to adopt the below method.
>>
>> >> Choice 2: Representation with 2 arrays:
>> >>
>> >> Table 1: ['a', 'b', 'c', 'd', 'e', 'f'], [3, 1, 2, 2, 3, 1]
>> >> Table 2: ['a', 'b', 'c', 'd', 'e', 'f'], [2, 3, 1, 1, 2, 3]
>> >>
>> >> It still doesn't help the case of pairwise joins because it's hard to
>> tell
>> >> which value belongs to which partition (the 2nd array carries the
>> original
>> >> partition numbers). Although it might still work for tuple-routing.
>> >
>> > It's very good for tuple routing. It can also be used to match up
>> > partitions for pairwise joins. Compare the first arrays. If they are
>> > unequal, stop. Else, compare the second arrays, incrementally
>> > building a mapping between them and returning false if the mapping
>> > turns out to be non-bijective. For example, in this case, we look at
>> > index 0 and decide that 3 -> 2. We look at index 1 and decide 1 -> 3.
>> > We look at index 2 and decide 2 -> 1. We look at index 4 and find
>> > that we already have a mapping for 2, but it's compatible because we
>> > need 2 -> 1 and that's what is already there. Similarly for the
>> > remaining entries. This is really a pretty easy loop to write and it
>> > should run very quickly.
>>
>> I see, it does make sense to try to implement this way.
>>
>> >> Choice 3: Order all lists' elements for each list individually and then
>> >> order the lists themselves on their first values:
>> >>
>> >> Table 1: p3 {'a', 'e'}, p2 {'b', 'f'}, p1 {'c', 'd'}
>> >> Table 2: p2 {'a', 'e'}, p1 {'b', 'f'}, p3 {'c', 'd'}
>> >>
>> >> This representation makes pairing partitions for pairwise joining
>> >> convenient but for tuple-routing we still need to visit each partition
>> in
>> >> the worst case.
>> >
>> > I think this is clearly not good enough for tuple routing. If the
>> > algorithm I proposed above turns out to be too slow for matching
>> > partitions, then we could keep both this representation and the
>> > previous one. We are not limited to just one. But I don't think
>> > that's likely to be the case.
>>
>> I agree. Let's see how the option 2 turns out.
>>
>> > Also, note that all of this presupposes we're doing range
>> > partitioning, or perhaps list partitioning with a btree opclass. For
>> > partitioning based on a hash opclass, you'd organize the data based on
>> > the hash values rather than range comparisons.
>>
>> Yes, the current patch does not implement hash partitioning, although I
>> have to think about how to support the hash case when designing the
>> internal data structures.
>>
>>
>> By the way, I am planning to start a new thread with the latest set of
>> patches which I will post in a day or two. I have tried to implement all
>> the bug fixes and improvements that have been suggested on this thread so
>> far. Thanks to all those who reviewed and gave their comments. Please
>> check this page to get a link to the new thread:
>> https://commitfest.postgresql.org/10/611/
>>
>> Thanks,
>> Amit
>>
>>
>>
>
>
> --
> Best Wishes,
> Ashutosh Bapat
> EnterpriseDB Corporation
> The Postgres Database Company
>
--
Best Wishes,
Ashutosh Bapat
EnterpriseDB Corporation
The Postgres Database Company
^ permalink raw reply [nested|flat] 161+ messages in thread
* Re: Declarative partitioning
@ 2016-08-10 10:29 Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
parent: Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
0 siblings, 0 replies; 161+ messages in thread
From: Amit Langote @ 2016-08-10 10:29 UTC (permalink / raw)
To: Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>; +Cc: Robert Haas <robertmhaas@gmail.com>; Ildar Musin <i.musin@postgrespro.ru>; Tom Lane <tgl@sss.pgh.pa.us>; pgsql-hackers
On 2016/08/10 19:18, Ashutosh Bapat wrote:
> FOR VALUE clause of a partition does not allow a constant expression like
> (10000/5 -1). It gives syntax error
> regression=# create table pt1_p1 partition of pt1 for values start (0) end
> ((10000/5) - 1);
> ERROR: syntax error at or near "("
> LINE 1: ...pt1_p1 partition of pt1 for values start (0) end ((10000/5) ...
>
> Shouldn't we allow constant expressions here?
>
> If this has been already discussed, please forgive me and point out the
> relevant mail chain.
Yes, the only allowed values there are string literals, numeric values and
null. Decision to change to it followed from this comment:
"Also, I don't think allowing an a_expr as a bound is remotely sensible
- I think you should allow only Sconst, NumericOnly, NULL, and
UNBOUNDED."
https://www.postgresql.org/message-id/CA%2BTgmoZ1ZMCyGR3b9yvGDq79xYLMnJQwhwn5GVs_GsvPiySDxw%40mail.g...
Thanks,
Amit
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
^ permalink raw reply [nested|flat] 161+ messages in thread
end of thread, other threads:[~2016-08-10 10:29 UTC | newest]
Thread overview: 161+ messages (download: mbox mbox.gz follow: Atom feed)
-- links below jump to the message on this page --
2015-08-18 10:30 Declarative partitioning Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2015-08-18 11:02 ` Thom Brown <thom@linux.com>
2015-08-18 11:23 ` Amit Langote <amitlangote09@gmail.com>
2015-10-20 17:26 ` Thom Brown <thom@linux.com>
2015-10-20 17:34 ` Amit Langote <amitlangote09@gmail.com>
2015-10-20 17:34 ` Thom Brown <thom@linux.com>
2015-08-18 13:43 ` David Fetter <david@fetter.org>
2015-08-19 07:30 ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2015-08-19 12:52 ` David Fetter <david@fetter.org>
2015-08-20 09:58 ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2015-08-20 13:19 ` David Fetter <david@fetter.org>
2015-08-20 17:36 ` Josh Berkus <josh@agliodbs.com>
2015-08-21 05:47 ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2015-08-18 17:31 ` Josh Berkus <josh@agliodbs.com>
2015-08-19 11:59 ` Simon Riggs <simon@2ndQuadrant.com>
2015-08-19 20:10 ` Josh Berkus <josh@agliodbs.com>
2015-08-19 20:18 ` Thom Brown <thom@linux.com>
2015-08-19 22:16 ` Josh Berkus <josh@agliodbs.com>
2015-08-20 09:10 ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2015-08-20 09:34 ` Simon Riggs <simon@2ndQuadrant.com>
2015-08-20 09:47 ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2015-08-22 03:34 ` Jim Nasby <Jim.Nasby@BlueTreble.com>
2015-08-23 23:53 ` Josh Berkus <josh@agliodbs.com>
2015-08-24 07:46 ` Simon Riggs <simon@2ndQuadrant.com>
2015-08-24 12:53 ` Andrew Dunstan <andrew@dunslane.net>
2015-08-18 17:57 ` Marc Mamin <M.Mamin@intershop.de>
2015-08-19 08:03 ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2015-08-19 19:55 ` Marc Mamin <M.Mamin@intershop.de>
2015-08-18 17:59 ` Corey Huinker <corey.huinker@gmail.com>
2015-08-20 09:57 ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2015-08-20 15:51 ` Corey Huinker <corey.huinker@gmail.com>
2015-08-19 12:23 ` Simon Riggs <simon@2ndQuadrant.com>
2015-08-20 02:16 ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2015-08-20 07:05 ` Michael Paquier <michael.paquier@gmail.com>
2015-08-20 07:43 ` Simon Riggs <simon@2ndQuadrant.com>
2015-08-20 21:27 ` David Fetter <david@fetter.org>
2015-08-21 05:52 ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2015-08-21 06:20 ` Pavan Deolasee <pavan.deolasee@gmail.com>
2015-08-20 09:27 ` Pavan Deolasee <pavan.deolasee@gmail.com>
2015-08-20 09:45 ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2015-08-20 12:36 ` David Steele <david@pgmasters.net>
2015-08-20 16:00 ` Corey Huinker <corey.huinker@gmail.com>
2015-08-26 14:00 ` Simon Riggs <simon@2ndQuadrant.com>
2015-09-04 05:51 ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2015-09-04 09:41 ` Simon Riggs <simon@2ndQuadrant.com>
2015-08-26 14:01 ` Simon Riggs <simon@2ndquadrant.com>
2015-10-30 10:08 ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2015-11-05 16:29 ` Robert Haas <robertmhaas@gmail.com>
2015-11-20 09:18 ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2015-11-20 10:20 ` Simon Riggs <simon@2ndQuadrant.com>
2015-11-20 11:09 ` Amit Langote <amitlangote09@gmail.com>
2015-11-20 11:44 ` Alvaro Herrera <alvherre@2ndquadrant.com>
2015-11-23 17:23 ` Robert Haas <robertmhaas@gmail.com>
2015-11-23 18:44 ` Alvaro Herrera <alvherre@2ndquadrant.com>
2015-11-23 22:03 ` Robert Haas <robertmhaas@gmail.com>
2015-12-02 06:41 ` Michael Paquier <michael.paquier@gmail.com>
2015-12-02 06:55 ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2015-12-02 07:36 ` Michael Paquier <michael.paquier@gmail.com>
2015-12-14 07:14 ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2015-12-17 18:56 ` Robert Haas <robertmhaas@gmail.com>
2015-12-22 01:51 ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2016-01-15 10:48 ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2016-01-17 00:47 ` Corey Huinker <corey.huinker@gmail.com>
2016-01-22 04:41 ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2016-01-22 18:42 ` Corey Huinker <corey.huinker@gmail.com>
2016-01-25 10:32 ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2016-02-16 12:57 ` Robert Haas <robertmhaas@gmail.com>
2016-02-18 05:41 ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2016-02-19 20:06 ` Corey Huinker <corey.huinker@gmail.com>
2016-02-24 08:11 ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2016-02-24 18:31 ` Corey Huinker <corey.huinker@gmail.com>
2016-03-08 01:39 ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2016-03-09 00:17 ` Corey Huinker <corey.huinker@gmail.com>
2016-03-09 00:57 ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2016-03-09 01:41 ` Corey Huinker <corey.huinker@gmail.com>
2016-03-16 13:56 ` Alexander Korotkov <a.korotkov@postgrespro.ru>
2016-03-16 14:29 ` Amit Langote <amitlangote09@gmail.com>
2016-03-16 14:49 ` Alexander Korotkov <a.korotkov@postgrespro.ru>
2016-03-21 19:55 ` Robert Haas <robertmhaas@gmail.com>
2016-03-21 20:00 ` Simon Riggs <simon@2ndQuadrant.com>
2016-03-22 02:23 ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2016-04-14 12:34 ` Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
2016-04-15 04:35 ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2016-04-15 09:46 ` Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
2016-04-15 17:00 ` Robert Haas <robertmhaas@gmail.com>
2016-04-18 06:19 ` Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
2016-04-18 05:19 ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2016-04-18 06:38 ` Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
2016-04-18 07:53 ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2016-04-18 08:43 ` Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
2016-04-15 09:48 ` Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
2016-04-18 05:02 ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2016-04-18 06:33 ` Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
2016-04-18 08:55 ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2016-04-19 13:57 ` Ildar Musin <i.musin@postgrespro.ru>
2016-04-19 14:26 ` Alexander Korotkov <a.korotkov@postgrespro.ru>
2016-04-19 14:52 ` Amit Langote <amitlangote09@gmail.com>
2016-04-20 10:28 ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2016-04-20 14:46 ` Ildar Musin <i.musin@postgrespro.ru>
2016-04-20 16:06 ` Amit Langote <amitlangote09@gmail.com>
2016-04-21 02:05 ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2016-05-09 11:21 ` Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
2016-05-10 10:40 ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2016-05-12 08:42 ` Sameer Thakur-2 <Sameer.Thakur@nttdata.com>
2016-05-12 08:55 ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2016-05-16 13:12 ` Ildar Musin <i.musin@postgrespro.ru>
2016-05-17 07:56 ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2016-05-17 17:22 ` Tom Lane <tgl@sss.pgh.pa.us>
2016-05-18 01:26 ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2016-05-18 15:36 ` Ildar Musin <i.musin@postgrespro.ru>
2016-05-20 08:37 ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2016-05-20 15:29 ` Ildar Musin <i.musin@postgrespro.ru>
2016-05-23 10:05 ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2016-06-08 13:22 ` Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
2016-06-09 01:50 ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2016-06-21 07:49 ` Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
2016-06-21 08:13 ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2016-06-24 14:08 ` Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
2016-06-27 10:26 ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2016-07-04 12:31 ` Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
2016-07-05 04:48 ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2016-07-19 13:53 ` Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
2016-07-21 04:40 ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2016-08-05 10:53 ` Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
2016-08-05 11:51 ` Robert Haas <robertmhaas@gmail.com>
2016-08-05 12:10 ` Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
2016-08-05 12:15 ` Robert Haas <robertmhaas@gmail.com>
2016-08-05 12:38 ` Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
2016-08-08 05:40 ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2016-08-08 06:40 ` Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
2016-08-08 21:02 ` Robert Haas <robertmhaas@gmail.com>
2016-08-09 03:47 ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2016-08-09 07:18 ` Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
2016-08-10 10:18 ` Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
2016-08-10 10:29 ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2016-05-18 17:48 ` Tom Lane <tgl@sss.pgh.pa.us>
2016-05-19 10:29 ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2016-04-26 08:46 ` Erik Rijkers <er@xs4all.nl>
2016-05-11 02:26 ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2016-03-09 14:19 ` Robert Haas <robertmhaas@gmail.com>
2016-03-09 17:48 ` Corey Huinker <corey.huinker@gmail.com>
2016-02-23 13:51 ` Robert Haas <robertmhaas@gmail.com>
2016-02-26 06:05 ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2016-03-09 14:20 ` Robert Haas <robertmhaas@gmail.com>
2016-01-22 13:54 ` Tomas Vondra <tomas.vondra@2ndquadrant.com>
2016-01-28 08:45 ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2016-02-15 01:55 ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2016-02-15 20:15 ` Corey Huinker <corey.huinker@gmail.com>
2016-02-16 00:28 ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2016-02-16 02:07 ` Corey Huinker <corey.huinker@gmail.com>
2016-02-16 02:41 ` Josh berkus <josh@agliodbs.com>
2016-02-16 02:46 ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2016-02-28 22:14 ` Ildar Musin <i.musin@postgrespro.ru>
2016-02-29 05:01 ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2016-02-17 02:56 ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
2016-02-20 04:41 ` Peter Eisentraut <peter_e@gmx.net>
2016-02-20 05:08 ` Amit Langote <amitlangote09@gmail.com>
2016-02-16 13:02 ` Robert Haas <robertmhaas@gmail.com>
2016-02-25 15:40 Re: Declarative partitioning Jean-Pierre Pelletier <jppelletier@e-djuster.com>
2016-02-25 15:43 Re: Declarative partitioning Jean-Pierre Pelletier <jppelletier@e-djuster.ca>
2016-02-26 07:14 ` Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
This inbox is served by DDX for PostgreSQL; see mirroring instructions
for how to clone and mirror all data and code used for this inbox