Subject: [PATCH 4/9] psql and pg_dump support for partitions.

From: amit <[email-masked]>
Date: Tue, 12 Jul 2016 17:50:33 +0900

Takes care of both the partition bound deparse stuff and handling
parent-partition relationship (filtering pg_inherits entries pertaining
to partitions and handling appropriately).
---
 src/backend/utils/adt/ruleutils.c          |   78 ++++++++++++++++++++++
 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/test/regress/expected/create_table.out |   23 +++++++
 src/test/regress/sql/create_table.sql      |    6 ++
 7 files changed, 340 insertions(+), 9 deletions(-)

diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 77ce807..5d90413 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -8405,6 +8405,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/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 c418ba7..626fa00 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -6125,6 +6125,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 inhrelid as partrelid, inhparent AS partparent,"
+						 "		 pg_get_expr(relpartbound, inhrelid) AS partbound"
+						 " FROM pg_class c, pg_inherits"
+						 " WHERE c.oid = inhrelid AND c.relispartition");
+
+	res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
+
+	ntups = PQntuples(res);
+
+	*numPartitions = ntups;
+
+	partinfo = (PartInfo *) pg_malloc(ntups * sizeof(PartInfo));
+
+	i_partrelid = PQfnumber(res, "partrelid");
+	i_partparent = PQfnumber(res, "partparent");
+	i_partbound = PQfnumber(res, "partbound");
+
+	for (i = 0; i < ntups; i++)
+	{
+		partinfo[i].partrelid = atooid(PQgetvalue(res, i, i_partrelid));
+		partinfo[i].partparent = atooid(PQgetvalue(res, i, i_partparent));
+		partinfo[i].partdef = pg_strdup(PQgetvalue(res, i, i_partbound));
+	}
+
+	PQclear(res);
+
+	destroyPQExpBuffer(query);
+
+	return partinfo;
+}
+
+/*
  * getIndexes
  *	  get information about every index on a dumpable table
  *
@@ -15302,6 +15359,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 */
@@ -15330,8 +15398,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;
 
@@ -15360,7 +15431,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");
 					}
@@ -15425,15 +15497,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 (");
@@ -15603,6 +15682,15 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 								  tbinfo->reloftype);
 			}
 
+			if (tbinfo->partitionOf)
+			{
+				appendPQExpBufferStr(q, "\n-- For binary upgrade, set up partitions this way.\n");
+				appendPQExpBuffer(q, "ALTER TABLE ONLY %s ATTACH PARTITION %s %s;\n",
+								  fmtId(tbinfo->partitionOf->dobj.name),
+								  tbinfo->dobj.name,
+								  tbinfo->partitiondef);
+			}
+
 			appendPQExpBufferStr(q, "\n-- For binary upgrade, set heap's relfrozenxid and relminmxid\n");
 			appendPQExpBuffer(q, "UPDATE pg_catalog.pg_class\n"
 							  "SET relfrozenxid = '%u', relminmxid = '%u'\n"
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 0292859..760067a 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -320,6 +320,8 @@ typedef struct _tableInfo
 	struct _tableDataInfo *dataObj;		/* TableDataInfo, if dumping its data */
 	int			numTriggers;	/* number of triggers for table */
 	struct _triggerInfo *triggers;		/* array of TriggerInfo structs */
+	struct _tableInfo *partitionOf;	/* TableInfo for the partition parent */
+	char	   *partitiondef;		/* partition key definition */
 } TableInfo;
 
 typedef struct _attrDefInfo
@@ -460,6 +462,15 @@ typedef struct _inhInfo
 	Oid			inhparent;		/* OID of its parent */
 } InhInfo;
 
+/* PartInfo isn't a DumpableObject, just temporary state */
+typedef struct _partInfo
+{
+	Oid			partrelid;		/* OID of a partition */
+	Oid			partparent;		/* OID of its parent */
+	char	   *partdef;		/* partition bound definition */
+} PartInfo;
+
+
 typedef struct _prsInfo
 {
 	DumpableObject dobj;
@@ -626,6 +637,7 @@ extern ConvInfo *getConversions(Archive *fout, int *numConversions);
 extern TableInfo *getTables(Archive *fout, int *numTables);
 extern void getOwnedSeqs(Archive *fout, TableInfo tblinfo[], int numTables);
 extern InhInfo *getInherits(Archive *fout, int *numInherits);
+extern PartInfo *getPartitions(Archive *fout, int *numPartitions);
 extern void getIndexes(Archive *fout, TableInfo tblinfo[], int numTables);
 extern void getConstraints(Archive *fout, TableInfo tblinfo[], int numTables);
 extern RuleInfo *getRules(Archive *fout, int *numRules);
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 10d924a..9554f5e 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -1798,6 +1798,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(c.relpartbound, inhrelid)"
+			 " FROM pg_catalog.pg_class c"
+			 " JOIN pg_catalog.pg_inherits"
+			 " ON c.oid = inhrelid"
+			 " WHERE c.oid = '%s' AND c.relispartition;", oid);
+		result = PSQLexec(buf.data);
+		if (!result)
+			goto error_return;
+
+		if (PQntuples(result) > 0)
+		{
+			parent_name = PQgetvalue(result, 0, 0);
+			partdef = PQgetvalue(result, 0, 1);
+			printfPQExpBuffer(&tmpbuf, _("Partition Of: %s %s"), parent_name,
+						  partdef);
+			printTableAddFooter(&cont, tmpbuf.data);
+			PQclear(result);
+		}
+	}
+
 	if (tableinfo.relkind == 'P')
 	{
 		/* Get the partition key information  */
@@ -2559,8 +2587,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)
@@ -2589,9 +2621,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/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 2b12276..5162c82 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -592,6 +592,29 @@ ERROR:  column "c" named in partition key does not exist
 CREATE TABLE part_c PARTITION OF parted FOR VALUES IN ('c') PARTITION BY RANGE (b);
 -- create a partition of partition
 CREATE TABLE part_c_1_10 PARTITION OF part_c FOR VALUES START (1) END (10);
+-- Partition bound in describe output
+\d part_b
+         Table "public.part_b"
+ Column |  Type   |      Modifiers      
+--------+---------+---------------------
+ a      | text    | 
+ b      | integer | not null default 10
+Partition Of: parted FOR VALUES IN ('b')
+Check constraints:
+    "check_b" CHECK (b > 0)
+
+-- Both partition bound and partition key in describe output
+\d part_c
+         Table "public.part_c"
+ Column |  Type   |     Modifiers      
+--------+---------+--------------------
+ a      | text    | 
+ b      | integer | not null default 1
+Partition Of: parted FOR VALUES IN ('c')
+Partition Key: RANGE (b)
+Check constraints:
+    "check_b" CHECK (b > 0)
+
 -- partition cannot be dropped directly
 DROP TABLE part_a;
 ERROR:  "part_a" is a partition of "parted"
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index c38312e..b29ac79 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -546,6 +546,12 @@ CREATE TABLE part_c PARTITION OF parted FOR VALUES IN ('c') PARTITION BY RANGE (
 -- create a partition of partition
 CREATE TABLE part_c_1_10 PARTITION OF part_c FOR VALUES START (1) END (10);
 
+-- Partition bound in describe output
+\d part_b
+
+-- Both partition bound and partition key in describe output
+\d part_c
+
 -- partition cannot be dropped directly
 DROP TABLE part_a;
 
-- 
1.7.1

--------------954458C17BBEB1BF6FC758E7
Content-Type: text/x-diff;
 name="0005-Refactor-optimizer-s-inheritance-set-expansion-code-2.patch"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
 filename*0="0005-Refactor-optimizer-s-inheritance-set-expansion-code-2.p";
 filename*1="atch"