pg.ddx.io pgsql-hackers@postgresql.org mailing list archive
help / color / mirror / Atom feed From: Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
To: Amit Langote <amitlangote09@gmail.com>
To: Ildar Musin <i.musin@postgrespro.ru>
Cc: Alexander Korotkov <a.korotkov@postgrespro.ru>
Cc: Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
Cc: Robert Haas <robertmhaas@gmail.com>
Cc: Corey Huinker <corey.huinker@gmail.com>
Cc: Pg Hackers <pgsql-hackers@postgresql.org>
Subject: Re: Declarative partitioning
Date: Thu, 21 Apr 2016 11:05:38 +0900
Message-ID: <57183572.8020900@lab.ntt.co.jp> (raw )
In-Reply-To: <CA+HiwqF=0v6afwGj=Fc3gBF94Wp65naJUiDt6sFQiNXvtsFXSg@mail.gmail.com >
References: <55D3093C.5010800@lab.ntt.co.jp >
<56CD6598.4090302@lab.ntt.co.jp >
<CADkLM=f1zyL-cV_LK8GpXv=iS_=VbHs8bernUMa+8yHtUXjebQ@mail.gmail.com >
<56DE2D4B.1040001@lab.ntt.co.jp >
<CADkLM=cb7q4TSYQYX4EsMHVK0hjEnBJiA8+XMSSropk2CHwa_A@mail.gmail.com >
<56DF74F0.7060404@lab.ntt.co.jp >
<CAPpHfdut34uqoY1vKgvBhsGZqhR6Nky=_Dxn0un57G_M2CvLTA@mail.gmail.com >
<CA+HiwqFFH10_ku6z_Ps=H7=pR5cWJk9Bdr9nE08+3F53yTEr9A@mail.gmail.com >
<CAPpHfdu0mGbt9T2Jx04OKJfJQ0zW55BFqmisinRYWd2OaowRWQ@mail.gmail.com >
<CA+TgmoaoZRqk2dr3xonta3o4omKuO4V4niVC2DKKo9VpdMc_zQ@mail.gmail.com >
<56F0AC96.2010303@lab.ntt.co.jp >
<CAFjFpReZ2nmpfOA2uGdgVp3ivWC13VDFYOQ7rs5LFeqo_Zm4vA@mail.gmail.com >
<57106F9E.3000309@lab.ntt.co.jp >
<57163954.2020701@postgrespro.ru >
<CAPpHfdtH4rhckHzkqGhGP8O7aejj=J7_qJRA3dTu+iAUpuzPgg@mail.gmail.com >
<CA+HiwqEMQ2EZ0GKBjg2hHT1uP-NRodtodc2JjN1vd0xp_BzTCg@mail.gmail.com >
<571759D8.7030205@lab.ntt.co.jp >
<57179653.30905@postgrespro.ru >
<CA+HiwqF=0v6afwGj=Fc3gBF94Wp65naJUiDt6sFQiNXvtsFXSg@mail.gmail.com >
List-Unsubscribe: <mailto:majordomo@postgresql.org?body=unsub%20pgsql-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
view thread (161+ messages) latest in thread
Message-ID: <57183572.8020900@lab.ntt.co.jp>
Permalink: ../57183572.8020900@lab.ntt.co.jp/
Also on: postgresql.org/message-id/57183572.8020900@lab.ntt.co.jp
copy link · copy postgr.es · copy git am
reply Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Reply to all the recipients using the --to and --cc options:
reply via email
To: pgsql-hackers@postgresql.org
Cc: Langote_Amit_f8@lab.ntt.co.jp, amitlangote09@gmail.com, i.musin@postgrespro.ru, a.korotkov@postgrespro.ru, ashutosh.bapat@enterprisedb.com, robertmhaas@gmail.com, corey.huinker@gmail.com
Subject: Re: Declarative partitioning
In-Reply-To: <57183572.8020900@lab.ntt.co.jp>
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
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