public inbox for [email protected]  
help / color / mirror / Atom feed
[PATCH 2/9] psql and pg_dump support for partitioned tables.
110+ messages / 2 participants
[nested] [flat]

* [PATCH 2/9] psql and pg_dump support for partitioned tables.
@ 2016-07-12 08:20  amit <[email protected]>
  0 siblings, 0 replies; 110+ messages in thread

From: amit @ 2016-07-12 08:20 UTC (permalink / raw)

Takes care of both the partition key deparse stuff and the new relkind.
---
 src/backend/utils/adt/ruleutils.c          |  140 ++++++++++++++++++++++++++++
 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/pg_proc.h              |    2 +
 src/include/utils/builtins.h               |    1 +
 src/test/regress/expected/create_table.out |   26 +++++
 src/test/regress/sql/create_table.sql      |   13 +++
 9 files changed, 260 insertions(+), 26 deletions(-)

diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 8a81d7a..03be202 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_table.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
@@ -315,6 +316,7 @@ static char *pg_get_indexdef_worker(Oid indexrelid, int colno,
 					   const Oid *excludeOps,
 					   bool attrsOnly, bool showTblSpc,
 					   int prettyFlags, bool missing_ok);
+static char *pg_get_partkeydef_worker(Oid relid, int prettyFlags);
 static char *pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 							int prettyFlags, bool missing_ok);
 static text *pg_get_expr_worker(text *expr, Oid relid, const char *relname,
@@ -1389,6 +1391,144 @@ 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 [ opclass_name ] [, ...])
+ */
+Datum
+pg_get_partkeydef(PG_FUNCTION_ARGS)
+{
+	Oid			relid = PG_GETARG_OID(0);
+	int			prettyFlags;
+
+	prettyFlags = PRETTYFLAG_INDENT;
+	PG_RETURN_TEXT_P(string_to_text(pg_get_partkeydef_worker(relid,
+									prettyFlags)));
+}
+
+/*
+ * Internal workhorse to decompile a partition key definition.
+ */
+static char *
+pg_get_partkeydef_worker(Oid relid, int prettyFlags)
+{
+	Form_pg_partitioned_table	form;
+	HeapTuple	tuple;
+	oidvector  *partclass;
+	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_table) GETSTRUCT(tuple);
+
+	Assert(form->partrelid == relid);
+
+	/* Must get partclass, and partexprs the hard way */
+	datum = SysCacheGetAttr(PARTEDRELID, tuple,
+							Anum_pg_partitioned_table_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_table_partexprbin))
+	{
+		Datum		exprsDatum;
+		bool		isnull;
+		char	   *exprsString;
+
+		exprsDatum = SysCacheGetAttr(PARTEDRELID, tuple,
+									 Anum_pg_partitioned_table_partexprbin, &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);
+
+	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,
+											0, 0);
+
+			appendStringInfoString(&buf, 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/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index ba9c276..c805a84 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -1253,9 +1253,10 @@ expand_table_name_patterns(Archive *fout,
 						  "SELECT c.oid"
 						  "\nFROM pg_catalog.pg_class c"
 		"\n     LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace"
-					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c')\n",
+					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c')\n",
 						  RELKIND_RELATION, RELKIND_SEQUENCE, RELKIND_VIEW,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 		processSQLNamePattern(GetConnection(fout), query, cell->val, true,
 							  false, "n.nspname", "c.relname", NULL,
 							  "pg_catalog.pg_table_is_visible(c.oid)");
@@ -2125,6 +2126,9 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo, bool oids)
 	/* Skip FOREIGN TABLEs (no data to dump) */
 	if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 		return;
+	/* Skip partitioned tables (data in partitions) */
+	if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+		return;
 
 	/* Don't dump data in unlogged tables, if so requested */
 	if (tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED &&
@@ -5204,6 +5208,7 @@ getTables(Archive *fout, int *numTables)
 	int			i_reloftype;
 	int			i_relpages;
 	int			i_changed_acl;
+	int			i_partkeydef;
 
 	/* Make sure we are in proper schema */
 	selectSourceSchema(fout, "pg_catalog");
@@ -5289,7 +5294,8 @@ getTables(Archive *fout, int *numTables)
 						  "OR %s IS NOT NULL "
 						  "OR %s IS NOT NULL"
 						  "))"
-						  "AS changed_acl "
+						  "AS changed_acl, "
+						  "CASE WHEN c.relkind = 'P' THEN pg_catalog.pg_get_partkeydef(c.oid) ELSE NULL END AS partkeydef "
 						  "FROM pg_class c "
 						  "LEFT JOIN pg_depend d ON "
 						  "(c.relkind = '%c' AND "
@@ -5301,7 +5307,7 @@ getTables(Archive *fout, int *numTables)
 						  "(c.oid = pip.objoid "
 						  "AND pip.classoid = 'pg_class'::regclass "
 						  "AND pip.objsubid = 0) "
-				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c') "
+				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c', '%c') "
 						  "ORDER BY c.oid",
 						  acl_subquery->data,
 						  racl_subquery->data,
@@ -5315,7 +5321,8 @@ getTables(Archive *fout, int *numTables)
 						  RELKIND_SEQUENCE,
 						  RELKIND_RELATION, RELKIND_SEQUENCE,
 						  RELKIND_VIEW, RELKIND_COMPOSITE_TYPE,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 
 		destroyPQExpBuffer(acl_subquery);
 		destroyPQExpBuffer(racl_subquery);
@@ -5877,6 +5884,7 @@ getTables(Archive *fout, int *numTables)
 	i_toastreloptions = PQfnumber(res, "toast_reloptions");
 	i_reloftype = PQfnumber(res, "reloftype");
 	i_changed_acl = PQfnumber(res, "changed_acl");
+	i_partkeydef = PQfnumber(res, "partkeydef");
 
 	if (dopt->lockWaitTimeout && fout->remoteVersion >= 70300)
 	{
@@ -5947,6 +5955,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 */
 
@@ -5991,7 +6000,9 @@ getTables(Archive *fout, int *numTables)
 		 * We only need to lock the table for certain components; see
 		 * pg_dump.h
 		 */
-		if (tblinfo[i].dobj.dump && tblinfo[i].relkind == RELKIND_RELATION &&
+		if (tblinfo[i].dobj.dump &&
+			(tblinfo[i].relkind == RELKIND_RELATION ||
+			 tblinfo->relkind == RELKIND_PARTITIONED_TABLE) &&
 			(tblinfo[i].dobj.dump & DUMP_COMPONENTS_REQUIRING_LOCK))
 		{
 			resetPQExpBuffer(query);
@@ -6093,7 +6104,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);
 
@@ -15446,6 +15460,9 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 				appendPQExpBufferChar(q, ')');
 			}
 
+			if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+				appendPQExpBuffer(q, "\nPARTITION BY %s", tbinfo->partkeydef);
+
 			if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 				appendPQExpBuffer(q, "\nSERVER %s", fmtId(srvname));
 		}
@@ -15506,6 +15523,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 		 */
 		if (dopt->binary_upgrade &&
 			(tbinfo->relkind == RELKIND_RELATION ||
+			 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 			 tbinfo->relkind == RELKIND_FOREIGN_TABLE))
 		{
 			for (j = 0; j < tbinfo->numatts; j++)
@@ -15524,7 +15542,8 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 					appendStringLiteralAH(q, fmtId(tbinfo->dobj.name), fout);
 					appendPQExpBufferStr(q, "::pg_catalog.regclass;\n");
 
-					if (tbinfo->relkind == RELKIND_RELATION)
+					if (tbinfo->relkind == RELKIND_RELATION ||
+						tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
 						appendPQExpBuffer(q, "ALTER TABLE ONLY %s ",
 										  fmtId(tbinfo->dobj.name));
 					else
@@ -15741,6 +15760,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 	 * dump properties we only have ALTER TABLE syntax for
 	 */
 	if ((tbinfo->relkind == RELKIND_RELATION ||
+		 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 		 tbinfo->relkind == RELKIND_MATVIEW) &&
 		tbinfo->relreplident != REPLICA_IDENTITY_DEFAULT)
 	{
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 2bfa2d9..0292859 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -310,6 +310,7 @@ typedef struct _tableInfo
 	bool	   *inhNotNull;		/* true if NOT NULL is inherited */
 	struct _attrDefInfo **attrdefs;		/* DEFAULT expressions */
 	struct _constraintInfo *checkexprs; /* CHECK constraints */
+	char	   *partkeydef;		/* partition key definition */
 
 	/*
 	 * Stuff computed only for dumpable tables.
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 6275a68..10d924a 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -861,6 +861,7 @@ permissionsList(const char *pattern)
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'S' THEN '%s'"
@@ -870,6 +871,7 @@ permissionsList(const char *pattern)
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("sequence"),
@@ -920,7 +922,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
@@ -1567,8 +1569,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)");
 	}
 
@@ -1633,6 +1635,14 @@ describeOneTableDetails(const char *schemaname,
 			printfPQExpBuffer(&title, _("Foreign table \"%s.%s\""),
 							  schemaname, relationname);
 			break;
+		case 'P':
+			if (tableinfo.relpersistence == 'u')
+				printfPQExpBuffer(&title, _("Unlogged table \"%s.%s\""),
+								  schemaname, relationname);
+			else
+				printfPQExpBuffer(&title, _("Table \"%s.%s\""),
+								  schemaname, relationname);
+			break;
 		default:
 			/* untranslated unknown relkind */
 			printfPQExpBuffer(&title, "?%c? \"%s.%s\"",
@@ -1646,8 +1656,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");
@@ -1667,12 +1677,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");
 	}
 
@@ -1772,7 +1782,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);
@@ -1780,14 +1790,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 (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 */
@@ -1926,7 +1955,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;
@@ -2485,7 +2514,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;
@@ -2696,7 +2725,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
@@ -3024,6 +3053,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'i' THEN '%s'"
@@ -3035,6 +3065,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("index"),
@@ -3079,7 +3110,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 019f75a..5e5c370 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/pg_proc.h b/src/include/catalog/pg_proc.h
index e2d08ba..b45688b 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -1980,6 +1980,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/utils/builtins.h b/src/include/utils/builtins.h
index 2ae212a..e800647 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -729,6 +729,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/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 708232d..2fec847 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -408,3 +408,29 @@ CREATE TABLE no_inh_con_parted (
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
 ERROR:  cannot add NO INHERIT constraint to partitioned table "no_inh_con_parted"
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+Table "public.describe_range_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition Key: RANGE ((a + b))
+
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+Table "public.describe_list_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition Key: LIST (a)
+
+DROP TABLE describe_range_key, describe_list_key;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 6e4a8be..4dd6a0a 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -402,3 +402,16 @@ CREATE TABLE no_inh_con_parted (
 	a int,
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
+
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+DROP TABLE describe_range_key, describe_list_key;
-- 
1.7.1


--------------4012470FC48539D6F6714CEA
Content-Type: text/x-diff;
 name="0003-Catalog-and-DDL-for-partitions-4.patch"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
 filename="0003-Catalog-and-DDL-for-partitions-4.patch"



^ permalink  raw  reply  [nested|flat] 110+ messages in thread

* [PATCH 2/9] psql and pg_dump support for partitioned tables.
@ 2016-07-12 08:20  amit <[email protected]>
  0 siblings, 0 replies; 110+ messages in thread

From: amit @ 2016-07-12 08:20 UTC (permalink / raw)

Takes care of both the partition key deparse stuff and the new relkind.
---
 src/backend/utils/adt/ruleutils.c          |  140 ++++++++++++++++++++++++++++
 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/pg_proc.h              |    2 +
 src/include/utils/builtins.h               |    1 +
 src/test/regress/expected/create_table.out |   26 +++++
 src/test/regress/sql/create_table.sql      |   13 +++
 9 files changed, 260 insertions(+), 26 deletions(-)

diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 8a81d7a..77ce807 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_table.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
@@ -315,6 +316,7 @@ static char *pg_get_indexdef_worker(Oid indexrelid, int colno,
 					   const Oid *excludeOps,
 					   bool attrsOnly, bool showTblSpc,
 					   int prettyFlags, bool missing_ok);
+static char *pg_get_partkeydef_worker(Oid relId, int prettyFlags);
 static char *pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 							int prettyFlags, bool missing_ok);
 static text *pg_get_expr_worker(text *expr, Oid relid, const char *relname,
@@ -1389,6 +1391,144 @@ 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 [ 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_table	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_table) GETSTRUCT(tuple);
+
+	Assert(form->partedrelid == relId);
+
+	/* Must get partclass, and partexprs the hard way */
+	datum = SysCacheGetAttr(PARTEDRELID, tuple,
+							Anum_pg_partitioned_table_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_table_partexprbin))
+	{
+		Datum		exprsDatum;
+		bool		isnull;
+		char	   *exprsString;
+
+		exprsDatum = SysCacheGetAttr(PARTEDRELID, tuple,
+									 Anum_pg_partitioned_table_partexprbin, &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);
+
+	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,
+											0, 0);
+
+			appendStringInfoString(&buf, 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/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index a5c2d09..c418ba7 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -1253,9 +1253,10 @@ expand_table_name_patterns(Archive *fout,
 						  "SELECT c.oid"
 						  "\nFROM pg_catalog.pg_class c"
 		"\n     LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace"
-					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c')\n",
+					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c')\n",
 						  RELKIND_RELATION, RELKIND_SEQUENCE, RELKIND_VIEW,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 		processSQLNamePattern(GetConnection(fout), query, cell->val, true,
 							  false, "n.nspname", "c.relname", NULL,
 							  "pg_catalog.pg_table_is_visible(c.oid)");
@@ -2117,6 +2118,9 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo, bool oids)
 	/* Skip FOREIGN TABLEs (no data to dump) */
 	if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 		return;
+	/* Skip partitioned tables (data in partitions) */
+	if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+		return;
 
 	/* Don't dump data in unlogged tables, if so requested */
 	if (tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED &&
@@ -5195,6 +5199,7 @@ getTables(Archive *fout, int *numTables)
 	int			i_reloftype;
 	int			i_relpages;
 	int			i_changed_acl;
+	int			i_partkeydef;
 
 	/* Make sure we are in proper schema */
 	selectSourceSchema(fout, "pg_catalog");
@@ -5280,7 +5285,8 @@ getTables(Archive *fout, int *numTables)
 						  "OR %s IS NOT NULL "
 						  "OR %s IS NOT NULL"
 						  "))"
-						  "AS changed_acl "
+						  "AS changed_acl, "
+						  "CASE WHEN c.relkind = 'P' THEN pg_catalog.pg_get_partkeydef(c.oid) ELSE NULL END AS partkeydef "
 						  "FROM pg_class c "
 						  "LEFT JOIN pg_depend d ON "
 						  "(c.relkind = '%c' AND "
@@ -5292,7 +5298,7 @@ getTables(Archive *fout, int *numTables)
 						  "(c.oid = pip.objoid "
 						  "AND pip.classoid = 'pg_class'::regclass "
 						  "AND pip.objsubid = 0) "
-				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c') "
+				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c', '%c') "
 						  "ORDER BY c.oid",
 						  acl_subquery->data,
 						  racl_subquery->data,
@@ -5306,7 +5312,8 @@ getTables(Archive *fout, int *numTables)
 						  RELKIND_SEQUENCE,
 						  RELKIND_RELATION, RELKIND_SEQUENCE,
 						  RELKIND_VIEW, RELKIND_COMPOSITE_TYPE,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 
 		destroyPQExpBuffer(acl_subquery);
 		destroyPQExpBuffer(racl_subquery);
@@ -5868,6 +5875,7 @@ getTables(Archive *fout, int *numTables)
 	i_toastreloptions = PQfnumber(res, "toast_reloptions");
 	i_reloftype = PQfnumber(res, "reloftype");
 	i_changed_acl = PQfnumber(res, "changed_acl");
+	i_partkeydef = PQfnumber(res, "partkeydef");
 
 	if (dopt->lockWaitTimeout && fout->remoteVersion >= 70300)
 	{
@@ -5938,6 +5946,7 @@ getTables(Archive *fout, int *numTables)
 		else
 			tblinfo[i].checkoption = pg_strdup(PQgetvalue(res, i, i_checkoption));
 		tblinfo[i].toast_reloptions = pg_strdup(PQgetvalue(res, i, i_toastreloptions));
+		tblinfo[i].partkeydef = pg_strdup(PQgetvalue(res, i, i_partkeydef));
 
 		/* other fields were zeroed above */
 
@@ -5982,7 +5991,9 @@ getTables(Archive *fout, int *numTables)
 		 * We only need to lock the table for certain components; see
 		 * pg_dump.h
 		 */
-		if (tblinfo[i].dobj.dump && tblinfo[i].relkind == RELKIND_RELATION &&
+		if (tblinfo[i].dobj.dump &&
+			(tblinfo[i].relkind == RELKIND_RELATION ||
+			 tblinfo->relkind == RELKIND_PARTITIONED_TABLE) &&
 			(tblinfo[i].dobj.dump & DUMP_COMPONENTS_REQUIRING_LOCK))
 		{
 			resetPQExpBuffer(query);
@@ -6084,7 +6095,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);
 
@@ -15437,6 +15451,9 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 				appendPQExpBufferChar(q, ')');
 			}
 
+			if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+				appendPQExpBuffer(q, "\nPARTITION BY %s", tbinfo->partkeydef);
+
 			if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 				appendPQExpBuffer(q, "\nSERVER %s", fmtId(srvname));
 		}
@@ -15497,6 +15514,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 		 */
 		if (dopt->binary_upgrade &&
 			(tbinfo->relkind == RELKIND_RELATION ||
+			 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 			 tbinfo->relkind == RELKIND_FOREIGN_TABLE))
 		{
 			for (j = 0; j < tbinfo->numatts; j++)
@@ -15515,7 +15533,8 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 					appendStringLiteralAH(q, fmtId(tbinfo->dobj.name), fout);
 					appendPQExpBufferStr(q, "::pg_catalog.regclass;\n");
 
-					if (tbinfo->relkind == RELKIND_RELATION)
+					if (tbinfo->relkind == RELKIND_RELATION ||
+						tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
 						appendPQExpBuffer(q, "ALTER TABLE ONLY %s ",
 										  fmtId(tbinfo->dobj.name));
 					else
@@ -15732,6 +15751,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 	 * dump properties we only have ALTER TABLE syntax for
 	 */
 	if ((tbinfo->relkind == RELKIND_RELATION ||
+		 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 		 tbinfo->relkind == RELKIND_MATVIEW) &&
 		tbinfo->relreplident != REPLICA_IDENTITY_DEFAULT)
 	{
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 2bfa2d9..0292859 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -310,6 +310,7 @@ typedef struct _tableInfo
 	bool	   *inhNotNull;		/* true if NOT NULL is inherited */
 	struct _attrDefInfo **attrdefs;		/* DEFAULT expressions */
 	struct _constraintInfo *checkexprs; /* CHECK constraints */
+	char	   *partkeydef;		/* partition key definition */
 
 	/*
 	 * Stuff computed only for dumpable tables.
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 6275a68..10d924a 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -861,6 +861,7 @@ permissionsList(const char *pattern)
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'S' THEN '%s'"
@@ -870,6 +871,7 @@ permissionsList(const char *pattern)
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("sequence"),
@@ -920,7 +922,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
@@ -1567,8 +1569,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)");
 	}
 
@@ -1633,6 +1635,14 @@ describeOneTableDetails(const char *schemaname,
 			printfPQExpBuffer(&title, _("Foreign table \"%s.%s\""),
 							  schemaname, relationname);
 			break;
+		case 'P':
+			if (tableinfo.relpersistence == 'u')
+				printfPQExpBuffer(&title, _("Unlogged table \"%s.%s\""),
+								  schemaname, relationname);
+			else
+				printfPQExpBuffer(&title, _("Table \"%s.%s\""),
+								  schemaname, relationname);
+			break;
 		default:
 			/* untranslated unknown relkind */
 			printfPQExpBuffer(&title, "?%c? \"%s.%s\"",
@@ -1646,8 +1656,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");
@@ -1667,12 +1677,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");
 	}
 
@@ -1772,7 +1782,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);
@@ -1780,14 +1790,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 (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 */
@@ -1926,7 +1955,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;
@@ -2485,7 +2514,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;
@@ -2696,7 +2725,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
@@ -3024,6 +3053,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'i' THEN '%s'"
@@ -3035,6 +3065,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("index"),
@@ -3079,7 +3110,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 019f75a..5e5c370 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/pg_proc.h b/src/include/catalog/pg_proc.h
index e2d08ba..b45688b 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -1980,6 +1980,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/utils/builtins.h b/src/include/utils/builtins.h
index 2ae212a..e800647 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -729,6 +729,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/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 708232d..2fec847 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -408,3 +408,29 @@ CREATE TABLE no_inh_con_parted (
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
 ERROR:  cannot add NO INHERIT constraint to partitioned table "no_inh_con_parted"
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+Table "public.describe_range_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition Key: RANGE ((a + b))
+
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+Table "public.describe_list_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition Key: LIST (a)
+
+DROP TABLE describe_range_key, describe_list_key;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 6e4a8be..4dd6a0a 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -402,3 +402,16 @@ CREATE TABLE no_inh_con_parted (
 	a int,
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
+
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+DROP TABLE describe_range_key, describe_list_key;
-- 
1.7.1


--------------6EB5B318125CB7DB1A4FD229
Content-Type: text/x-diff;
 name="0003-Catalog-and-DDL-for-partitions-3.patch"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
 filename="0003-Catalog-and-DDL-for-partitions-3.patch"



^ permalink  raw  reply  [nested|flat] 110+ messages in thread

* [PATCH 2/9] psql and pg_dump support for partitioned tables.
@ 2016-07-12 08:20  amit <[email protected]>
  0 siblings, 0 replies; 110+ messages in thread

From: amit @ 2016-07-12 08:20 UTC (permalink / raw)

Takes care of both the partition key deparse stuff and the new relkind.
---
 src/backend/utils/adt/ruleutils.c          |  140 ++++++++++++++++++++++++++++
 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/pg_proc.h              |    2 +
 src/include/utils/builtins.h               |    1 +
 src/test/regress/expected/create_table.out |   26 +++++
 src/test/regress/sql/create_table.sql      |   13 +++
 9 files changed, 260 insertions(+), 26 deletions(-)

diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 8a81d7a..03be202 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_table.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
@@ -315,6 +316,7 @@ static char *pg_get_indexdef_worker(Oid indexrelid, int colno,
 					   const Oid *excludeOps,
 					   bool attrsOnly, bool showTblSpc,
 					   int prettyFlags, bool missing_ok);
+static char *pg_get_partkeydef_worker(Oid relid, int prettyFlags);
 static char *pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 							int prettyFlags, bool missing_ok);
 static text *pg_get_expr_worker(text *expr, Oid relid, const char *relname,
@@ -1389,6 +1391,144 @@ 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 [ opclass_name ] [, ...])
+ */
+Datum
+pg_get_partkeydef(PG_FUNCTION_ARGS)
+{
+	Oid			relid = PG_GETARG_OID(0);
+	int			prettyFlags;
+
+	prettyFlags = PRETTYFLAG_INDENT;
+	PG_RETURN_TEXT_P(string_to_text(pg_get_partkeydef_worker(relid,
+									prettyFlags)));
+}
+
+/*
+ * Internal workhorse to decompile a partition key definition.
+ */
+static char *
+pg_get_partkeydef_worker(Oid relid, int prettyFlags)
+{
+	Form_pg_partitioned_table	form;
+	HeapTuple	tuple;
+	oidvector  *partclass;
+	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_table) GETSTRUCT(tuple);
+
+	Assert(form->partrelid == relid);
+
+	/* Must get partclass, and partexprs the hard way */
+	datum = SysCacheGetAttr(PARTEDRELID, tuple,
+							Anum_pg_partitioned_table_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_table_partexprbin))
+	{
+		Datum		exprsDatum;
+		bool		isnull;
+		char	   *exprsString;
+
+		exprsDatum = SysCacheGetAttr(PARTEDRELID, tuple,
+									 Anum_pg_partitioned_table_partexprbin, &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);
+
+	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,
+											0, 0);
+
+			appendStringInfoString(&buf, 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/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index ba9c276..c805a84 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -1253,9 +1253,10 @@ expand_table_name_patterns(Archive *fout,
 						  "SELECT c.oid"
 						  "\nFROM pg_catalog.pg_class c"
 		"\n     LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace"
-					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c')\n",
+					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c')\n",
 						  RELKIND_RELATION, RELKIND_SEQUENCE, RELKIND_VIEW,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 		processSQLNamePattern(GetConnection(fout), query, cell->val, true,
 							  false, "n.nspname", "c.relname", NULL,
 							  "pg_catalog.pg_table_is_visible(c.oid)");
@@ -2125,6 +2126,9 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo, bool oids)
 	/* Skip FOREIGN TABLEs (no data to dump) */
 	if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 		return;
+	/* Skip partitioned tables (data in partitions) */
+	if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+		return;
 
 	/* Don't dump data in unlogged tables, if so requested */
 	if (tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED &&
@@ -5204,6 +5208,7 @@ getTables(Archive *fout, int *numTables)
 	int			i_reloftype;
 	int			i_relpages;
 	int			i_changed_acl;
+	int			i_partkeydef;
 
 	/* Make sure we are in proper schema */
 	selectSourceSchema(fout, "pg_catalog");
@@ -5289,7 +5294,8 @@ getTables(Archive *fout, int *numTables)
 						  "OR %s IS NOT NULL "
 						  "OR %s IS NOT NULL"
 						  "))"
-						  "AS changed_acl "
+						  "AS changed_acl, "
+						  "CASE WHEN c.relkind = 'P' THEN pg_catalog.pg_get_partkeydef(c.oid) ELSE NULL END AS partkeydef "
 						  "FROM pg_class c "
 						  "LEFT JOIN pg_depend d ON "
 						  "(c.relkind = '%c' AND "
@@ -5301,7 +5307,7 @@ getTables(Archive *fout, int *numTables)
 						  "(c.oid = pip.objoid "
 						  "AND pip.classoid = 'pg_class'::regclass "
 						  "AND pip.objsubid = 0) "
-				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c') "
+				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c', '%c') "
 						  "ORDER BY c.oid",
 						  acl_subquery->data,
 						  racl_subquery->data,
@@ -5315,7 +5321,8 @@ getTables(Archive *fout, int *numTables)
 						  RELKIND_SEQUENCE,
 						  RELKIND_RELATION, RELKIND_SEQUENCE,
 						  RELKIND_VIEW, RELKIND_COMPOSITE_TYPE,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 
 		destroyPQExpBuffer(acl_subquery);
 		destroyPQExpBuffer(racl_subquery);
@@ -5877,6 +5884,7 @@ getTables(Archive *fout, int *numTables)
 	i_toastreloptions = PQfnumber(res, "toast_reloptions");
 	i_reloftype = PQfnumber(res, "reloftype");
 	i_changed_acl = PQfnumber(res, "changed_acl");
+	i_partkeydef = PQfnumber(res, "partkeydef");
 
 	if (dopt->lockWaitTimeout && fout->remoteVersion >= 70300)
 	{
@@ -5947,6 +5955,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 */
 
@@ -5991,7 +6000,9 @@ getTables(Archive *fout, int *numTables)
 		 * We only need to lock the table for certain components; see
 		 * pg_dump.h
 		 */
-		if (tblinfo[i].dobj.dump && tblinfo[i].relkind == RELKIND_RELATION &&
+		if (tblinfo[i].dobj.dump &&
+			(tblinfo[i].relkind == RELKIND_RELATION ||
+			 tblinfo->relkind == RELKIND_PARTITIONED_TABLE) &&
 			(tblinfo[i].dobj.dump & DUMP_COMPONENTS_REQUIRING_LOCK))
 		{
 			resetPQExpBuffer(query);
@@ -6093,7 +6104,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);
 
@@ -15446,6 +15460,9 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 				appendPQExpBufferChar(q, ')');
 			}
 
+			if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+				appendPQExpBuffer(q, "\nPARTITION BY %s", tbinfo->partkeydef);
+
 			if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 				appendPQExpBuffer(q, "\nSERVER %s", fmtId(srvname));
 		}
@@ -15506,6 +15523,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 		 */
 		if (dopt->binary_upgrade &&
 			(tbinfo->relkind == RELKIND_RELATION ||
+			 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 			 tbinfo->relkind == RELKIND_FOREIGN_TABLE))
 		{
 			for (j = 0; j < tbinfo->numatts; j++)
@@ -15524,7 +15542,8 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 					appendStringLiteralAH(q, fmtId(tbinfo->dobj.name), fout);
 					appendPQExpBufferStr(q, "::pg_catalog.regclass;\n");
 
-					if (tbinfo->relkind == RELKIND_RELATION)
+					if (tbinfo->relkind == RELKIND_RELATION ||
+						tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
 						appendPQExpBuffer(q, "ALTER TABLE ONLY %s ",
 										  fmtId(tbinfo->dobj.name));
 					else
@@ -15741,6 +15760,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 	 * dump properties we only have ALTER TABLE syntax for
 	 */
 	if ((tbinfo->relkind == RELKIND_RELATION ||
+		 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 		 tbinfo->relkind == RELKIND_MATVIEW) &&
 		tbinfo->relreplident != REPLICA_IDENTITY_DEFAULT)
 	{
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 2bfa2d9..0292859 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -310,6 +310,7 @@ typedef struct _tableInfo
 	bool	   *inhNotNull;		/* true if NOT NULL is inherited */
 	struct _attrDefInfo **attrdefs;		/* DEFAULT expressions */
 	struct _constraintInfo *checkexprs; /* CHECK constraints */
+	char	   *partkeydef;		/* partition key definition */
 
 	/*
 	 * Stuff computed only for dumpable tables.
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 6275a68..10d924a 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -861,6 +861,7 @@ permissionsList(const char *pattern)
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'S' THEN '%s'"
@@ -870,6 +871,7 @@ permissionsList(const char *pattern)
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("sequence"),
@@ -920,7 +922,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
@@ -1567,8 +1569,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)");
 	}
 
@@ -1633,6 +1635,14 @@ describeOneTableDetails(const char *schemaname,
 			printfPQExpBuffer(&title, _("Foreign table \"%s.%s\""),
 							  schemaname, relationname);
 			break;
+		case 'P':
+			if (tableinfo.relpersistence == 'u')
+				printfPQExpBuffer(&title, _("Unlogged table \"%s.%s\""),
+								  schemaname, relationname);
+			else
+				printfPQExpBuffer(&title, _("Table \"%s.%s\""),
+								  schemaname, relationname);
+			break;
 		default:
 			/* untranslated unknown relkind */
 			printfPQExpBuffer(&title, "?%c? \"%s.%s\"",
@@ -1646,8 +1656,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");
@@ -1667,12 +1677,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");
 	}
 
@@ -1772,7 +1782,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);
@@ -1780,14 +1790,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 (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 */
@@ -1926,7 +1955,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;
@@ -2485,7 +2514,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;
@@ -2696,7 +2725,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
@@ -3024,6 +3053,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'i' THEN '%s'"
@@ -3035,6 +3065,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("index"),
@@ -3079,7 +3110,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 50a45eb..8284a9c 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/pg_proc.h b/src/include/catalog/pg_proc.h
index e2d08ba..b45688b 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -1980,6 +1980,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/utils/builtins.h b/src/include/utils/builtins.h
index 2ae212a..e800647 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -729,6 +729,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/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 0af1422..91ba1be 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -412,3 +412,29 @@ CREATE TABLE no_inh_con_parted (
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
 ERROR:  cannot add NO INHERIT constraint to partitioned table "no_inh_con_parted"
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+Table "public.describe_range_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition Key: RANGE ((a + b))
+
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+Table "public.describe_list_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition Key: LIST (a)
+
+DROP TABLE describe_range_key, describe_list_key;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 418ac55..b75542d 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -406,3 +406,16 @@ CREATE TABLE no_inh_con_parted (
 	a int,
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
+
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+DROP TABLE describe_range_key, describe_list_key;
-- 
1.7.1


--------------D008BA677674C4836D7B65AD
Content-Type: text/x-diff;
 name="0003-Catalog-and-DDL-for-partitions-5.patch"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
 filename="0003-Catalog-and-DDL-for-partitions-5.patch"



^ permalink  raw  reply  [nested|flat] 110+ messages in thread

* [PATCH 2/9] psql and pg_dump support for partitioned tables.
@ 2016-07-12 08:20  amit <[email protected]>
  0 siblings, 0 replies; 110+ messages in thread

From: amit @ 2016-07-12 08:20 UTC (permalink / raw)

Takes care of both the partition key deparse stuff and the new relkind.
---
 src/backend/utils/adt/ruleutils.c          |  140 ++++++++++++++++++++++++++++
 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/pg_proc.h              |    2 +
 src/include/utils/builtins.h               |    1 +
 src/test/regress/expected/create_table.out |   26 +++++
 src/test/regress/sql/create_table.sql      |   13 +++
 9 files changed, 260 insertions(+), 26 deletions(-)

diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 8a81d7a..03be202 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_table.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
@@ -315,6 +316,7 @@ static char *pg_get_indexdef_worker(Oid indexrelid, int colno,
 					   const Oid *excludeOps,
 					   bool attrsOnly, bool showTblSpc,
 					   int prettyFlags, bool missing_ok);
+static char *pg_get_partkeydef_worker(Oid relid, int prettyFlags);
 static char *pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 							int prettyFlags, bool missing_ok);
 static text *pg_get_expr_worker(text *expr, Oid relid, const char *relname,
@@ -1389,6 +1391,144 @@ 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 [ opclass_name ] [, ...])
+ */
+Datum
+pg_get_partkeydef(PG_FUNCTION_ARGS)
+{
+	Oid			relid = PG_GETARG_OID(0);
+	int			prettyFlags;
+
+	prettyFlags = PRETTYFLAG_INDENT;
+	PG_RETURN_TEXT_P(string_to_text(pg_get_partkeydef_worker(relid,
+									prettyFlags)));
+}
+
+/*
+ * Internal workhorse to decompile a partition key definition.
+ */
+static char *
+pg_get_partkeydef_worker(Oid relid, int prettyFlags)
+{
+	Form_pg_partitioned_table	form;
+	HeapTuple	tuple;
+	oidvector  *partclass;
+	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_table) GETSTRUCT(tuple);
+
+	Assert(form->partrelid == relid);
+
+	/* Must get partclass, and partexprs the hard way */
+	datum = SysCacheGetAttr(PARTEDRELID, tuple,
+							Anum_pg_partitioned_table_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_table_partexprbin))
+	{
+		Datum		exprsDatum;
+		bool		isnull;
+		char	   *exprsString;
+
+		exprsDatum = SysCacheGetAttr(PARTEDRELID, tuple,
+									 Anum_pg_partitioned_table_partexprbin, &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);
+
+	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,
+											0, 0);
+
+			appendStringInfoString(&buf, 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/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 51b8a1a..854dddc 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -1253,9 +1253,10 @@ expand_table_name_patterns(Archive *fout,
 						  "SELECT c.oid"
 						  "\nFROM pg_catalog.pg_class c"
 		"\n     LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace"
-					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c')\n",
+					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c')\n",
 						  RELKIND_RELATION, RELKIND_SEQUENCE, RELKIND_VIEW,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 		processSQLNamePattern(GetConnection(fout), query, cell->val, true,
 							  false, "n.nspname", "c.relname", NULL,
 							  "pg_catalog.pg_table_is_visible(c.oid)");
@@ -2125,6 +2126,9 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo, bool oids)
 	/* Skip FOREIGN TABLEs (no data to dump) */
 	if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 		return;
+	/* Skip partitioned tables (data in partitions) */
+	if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+		return;
 
 	/* Don't dump data in unlogged tables, if so requested */
 	if (tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED &&
@@ -5211,6 +5215,7 @@ getTables(Archive *fout, int *numTables)
 	int			i_reloftype;
 	int			i_relpages;
 	int			i_changed_acl;
+	int			i_partkeydef;
 
 	/* Make sure we are in proper schema */
 	selectSourceSchema(fout, "pg_catalog");
@@ -5296,7 +5301,8 @@ getTables(Archive *fout, int *numTables)
 						  "OR %s IS NOT NULL "
 						  "OR %s IS NOT NULL"
 						  "))"
-						  "AS changed_acl "
+						  "AS changed_acl, "
+						  "CASE WHEN c.relkind = 'P' THEN pg_catalog.pg_get_partkeydef(c.oid) ELSE NULL END AS partkeydef "
 						  "FROM pg_class c "
 						  "LEFT JOIN pg_depend d ON "
 						  "(c.relkind = '%c' AND "
@@ -5308,7 +5314,7 @@ getTables(Archive *fout, int *numTables)
 						  "(c.oid = pip.objoid "
 						  "AND pip.classoid = 'pg_class'::regclass "
 						  "AND pip.objsubid = 0) "
-				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c') "
+				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c', '%c') "
 						  "ORDER BY c.oid",
 						  acl_subquery->data,
 						  racl_subquery->data,
@@ -5322,7 +5328,8 @@ getTables(Archive *fout, int *numTables)
 						  RELKIND_SEQUENCE,
 						  RELKIND_RELATION, RELKIND_SEQUENCE,
 						  RELKIND_VIEW, RELKIND_COMPOSITE_TYPE,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 
 		destroyPQExpBuffer(acl_subquery);
 		destroyPQExpBuffer(racl_subquery);
@@ -5884,6 +5891,7 @@ getTables(Archive *fout, int *numTables)
 	i_toastreloptions = PQfnumber(res, "toast_reloptions");
 	i_reloftype = PQfnumber(res, "reloftype");
 	i_changed_acl = PQfnumber(res, "changed_acl");
+	i_partkeydef = PQfnumber(res, "partkeydef");
 
 	if (dopt->lockWaitTimeout && fout->remoteVersion >= 70300)
 	{
@@ -5954,6 +5962,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 */
 
@@ -5998,7 +6007,9 @@ getTables(Archive *fout, int *numTables)
 		 * We only need to lock the table for certain components; see
 		 * pg_dump.h
 		 */
-		if (tblinfo[i].dobj.dump && tblinfo[i].relkind == RELKIND_RELATION &&
+		if (tblinfo[i].dobj.dump &&
+			(tblinfo[i].relkind == RELKIND_RELATION ||
+			 tblinfo->relkind == RELKIND_PARTITIONED_TABLE) &&
 			(tblinfo[i].dobj.dump & DUMP_COMPONENTS_REQUIRING_LOCK))
 		{
 			resetPQExpBuffer(query);
@@ -6100,7 +6111,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);
 
@@ -15453,6 +15467,9 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 				appendPQExpBufferChar(q, ')');
 			}
 
+			if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+				appendPQExpBuffer(q, "\nPARTITION BY %s", tbinfo->partkeydef);
+
 			if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 				appendPQExpBuffer(q, "\nSERVER %s", fmtId(srvname));
 		}
@@ -15513,6 +15530,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 		 */
 		if (dopt->binary_upgrade &&
 			(tbinfo->relkind == RELKIND_RELATION ||
+			 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 			 tbinfo->relkind == RELKIND_FOREIGN_TABLE))
 		{
 			for (j = 0; j < tbinfo->numatts; j++)
@@ -15531,7 +15549,8 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 					appendStringLiteralAH(q, fmtId(tbinfo->dobj.name), fout);
 					appendPQExpBufferStr(q, "::pg_catalog.regclass;\n");
 
-					if (tbinfo->relkind == RELKIND_RELATION)
+					if (tbinfo->relkind == RELKIND_RELATION ||
+						tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
 						appendPQExpBuffer(q, "ALTER TABLE ONLY %s ",
 										  fmtId(tbinfo->dobj.name));
 					else
@@ -15748,6 +15767,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 	 * dump properties we only have ALTER TABLE syntax for
 	 */
 	if ((tbinfo->relkind == RELKIND_RELATION ||
+		 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 		 tbinfo->relkind == RELKIND_MATVIEW) &&
 		tbinfo->relreplident != REPLICA_IDENTITY_DEFAULT)
 	{
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 2bfa2d9..0292859 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -310,6 +310,7 @@ typedef struct _tableInfo
 	bool	   *inhNotNull;		/* true if NOT NULL is inherited */
 	struct _attrDefInfo **attrdefs;		/* DEFAULT expressions */
 	struct _constraintInfo *checkexprs; /* CHECK constraints */
+	char	   *partkeydef;		/* partition key definition */
 
 	/*
 	 * Stuff computed only for dumpable tables.
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 6275a68..db6dc5c 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -861,6 +861,7 @@ permissionsList(const char *pattern)
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'S' THEN '%s'"
@@ -870,6 +871,7 @@ permissionsList(const char *pattern)
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("sequence"),
@@ -920,7 +922,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
@@ -1567,8 +1569,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)");
 	}
 
@@ -1633,6 +1635,14 @@ describeOneTableDetails(const char *schemaname,
 			printfPQExpBuffer(&title, _("Foreign table \"%s.%s\""),
 							  schemaname, relationname);
 			break;
+		case 'P':
+			if (tableinfo.relpersistence == 'u')
+				printfPQExpBuffer(&title, _("Unlogged table \"%s.%s\""),
+								  schemaname, relationname);
+			else
+				printfPQExpBuffer(&title, _("Table \"%s.%s\""),
+								  schemaname, relationname);
+			break;
 		default:
 			/* untranslated unknown relkind */
 			printfPQExpBuffer(&title, "?%c? \"%s.%s\"",
@@ -1646,8 +1656,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");
@@ -1667,12 +1677,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");
 	}
 
@@ -1772,7 +1782,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);
@@ -1780,14 +1790,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 (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 */
@@ -1926,7 +1955,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;
@@ -2485,7 +2514,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;
@@ -2696,7 +2725,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
@@ -3024,6 +3053,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'i' THEN '%s'"
@@ -3035,6 +3065,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("index"),
@@ -3079,7 +3110,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 50a45eb..8284a9c 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/pg_proc.h b/src/include/catalog/pg_proc.h
index e2d08ba..b45688b 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -1980,6 +1980,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/utils/builtins.h b/src/include/utils/builtins.h
index 2ae212a..e800647 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -729,6 +729,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/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 1394362..260ec34 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -407,3 +407,29 @@ CREATE TABLE no_inh_con_parted (
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
 ERROR:  cannot add NO INHERIT constraint to partitioned table "no_inh_con_parted"
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+Table "public.describe_range_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition key: RANGE ((a + b))
+
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+Table "public.describe_list_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition key: LIST (a)
+
+DROP TABLE describe_range_key, describe_list_key;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index d383f58..7a1f8de 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -401,3 +401,16 @@ CREATE TABLE no_inh_con_parted (
 	a int,
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
+
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+DROP TABLE describe_range_key, describe_list_key;
-- 
1.7.1


--------------C3235EE96C69D8DAE4DB543E
Content-Type: text/x-diff;
 name="0003-Catalog-and-DDL-for-partitions-6.patch"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
 filename="0003-Catalog-and-DDL-for-partitions-6.patch"



^ permalink  raw  reply  [nested|flat] 110+ messages in thread

* [PATCH 2/8] psql and pg_dump support for partitioned tables.
@ 2016-07-12 08:20  amit <[email protected]>
  0 siblings, 0 replies; 110+ messages in thread

From: amit @ 2016-07-12 08:20 UTC (permalink / raw)

Takes care of both the partition key deparse stuff and the new relkind.
---
 src/backend/utils/adt/ruleutils.c          |  147 ++++++++++++++++++++++++++++
 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/pg_proc.h              |    2 +
 src/include/utils/builtins.h               |    1 +
 src/test/regress/expected/create_table.out |   26 +++++
 src/test/regress/sql/create_table.sql      |   13 +++
 9 files changed, 267 insertions(+), 26 deletions(-)

diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index ec966c7..4691b5b 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, bool missing_ok);
+static char *pg_get_partkeydef_worker(Oid relId, int prettyFlags);
 static char *pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 							int prettyFlags, bool missing_ok);
 static text *pg_get_expr_worker(text *expr, Oid relid, const char *relname,
@@ -1389,6 +1391,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 [ 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/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 4ee10fc..a127672 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -1253,9 +1253,10 @@ expand_table_name_patterns(Archive *fout,
 						  "SELECT c.oid"
 						  "\nFROM pg_catalog.pg_class c"
 		"\n     LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace"
-					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c')\n",
+					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c')\n",
 						  RELKIND_RELATION, RELKIND_SEQUENCE, RELKIND_VIEW,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_REL);
 		processSQLNamePattern(GetConnection(fout), query, cell->val, true,
 							  false, "n.nspname", "c.relname", NULL,
 							  "pg_catalog.pg_table_is_visible(c.oid)");
@@ -2117,6 +2118,9 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo, bool oids)
 	/* Skip FOREIGN TABLEs (no data to dump) */
 	if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 		return;
+	/* Skip partitioned tables (data in partitions) */
+	if (tbinfo->relkind == RELKIND_PARTITIONED_REL)
+		return;
 
 	/* Don't dump data in unlogged tables, if so requested */
 	if (tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED &&
@@ -5195,6 +5199,7 @@ getTables(Archive *fout, int *numTables)
 	int			i_reloftype;
 	int			i_relpages;
 	int			i_changed_acl;
+	int			i_partkeydef;
 
 	/* Make sure we are in proper schema */
 	selectSourceSchema(fout, "pg_catalog");
@@ -5280,7 +5285,8 @@ getTables(Archive *fout, int *numTables)
 						  "OR %s IS NOT NULL "
 						  "OR %s IS NOT NULL"
 						  "))"
-						  "AS changed_acl "
+						  "AS changed_acl, "
+						  "CASE WHEN c.relkind = 'P' THEN pg_catalog.pg_get_partkeydef(c.oid) ELSE NULL END AS partkeydef "
 						  "FROM pg_class c "
 						  "LEFT JOIN pg_depend d ON "
 						  "(c.relkind = '%c' AND "
@@ -5292,7 +5298,7 @@ getTables(Archive *fout, int *numTables)
 						  "(c.oid = pip.objoid "
 						  "AND pip.classoid = 'pg_class'::regclass "
 						  "AND pip.objsubid = 0) "
-				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c') "
+				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c', '%c') "
 						  "ORDER BY c.oid",
 						  acl_subquery->data,
 						  racl_subquery->data,
@@ -5306,7 +5312,8 @@ getTables(Archive *fout, int *numTables)
 						  RELKIND_SEQUENCE,
 						  RELKIND_RELATION, RELKIND_SEQUENCE,
 						  RELKIND_VIEW, RELKIND_COMPOSITE_TYPE,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_REL);
 
 		destroyPQExpBuffer(acl_subquery);
 		destroyPQExpBuffer(racl_subquery);
@@ -5868,6 +5875,7 @@ getTables(Archive *fout, int *numTables)
 	i_toastreloptions = PQfnumber(res, "toast_reloptions");
 	i_reloftype = PQfnumber(res, "reloftype");
 	i_changed_acl = PQfnumber(res, "changed_acl");
+	i_partkeydef = PQfnumber(res, "partkeydef");
 
 	if (dopt->lockWaitTimeout && fout->remoteVersion >= 70300)
 	{
@@ -5938,6 +5946,7 @@ getTables(Archive *fout, int *numTables)
 		else
 			tblinfo[i].checkoption = pg_strdup(PQgetvalue(res, i, i_checkoption));
 		tblinfo[i].toast_reloptions = pg_strdup(PQgetvalue(res, i, i_toastreloptions));
+		tblinfo[i].partkeydef = pg_strdup(PQgetvalue(res, i, i_partkeydef));
 
 		/* other fields were zeroed above */
 
@@ -5982,7 +5991,9 @@ getTables(Archive *fout, int *numTables)
 		 * We only need to lock the table for certain components; see
 		 * pg_dump.h
 		 */
-		if (tblinfo[i].dobj.dump && tblinfo[i].relkind == RELKIND_RELATION &&
+		if (tblinfo[i].dobj.dump &&
+			(tblinfo[i].relkind == RELKIND_RELATION ||
+			 tblinfo->relkind == RELKIND_PARTITIONED_REL) &&
 			(tblinfo[i].dobj.dump & DUMP_COMPONENTS_REQUIRING_LOCK))
 		{
 			resetPQExpBuffer(query);
@@ -6083,7 +6094,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);
 
@@ -15436,6 +15450,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));
 		}
@@ -15496,6 +15513,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++)
@@ -15514,7 +15532,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
@@ -15731,6 +15750,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 	 * dump properties we only have ALTER TABLE syntax for
 	 */
 	if ((tbinfo->relkind == RELKIND_RELATION ||
+		 tbinfo->relkind == RELKIND_PARTITIONED_REL ||
 		 tbinfo->relkind == RELKIND_MATVIEW) &&
 		tbinfo->relreplident != REPLICA_IDENTITY_DEFAULT)
 	{
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 2bfa2d9..0292859 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -310,6 +310,7 @@ typedef struct _tableInfo
 	bool	   *inhNotNull;		/* true if NOT NULL is inherited */
 	struct _attrDefInfo **attrdefs;		/* DEFAULT expressions */
 	struct _constraintInfo *checkexprs; /* CHECK constraints */
+	char	   *partkeydef;		/* partition key definition */
 
 	/*
 	 * Stuff computed only for dumpable tables.
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 27be102..3ba8a90 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -856,6 +856,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"),
@@ -865,6 +866,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");
@@ -911,7 +913,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
@@ -1555,8 +1557,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)");
 	}
 
@@ -1621,6 +1623,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\"",
@@ -1634,8 +1644,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");
@@ -1655,12 +1665,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");
 	}
 
@@ -1760,7 +1770,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);
@@ -1768,14 +1778,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 */
@@ -1914,7 +1943,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;
@@ -2473,7 +2502,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;
@@ -2684,7 +2713,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
@@ -3018,6 +3047,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"),
@@ -3029,6 +3059,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"));
 
@@ -3067,7 +3098,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 
 	appendPQExpBufferStr(&buf, "\nWHERE c.relkind IN (");
 	if (showTables)
-		appendPQExpBufferStr(&buf, "'r',");
+		appendPQExpBufferStr(&buf, "'r', 'P',");
 	if (showViews)
 		appendPQExpBufferStr(&buf, "'v',");
 	if (showMatViews)
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 8469d9f..cd4eb7c 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -427,7 +427,7 @@ static const SchemaQuery Query_for_list_of_tables = {
 	/* catname */
 	"pg_catalog.pg_class c",
 	/* selcondition */
-	"c.relkind IN ('r')",
+	"c.relkind IN ('r', 'P')",
 	/* viscondition */
 	"pg_catalog.pg_table_is_visible(c.oid)",
 	/* namespace */
@@ -458,7 +458,7 @@ static const SchemaQuery Query_for_list_of_updatables = {
 	/* catname */
 	"pg_catalog.pg_class c",
 	/* selcondition */
-	"c.relkind IN ('r', 'f', 'v')",
+	"c.relkind IN ('r', 'f', 'v', 'P')",
 	/* viscondition */
 	"pg_catalog.pg_table_is_visible(c.oid)",
 	/* namespace */
@@ -488,7 +488,7 @@ static const SchemaQuery Query_for_list_of_tsvmf = {
 	/* catname */
 	"pg_catalog.pg_class c",
 	/* selcondition */
-	"c.relkind IN ('r', 'S', 'v', 'm', 'f')",
+	"c.relkind IN ('r', 'S', 'v', 'm', 'f', 'P')",
 	/* viscondition */
 	"pg_catalog.pg_table_is_visible(c.oid)",
 	/* namespace */
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index 270dd21..5a444d9 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/utils/builtins.h b/src/include/utils/builtins.h
index 8cebc86..351bfd3 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -722,6 +722,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/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index fb7a1b3..4fc8c06 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -413,3 +413,29 @@ SELECT connoinherit FROM pg_constraint WHERE conrelid = 'no_inh_con_parted'::reg
 (1 row)
 
 DROP TABLE no_inh_con_parted;
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+Partitioned table "public.describe_range_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition Key: PARTITION BY RANGE (((a + b)))
+
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+Partitioned table "public.describe_list_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition Key: PARTITION BY LIST (a)
+
+DROP TABLE describe_range_key, describe_list_key;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index cb17517..7c7a18a 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -404,3 +404,16 @@ CREATE TABLE no_inh_con_parted (
 ) PARTITION BY RANGE (a);
 SELECT connoinherit FROM pg_constraint WHERE conrelid = 'no_inh_con_parted'::regclass;
 DROP TABLE no_inh_con_parted;
+
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+DROP TABLE describe_range_key, describe_list_key;
-- 
1.7.1


--------------8C4C48E640631A327D3D2826
Content-Type: text/x-diff;
 name="0003-Catalog-and-DDL-for-partitions.patch"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
 filename="0003-Catalog-and-DDL-for-partitions.patch"



^ permalink  raw  reply  [nested|flat] 110+ messages in thread

* [PATCH 2/9] psql and pg_dump support for partitioned tables.
@ 2016-07-12 08:20  amit <[email protected]>
  0 siblings, 0 replies; 110+ messages in thread

From: amit @ 2016-07-12 08:20 UTC (permalink / raw)

Takes care of both the partition key deparse stuff and the new relkind.
---
 src/backend/utils/adt/ruleutils.c          |  140 ++++++++++++++++++++++++++++
 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/pg_proc.h              |    2 +
 src/include/utils/builtins.h               |    1 +
 src/test/regress/expected/create_table.out |   26 +++++
 src/test/regress/sql/create_table.sql      |   13 +++
 9 files changed, 260 insertions(+), 26 deletions(-)

diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 8a81d7a..03be202 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_table.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
@@ -315,6 +316,7 @@ static char *pg_get_indexdef_worker(Oid indexrelid, int colno,
 					   const Oid *excludeOps,
 					   bool attrsOnly, bool showTblSpc,
 					   int prettyFlags, bool missing_ok);
+static char *pg_get_partkeydef_worker(Oid relid, int prettyFlags);
 static char *pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 							int prettyFlags, bool missing_ok);
 static text *pg_get_expr_worker(text *expr, Oid relid, const char *relname,
@@ -1389,6 +1391,144 @@ 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 [ opclass_name ] [, ...])
+ */
+Datum
+pg_get_partkeydef(PG_FUNCTION_ARGS)
+{
+	Oid			relid = PG_GETARG_OID(0);
+	int			prettyFlags;
+
+	prettyFlags = PRETTYFLAG_INDENT;
+	PG_RETURN_TEXT_P(string_to_text(pg_get_partkeydef_worker(relid,
+									prettyFlags)));
+}
+
+/*
+ * Internal workhorse to decompile a partition key definition.
+ */
+static char *
+pg_get_partkeydef_worker(Oid relid, int prettyFlags)
+{
+	Form_pg_partitioned_table	form;
+	HeapTuple	tuple;
+	oidvector  *partclass;
+	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_table) GETSTRUCT(tuple);
+
+	Assert(form->partrelid == relid);
+
+	/* Must get partclass, and partexprs the hard way */
+	datum = SysCacheGetAttr(PARTEDRELID, tuple,
+							Anum_pg_partitioned_table_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_table_partexprbin))
+	{
+		Datum		exprsDatum;
+		bool		isnull;
+		char	   *exprsString;
+
+		exprsDatum = SysCacheGetAttr(PARTEDRELID, tuple,
+									 Anum_pg_partitioned_table_partexprbin, &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);
+
+	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,
+											0, 0);
+
+			appendStringInfoString(&buf, 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/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index ba9c276..c805a84 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -1253,9 +1253,10 @@ expand_table_name_patterns(Archive *fout,
 						  "SELECT c.oid"
 						  "\nFROM pg_catalog.pg_class c"
 		"\n     LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace"
-					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c')\n",
+					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c')\n",
 						  RELKIND_RELATION, RELKIND_SEQUENCE, RELKIND_VIEW,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 		processSQLNamePattern(GetConnection(fout), query, cell->val, true,
 							  false, "n.nspname", "c.relname", NULL,
 							  "pg_catalog.pg_table_is_visible(c.oid)");
@@ -2125,6 +2126,9 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo, bool oids)
 	/* Skip FOREIGN TABLEs (no data to dump) */
 	if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 		return;
+	/* Skip partitioned tables (data in partitions) */
+	if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+		return;
 
 	/* Don't dump data in unlogged tables, if so requested */
 	if (tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED &&
@@ -5204,6 +5208,7 @@ getTables(Archive *fout, int *numTables)
 	int			i_reloftype;
 	int			i_relpages;
 	int			i_changed_acl;
+	int			i_partkeydef;
 
 	/* Make sure we are in proper schema */
 	selectSourceSchema(fout, "pg_catalog");
@@ -5289,7 +5294,8 @@ getTables(Archive *fout, int *numTables)
 						  "OR %s IS NOT NULL "
 						  "OR %s IS NOT NULL"
 						  "))"
-						  "AS changed_acl "
+						  "AS changed_acl, "
+						  "CASE WHEN c.relkind = 'P' THEN pg_catalog.pg_get_partkeydef(c.oid) ELSE NULL END AS partkeydef "
 						  "FROM pg_class c "
 						  "LEFT JOIN pg_depend d ON "
 						  "(c.relkind = '%c' AND "
@@ -5301,7 +5307,7 @@ getTables(Archive *fout, int *numTables)
 						  "(c.oid = pip.objoid "
 						  "AND pip.classoid = 'pg_class'::regclass "
 						  "AND pip.objsubid = 0) "
-				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c') "
+				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c', '%c') "
 						  "ORDER BY c.oid",
 						  acl_subquery->data,
 						  racl_subquery->data,
@@ -5315,7 +5321,8 @@ getTables(Archive *fout, int *numTables)
 						  RELKIND_SEQUENCE,
 						  RELKIND_RELATION, RELKIND_SEQUENCE,
 						  RELKIND_VIEW, RELKIND_COMPOSITE_TYPE,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 
 		destroyPQExpBuffer(acl_subquery);
 		destroyPQExpBuffer(racl_subquery);
@@ -5877,6 +5884,7 @@ getTables(Archive *fout, int *numTables)
 	i_toastreloptions = PQfnumber(res, "toast_reloptions");
 	i_reloftype = PQfnumber(res, "reloftype");
 	i_changed_acl = PQfnumber(res, "changed_acl");
+	i_partkeydef = PQfnumber(res, "partkeydef");
 
 	if (dopt->lockWaitTimeout && fout->remoteVersion >= 70300)
 	{
@@ -5947,6 +5955,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 */
 
@@ -5991,7 +6000,9 @@ getTables(Archive *fout, int *numTables)
 		 * We only need to lock the table for certain components; see
 		 * pg_dump.h
 		 */
-		if (tblinfo[i].dobj.dump && tblinfo[i].relkind == RELKIND_RELATION &&
+		if (tblinfo[i].dobj.dump &&
+			(tblinfo[i].relkind == RELKIND_RELATION ||
+			 tblinfo->relkind == RELKIND_PARTITIONED_TABLE) &&
 			(tblinfo[i].dobj.dump & DUMP_COMPONENTS_REQUIRING_LOCK))
 		{
 			resetPQExpBuffer(query);
@@ -6093,7 +6104,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);
 
@@ -15446,6 +15460,9 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 				appendPQExpBufferChar(q, ')');
 			}
 
+			if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+				appendPQExpBuffer(q, "\nPARTITION BY %s", tbinfo->partkeydef);
+
 			if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 				appendPQExpBuffer(q, "\nSERVER %s", fmtId(srvname));
 		}
@@ -15506,6 +15523,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 		 */
 		if (dopt->binary_upgrade &&
 			(tbinfo->relkind == RELKIND_RELATION ||
+			 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 			 tbinfo->relkind == RELKIND_FOREIGN_TABLE))
 		{
 			for (j = 0; j < tbinfo->numatts; j++)
@@ -15524,7 +15542,8 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 					appendStringLiteralAH(q, fmtId(tbinfo->dobj.name), fout);
 					appendPQExpBufferStr(q, "::pg_catalog.regclass;\n");
 
-					if (tbinfo->relkind == RELKIND_RELATION)
+					if (tbinfo->relkind == RELKIND_RELATION ||
+						tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
 						appendPQExpBuffer(q, "ALTER TABLE ONLY %s ",
 										  fmtId(tbinfo->dobj.name));
 					else
@@ -15741,6 +15760,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 	 * dump properties we only have ALTER TABLE syntax for
 	 */
 	if ((tbinfo->relkind == RELKIND_RELATION ||
+		 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 		 tbinfo->relkind == RELKIND_MATVIEW) &&
 		tbinfo->relreplident != REPLICA_IDENTITY_DEFAULT)
 	{
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 2bfa2d9..0292859 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -310,6 +310,7 @@ typedef struct _tableInfo
 	bool	   *inhNotNull;		/* true if NOT NULL is inherited */
 	struct _attrDefInfo **attrdefs;		/* DEFAULT expressions */
 	struct _constraintInfo *checkexprs; /* CHECK constraints */
+	char	   *partkeydef;		/* partition key definition */
 
 	/*
 	 * Stuff computed only for dumpable tables.
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 6275a68..10d924a 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -861,6 +861,7 @@ permissionsList(const char *pattern)
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'S' THEN '%s'"
@@ -870,6 +871,7 @@ permissionsList(const char *pattern)
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("sequence"),
@@ -920,7 +922,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
@@ -1567,8 +1569,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)");
 	}
 
@@ -1633,6 +1635,14 @@ describeOneTableDetails(const char *schemaname,
 			printfPQExpBuffer(&title, _("Foreign table \"%s.%s\""),
 							  schemaname, relationname);
 			break;
+		case 'P':
+			if (tableinfo.relpersistence == 'u')
+				printfPQExpBuffer(&title, _("Unlogged table \"%s.%s\""),
+								  schemaname, relationname);
+			else
+				printfPQExpBuffer(&title, _("Table \"%s.%s\""),
+								  schemaname, relationname);
+			break;
 		default:
 			/* untranslated unknown relkind */
 			printfPQExpBuffer(&title, "?%c? \"%s.%s\"",
@@ -1646,8 +1656,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");
@@ -1667,12 +1677,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");
 	}
 
@@ -1772,7 +1782,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);
@@ -1780,14 +1790,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 (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 */
@@ -1926,7 +1955,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;
@@ -2485,7 +2514,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;
@@ -2696,7 +2725,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
@@ -3024,6 +3053,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'i' THEN '%s'"
@@ -3035,6 +3065,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("index"),
@@ -3079,7 +3110,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 50a45eb..8284a9c 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/pg_proc.h b/src/include/catalog/pg_proc.h
index e2d08ba..b45688b 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -1980,6 +1980,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/utils/builtins.h b/src/include/utils/builtins.h
index 2ae212a..e800647 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -729,6 +729,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/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 0af1422..91ba1be 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -412,3 +412,29 @@ CREATE TABLE no_inh_con_parted (
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
 ERROR:  cannot add NO INHERIT constraint to partitioned table "no_inh_con_parted"
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+Table "public.describe_range_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition Key: RANGE ((a + b))
+
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+Table "public.describe_list_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition Key: LIST (a)
+
+DROP TABLE describe_range_key, describe_list_key;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 418ac55..b75542d 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -406,3 +406,16 @@ CREATE TABLE no_inh_con_parted (
 	a int,
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
+
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+DROP TABLE describe_range_key, describe_list_key;
-- 
1.7.1


--------------D008BA677674C4836D7B65AD
Content-Type: text/x-diff;
 name="0003-Catalog-and-DDL-for-partitions-5.patch"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
 filename="0003-Catalog-and-DDL-for-partitions-5.patch"



^ permalink  raw  reply  [nested|flat] 110+ messages in thread

* [PATCH 2/9] psql and pg_dump support for partitioned tables.
@ 2016-07-12 08:20  amit <[email protected]>
  0 siblings, 0 replies; 110+ messages in thread

From: amit @ 2016-07-12 08:20 UTC (permalink / raw)

Takes care of both the partition key deparse stuff and the new relkind.
---
 src/backend/utils/adt/ruleutils.c          |  140 ++++++++++++++++++++++++++++
 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/pg_proc.h              |    2 +
 src/include/utils/builtins.h               |    1 +
 src/test/regress/expected/create_table.out |   26 +++++
 src/test/regress/sql/create_table.sql      |   13 +++
 9 files changed, 260 insertions(+), 26 deletions(-)

diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 8a81d7a..77ce807 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_table.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
@@ -315,6 +316,7 @@ static char *pg_get_indexdef_worker(Oid indexrelid, int colno,
 					   const Oid *excludeOps,
 					   bool attrsOnly, bool showTblSpc,
 					   int prettyFlags, bool missing_ok);
+static char *pg_get_partkeydef_worker(Oid relId, int prettyFlags);
 static char *pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 							int prettyFlags, bool missing_ok);
 static text *pg_get_expr_worker(text *expr, Oid relid, const char *relname,
@@ -1389,6 +1391,144 @@ 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 [ 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_table	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_table) GETSTRUCT(tuple);
+
+	Assert(form->partedrelid == relId);
+
+	/* Must get partclass, and partexprs the hard way */
+	datum = SysCacheGetAttr(PARTEDRELID, tuple,
+							Anum_pg_partitioned_table_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_table_partexprbin))
+	{
+		Datum		exprsDatum;
+		bool		isnull;
+		char	   *exprsString;
+
+		exprsDatum = SysCacheGetAttr(PARTEDRELID, tuple,
+									 Anum_pg_partitioned_table_partexprbin, &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);
+
+	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,
+											0, 0);
+
+			appendStringInfoString(&buf, 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/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index a5c2d09..c418ba7 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -1253,9 +1253,10 @@ expand_table_name_patterns(Archive *fout,
 						  "SELECT c.oid"
 						  "\nFROM pg_catalog.pg_class c"
 		"\n     LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace"
-					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c')\n",
+					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c')\n",
 						  RELKIND_RELATION, RELKIND_SEQUENCE, RELKIND_VIEW,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 		processSQLNamePattern(GetConnection(fout), query, cell->val, true,
 							  false, "n.nspname", "c.relname", NULL,
 							  "pg_catalog.pg_table_is_visible(c.oid)");
@@ -2117,6 +2118,9 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo, bool oids)
 	/* Skip FOREIGN TABLEs (no data to dump) */
 	if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 		return;
+	/* Skip partitioned tables (data in partitions) */
+	if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+		return;
 
 	/* Don't dump data in unlogged tables, if so requested */
 	if (tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED &&
@@ -5195,6 +5199,7 @@ getTables(Archive *fout, int *numTables)
 	int			i_reloftype;
 	int			i_relpages;
 	int			i_changed_acl;
+	int			i_partkeydef;
 
 	/* Make sure we are in proper schema */
 	selectSourceSchema(fout, "pg_catalog");
@@ -5280,7 +5285,8 @@ getTables(Archive *fout, int *numTables)
 						  "OR %s IS NOT NULL "
 						  "OR %s IS NOT NULL"
 						  "))"
-						  "AS changed_acl "
+						  "AS changed_acl, "
+						  "CASE WHEN c.relkind = 'P' THEN pg_catalog.pg_get_partkeydef(c.oid) ELSE NULL END AS partkeydef "
 						  "FROM pg_class c "
 						  "LEFT JOIN pg_depend d ON "
 						  "(c.relkind = '%c' AND "
@@ -5292,7 +5298,7 @@ getTables(Archive *fout, int *numTables)
 						  "(c.oid = pip.objoid "
 						  "AND pip.classoid = 'pg_class'::regclass "
 						  "AND pip.objsubid = 0) "
-				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c') "
+				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c', '%c') "
 						  "ORDER BY c.oid",
 						  acl_subquery->data,
 						  racl_subquery->data,
@@ -5306,7 +5312,8 @@ getTables(Archive *fout, int *numTables)
 						  RELKIND_SEQUENCE,
 						  RELKIND_RELATION, RELKIND_SEQUENCE,
 						  RELKIND_VIEW, RELKIND_COMPOSITE_TYPE,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 
 		destroyPQExpBuffer(acl_subquery);
 		destroyPQExpBuffer(racl_subquery);
@@ -5868,6 +5875,7 @@ getTables(Archive *fout, int *numTables)
 	i_toastreloptions = PQfnumber(res, "toast_reloptions");
 	i_reloftype = PQfnumber(res, "reloftype");
 	i_changed_acl = PQfnumber(res, "changed_acl");
+	i_partkeydef = PQfnumber(res, "partkeydef");
 
 	if (dopt->lockWaitTimeout && fout->remoteVersion >= 70300)
 	{
@@ -5938,6 +5946,7 @@ getTables(Archive *fout, int *numTables)
 		else
 			tblinfo[i].checkoption = pg_strdup(PQgetvalue(res, i, i_checkoption));
 		tblinfo[i].toast_reloptions = pg_strdup(PQgetvalue(res, i, i_toastreloptions));
+		tblinfo[i].partkeydef = pg_strdup(PQgetvalue(res, i, i_partkeydef));
 
 		/* other fields were zeroed above */
 
@@ -5982,7 +5991,9 @@ getTables(Archive *fout, int *numTables)
 		 * We only need to lock the table for certain components; see
 		 * pg_dump.h
 		 */
-		if (tblinfo[i].dobj.dump && tblinfo[i].relkind == RELKIND_RELATION &&
+		if (tblinfo[i].dobj.dump &&
+			(tblinfo[i].relkind == RELKIND_RELATION ||
+			 tblinfo->relkind == RELKIND_PARTITIONED_TABLE) &&
 			(tblinfo[i].dobj.dump & DUMP_COMPONENTS_REQUIRING_LOCK))
 		{
 			resetPQExpBuffer(query);
@@ -6084,7 +6095,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);
 
@@ -15437,6 +15451,9 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 				appendPQExpBufferChar(q, ')');
 			}
 
+			if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+				appendPQExpBuffer(q, "\nPARTITION BY %s", tbinfo->partkeydef);
+
 			if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 				appendPQExpBuffer(q, "\nSERVER %s", fmtId(srvname));
 		}
@@ -15497,6 +15514,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 		 */
 		if (dopt->binary_upgrade &&
 			(tbinfo->relkind == RELKIND_RELATION ||
+			 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 			 tbinfo->relkind == RELKIND_FOREIGN_TABLE))
 		{
 			for (j = 0; j < tbinfo->numatts; j++)
@@ -15515,7 +15533,8 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 					appendStringLiteralAH(q, fmtId(tbinfo->dobj.name), fout);
 					appendPQExpBufferStr(q, "::pg_catalog.regclass;\n");
 
-					if (tbinfo->relkind == RELKIND_RELATION)
+					if (tbinfo->relkind == RELKIND_RELATION ||
+						tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
 						appendPQExpBuffer(q, "ALTER TABLE ONLY %s ",
 										  fmtId(tbinfo->dobj.name));
 					else
@@ -15732,6 +15751,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 	 * dump properties we only have ALTER TABLE syntax for
 	 */
 	if ((tbinfo->relkind == RELKIND_RELATION ||
+		 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 		 tbinfo->relkind == RELKIND_MATVIEW) &&
 		tbinfo->relreplident != REPLICA_IDENTITY_DEFAULT)
 	{
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 2bfa2d9..0292859 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -310,6 +310,7 @@ typedef struct _tableInfo
 	bool	   *inhNotNull;		/* true if NOT NULL is inherited */
 	struct _attrDefInfo **attrdefs;		/* DEFAULT expressions */
 	struct _constraintInfo *checkexprs; /* CHECK constraints */
+	char	   *partkeydef;		/* partition key definition */
 
 	/*
 	 * Stuff computed only for dumpable tables.
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 6275a68..10d924a 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -861,6 +861,7 @@ permissionsList(const char *pattern)
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'S' THEN '%s'"
@@ -870,6 +871,7 @@ permissionsList(const char *pattern)
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("sequence"),
@@ -920,7 +922,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
@@ -1567,8 +1569,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)");
 	}
 
@@ -1633,6 +1635,14 @@ describeOneTableDetails(const char *schemaname,
 			printfPQExpBuffer(&title, _("Foreign table \"%s.%s\""),
 							  schemaname, relationname);
 			break;
+		case 'P':
+			if (tableinfo.relpersistence == 'u')
+				printfPQExpBuffer(&title, _("Unlogged table \"%s.%s\""),
+								  schemaname, relationname);
+			else
+				printfPQExpBuffer(&title, _("Table \"%s.%s\""),
+								  schemaname, relationname);
+			break;
 		default:
 			/* untranslated unknown relkind */
 			printfPQExpBuffer(&title, "?%c? \"%s.%s\"",
@@ -1646,8 +1656,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");
@@ -1667,12 +1677,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");
 	}
 
@@ -1772,7 +1782,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);
@@ -1780,14 +1790,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 (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 */
@@ -1926,7 +1955,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;
@@ -2485,7 +2514,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;
@@ -2696,7 +2725,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
@@ -3024,6 +3053,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'i' THEN '%s'"
@@ -3035,6 +3065,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("index"),
@@ -3079,7 +3110,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 1345e4e..4686cd1 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/pg_proc.h b/src/include/catalog/pg_proc.h
index e2d08ba..b45688b 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -1980,6 +1980,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/utils/builtins.h b/src/include/utils/builtins.h
index 2ae212a..e800647 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -729,6 +729,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/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 708232d..2fec847 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -408,3 +408,29 @@ CREATE TABLE no_inh_con_parted (
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
 ERROR:  cannot add NO INHERIT constraint to partitioned table "no_inh_con_parted"
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+Table "public.describe_range_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition Key: RANGE ((a + b))
+
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+Table "public.describe_list_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition Key: LIST (a)
+
+DROP TABLE describe_range_key, describe_list_key;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 6e4a8be..4dd6a0a 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -402,3 +402,16 @@ CREATE TABLE no_inh_con_parted (
 	a int,
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
+
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+DROP TABLE describe_range_key, describe_list_key;
-- 
1.7.1


--------------954458C17BBEB1BF6FC758E7
Content-Type: text/x-diff;
 name="0003-Catalog-and-DDL-for-partitions-2.patch"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
 filename="0003-Catalog-and-DDL-for-partitions-2.patch"



^ permalink  raw  reply  [nested|flat] 110+ messages in thread

* [PATCH 2/9] psql and pg_dump support for partitioned tables.
@ 2016-07-12 08:20  amit <[email protected]>
  0 siblings, 0 replies; 110+ messages in thread

From: amit @ 2016-07-12 08:20 UTC (permalink / raw)

Takes care of both the partition key deparse stuff and the new relkind.
---
 src/backend/utils/adt/ruleutils.c          |  140 ++++++++++++++++++++++++++++
 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/pg_proc.h              |    2 +
 src/include/utils/builtins.h               |    1 +
 src/test/regress/expected/create_table.out |   26 +++++
 src/test/regress/sql/create_table.sql      |   13 +++
 9 files changed, 260 insertions(+), 26 deletions(-)

diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 8a81d7a..77ce807 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_table.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
@@ -315,6 +316,7 @@ static char *pg_get_indexdef_worker(Oid indexrelid, int colno,
 					   const Oid *excludeOps,
 					   bool attrsOnly, bool showTblSpc,
 					   int prettyFlags, bool missing_ok);
+static char *pg_get_partkeydef_worker(Oid relId, int prettyFlags);
 static char *pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 							int prettyFlags, bool missing_ok);
 static text *pg_get_expr_worker(text *expr, Oid relid, const char *relname,
@@ -1389,6 +1391,144 @@ 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 [ 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_table	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_table) GETSTRUCT(tuple);
+
+	Assert(form->partedrelid == relId);
+
+	/* Must get partclass, and partexprs the hard way */
+	datum = SysCacheGetAttr(PARTEDRELID, tuple,
+							Anum_pg_partitioned_table_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_table_partexprbin))
+	{
+		Datum		exprsDatum;
+		bool		isnull;
+		char	   *exprsString;
+
+		exprsDatum = SysCacheGetAttr(PARTEDRELID, tuple,
+									 Anum_pg_partitioned_table_partexprbin, &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);
+
+	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,
+											0, 0);
+
+			appendStringInfoString(&buf, 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/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index a5c2d09..c418ba7 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -1253,9 +1253,10 @@ expand_table_name_patterns(Archive *fout,
 						  "SELECT c.oid"
 						  "\nFROM pg_catalog.pg_class c"
 		"\n     LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace"
-					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c')\n",
+					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c')\n",
 						  RELKIND_RELATION, RELKIND_SEQUENCE, RELKIND_VIEW,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 		processSQLNamePattern(GetConnection(fout), query, cell->val, true,
 							  false, "n.nspname", "c.relname", NULL,
 							  "pg_catalog.pg_table_is_visible(c.oid)");
@@ -2117,6 +2118,9 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo, bool oids)
 	/* Skip FOREIGN TABLEs (no data to dump) */
 	if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 		return;
+	/* Skip partitioned tables (data in partitions) */
+	if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+		return;
 
 	/* Don't dump data in unlogged tables, if so requested */
 	if (tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED &&
@@ -5195,6 +5199,7 @@ getTables(Archive *fout, int *numTables)
 	int			i_reloftype;
 	int			i_relpages;
 	int			i_changed_acl;
+	int			i_partkeydef;
 
 	/* Make sure we are in proper schema */
 	selectSourceSchema(fout, "pg_catalog");
@@ -5280,7 +5285,8 @@ getTables(Archive *fout, int *numTables)
 						  "OR %s IS NOT NULL "
 						  "OR %s IS NOT NULL"
 						  "))"
-						  "AS changed_acl "
+						  "AS changed_acl, "
+						  "CASE WHEN c.relkind = 'P' THEN pg_catalog.pg_get_partkeydef(c.oid) ELSE NULL END AS partkeydef "
 						  "FROM pg_class c "
 						  "LEFT JOIN pg_depend d ON "
 						  "(c.relkind = '%c' AND "
@@ -5292,7 +5298,7 @@ getTables(Archive *fout, int *numTables)
 						  "(c.oid = pip.objoid "
 						  "AND pip.classoid = 'pg_class'::regclass "
 						  "AND pip.objsubid = 0) "
-				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c') "
+				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c', '%c') "
 						  "ORDER BY c.oid",
 						  acl_subquery->data,
 						  racl_subquery->data,
@@ -5306,7 +5312,8 @@ getTables(Archive *fout, int *numTables)
 						  RELKIND_SEQUENCE,
 						  RELKIND_RELATION, RELKIND_SEQUENCE,
 						  RELKIND_VIEW, RELKIND_COMPOSITE_TYPE,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 
 		destroyPQExpBuffer(acl_subquery);
 		destroyPQExpBuffer(racl_subquery);
@@ -5868,6 +5875,7 @@ getTables(Archive *fout, int *numTables)
 	i_toastreloptions = PQfnumber(res, "toast_reloptions");
 	i_reloftype = PQfnumber(res, "reloftype");
 	i_changed_acl = PQfnumber(res, "changed_acl");
+	i_partkeydef = PQfnumber(res, "partkeydef");
 
 	if (dopt->lockWaitTimeout && fout->remoteVersion >= 70300)
 	{
@@ -5938,6 +5946,7 @@ getTables(Archive *fout, int *numTables)
 		else
 			tblinfo[i].checkoption = pg_strdup(PQgetvalue(res, i, i_checkoption));
 		tblinfo[i].toast_reloptions = pg_strdup(PQgetvalue(res, i, i_toastreloptions));
+		tblinfo[i].partkeydef = pg_strdup(PQgetvalue(res, i, i_partkeydef));
 
 		/* other fields were zeroed above */
 
@@ -5982,7 +5991,9 @@ getTables(Archive *fout, int *numTables)
 		 * We only need to lock the table for certain components; see
 		 * pg_dump.h
 		 */
-		if (tblinfo[i].dobj.dump && tblinfo[i].relkind == RELKIND_RELATION &&
+		if (tblinfo[i].dobj.dump &&
+			(tblinfo[i].relkind == RELKIND_RELATION ||
+			 tblinfo->relkind == RELKIND_PARTITIONED_TABLE) &&
 			(tblinfo[i].dobj.dump & DUMP_COMPONENTS_REQUIRING_LOCK))
 		{
 			resetPQExpBuffer(query);
@@ -6084,7 +6095,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);
 
@@ -15437,6 +15451,9 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 				appendPQExpBufferChar(q, ')');
 			}
 
+			if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+				appendPQExpBuffer(q, "\nPARTITION BY %s", tbinfo->partkeydef);
+
 			if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 				appendPQExpBuffer(q, "\nSERVER %s", fmtId(srvname));
 		}
@@ -15497,6 +15514,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 		 */
 		if (dopt->binary_upgrade &&
 			(tbinfo->relkind == RELKIND_RELATION ||
+			 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 			 tbinfo->relkind == RELKIND_FOREIGN_TABLE))
 		{
 			for (j = 0; j < tbinfo->numatts; j++)
@@ -15515,7 +15533,8 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 					appendStringLiteralAH(q, fmtId(tbinfo->dobj.name), fout);
 					appendPQExpBufferStr(q, "::pg_catalog.regclass;\n");
 
-					if (tbinfo->relkind == RELKIND_RELATION)
+					if (tbinfo->relkind == RELKIND_RELATION ||
+						tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
 						appendPQExpBuffer(q, "ALTER TABLE ONLY %s ",
 										  fmtId(tbinfo->dobj.name));
 					else
@@ -15732,6 +15751,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 	 * dump properties we only have ALTER TABLE syntax for
 	 */
 	if ((tbinfo->relkind == RELKIND_RELATION ||
+		 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 		 tbinfo->relkind == RELKIND_MATVIEW) &&
 		tbinfo->relreplident != REPLICA_IDENTITY_DEFAULT)
 	{
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 2bfa2d9..0292859 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -310,6 +310,7 @@ typedef struct _tableInfo
 	bool	   *inhNotNull;		/* true if NOT NULL is inherited */
 	struct _attrDefInfo **attrdefs;		/* DEFAULT expressions */
 	struct _constraintInfo *checkexprs; /* CHECK constraints */
+	char	   *partkeydef;		/* partition key definition */
 
 	/*
 	 * Stuff computed only for dumpable tables.
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 6275a68..10d924a 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -861,6 +861,7 @@ permissionsList(const char *pattern)
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'S' THEN '%s'"
@@ -870,6 +871,7 @@ permissionsList(const char *pattern)
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("sequence"),
@@ -920,7 +922,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
@@ -1567,8 +1569,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)");
 	}
 
@@ -1633,6 +1635,14 @@ describeOneTableDetails(const char *schemaname,
 			printfPQExpBuffer(&title, _("Foreign table \"%s.%s\""),
 							  schemaname, relationname);
 			break;
+		case 'P':
+			if (tableinfo.relpersistence == 'u')
+				printfPQExpBuffer(&title, _("Unlogged table \"%s.%s\""),
+								  schemaname, relationname);
+			else
+				printfPQExpBuffer(&title, _("Table \"%s.%s\""),
+								  schemaname, relationname);
+			break;
 		default:
 			/* untranslated unknown relkind */
 			printfPQExpBuffer(&title, "?%c? \"%s.%s\"",
@@ -1646,8 +1656,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");
@@ -1667,12 +1677,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");
 	}
 
@@ -1772,7 +1782,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);
@@ -1780,14 +1790,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 (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 */
@@ -1926,7 +1955,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;
@@ -2485,7 +2514,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;
@@ -2696,7 +2725,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
@@ -3024,6 +3053,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'i' THEN '%s'"
@@ -3035,6 +3065,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("index"),
@@ -3079,7 +3110,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 019f75a..5e5c370 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/pg_proc.h b/src/include/catalog/pg_proc.h
index e2d08ba..b45688b 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -1980,6 +1980,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/utils/builtins.h b/src/include/utils/builtins.h
index 2ae212a..e800647 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -729,6 +729,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/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 708232d..2fec847 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -408,3 +408,29 @@ CREATE TABLE no_inh_con_parted (
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
 ERROR:  cannot add NO INHERIT constraint to partitioned table "no_inh_con_parted"
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+Table "public.describe_range_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition Key: RANGE ((a + b))
+
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+Table "public.describe_list_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition Key: LIST (a)
+
+DROP TABLE describe_range_key, describe_list_key;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 6e4a8be..4dd6a0a 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -402,3 +402,16 @@ CREATE TABLE no_inh_con_parted (
 	a int,
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
+
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+DROP TABLE describe_range_key, describe_list_key;
-- 
1.7.1


--------------6EB5B318125CB7DB1A4FD229
Content-Type: text/x-diff;
 name="0003-Catalog-and-DDL-for-partitions-3.patch"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
 filename="0003-Catalog-and-DDL-for-partitions-3.patch"



^ permalink  raw  reply  [nested|flat] 110+ messages in thread

* [PATCH 2/9] psql and pg_dump support for partitioned tables.
@ 2016-07-12 08:20  amit <[email protected]>
  0 siblings, 0 replies; 110+ messages in thread

From: amit @ 2016-07-12 08:20 UTC (permalink / raw)

Takes care of both the partition key deparse stuff and the new relkind.
---
 src/backend/utils/adt/ruleutils.c          |  140 ++++++++++++++++++++++++++++
 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/pg_proc.h              |    2 +
 src/include/utils/builtins.h               |    1 +
 src/test/regress/expected/create_table.out |   26 +++++
 src/test/regress/sql/create_table.sql      |   13 +++
 9 files changed, 260 insertions(+), 26 deletions(-)

diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 8a81d7a..03be202 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_table.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
@@ -315,6 +316,7 @@ static char *pg_get_indexdef_worker(Oid indexrelid, int colno,
 					   const Oid *excludeOps,
 					   bool attrsOnly, bool showTblSpc,
 					   int prettyFlags, bool missing_ok);
+static char *pg_get_partkeydef_worker(Oid relid, int prettyFlags);
 static char *pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 							int prettyFlags, bool missing_ok);
 static text *pg_get_expr_worker(text *expr, Oid relid, const char *relname,
@@ -1389,6 +1391,144 @@ 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 [ opclass_name ] [, ...])
+ */
+Datum
+pg_get_partkeydef(PG_FUNCTION_ARGS)
+{
+	Oid			relid = PG_GETARG_OID(0);
+	int			prettyFlags;
+
+	prettyFlags = PRETTYFLAG_INDENT;
+	PG_RETURN_TEXT_P(string_to_text(pg_get_partkeydef_worker(relid,
+									prettyFlags)));
+}
+
+/*
+ * Internal workhorse to decompile a partition key definition.
+ */
+static char *
+pg_get_partkeydef_worker(Oid relid, int prettyFlags)
+{
+	Form_pg_partitioned_table	form;
+	HeapTuple	tuple;
+	oidvector  *partclass;
+	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_table) GETSTRUCT(tuple);
+
+	Assert(form->partrelid == relid);
+
+	/* Must get partclass, and partexprs the hard way */
+	datum = SysCacheGetAttr(PARTEDRELID, tuple,
+							Anum_pg_partitioned_table_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_table_partexprbin))
+	{
+		Datum		exprsDatum;
+		bool		isnull;
+		char	   *exprsString;
+
+		exprsDatum = SysCacheGetAttr(PARTEDRELID, tuple,
+									 Anum_pg_partitioned_table_partexprbin, &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);
+
+	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,
+											0, 0);
+
+			appendStringInfoString(&buf, 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/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index ba9c276..c805a84 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -1253,9 +1253,10 @@ expand_table_name_patterns(Archive *fout,
 						  "SELECT c.oid"
 						  "\nFROM pg_catalog.pg_class c"
 		"\n     LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace"
-					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c')\n",
+					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c')\n",
 						  RELKIND_RELATION, RELKIND_SEQUENCE, RELKIND_VIEW,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 		processSQLNamePattern(GetConnection(fout), query, cell->val, true,
 							  false, "n.nspname", "c.relname", NULL,
 							  "pg_catalog.pg_table_is_visible(c.oid)");
@@ -2125,6 +2126,9 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo, bool oids)
 	/* Skip FOREIGN TABLEs (no data to dump) */
 	if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 		return;
+	/* Skip partitioned tables (data in partitions) */
+	if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+		return;
 
 	/* Don't dump data in unlogged tables, if so requested */
 	if (tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED &&
@@ -5204,6 +5208,7 @@ getTables(Archive *fout, int *numTables)
 	int			i_reloftype;
 	int			i_relpages;
 	int			i_changed_acl;
+	int			i_partkeydef;
 
 	/* Make sure we are in proper schema */
 	selectSourceSchema(fout, "pg_catalog");
@@ -5289,7 +5294,8 @@ getTables(Archive *fout, int *numTables)
 						  "OR %s IS NOT NULL "
 						  "OR %s IS NOT NULL"
 						  "))"
-						  "AS changed_acl "
+						  "AS changed_acl, "
+						  "CASE WHEN c.relkind = 'P' THEN pg_catalog.pg_get_partkeydef(c.oid) ELSE NULL END AS partkeydef "
 						  "FROM pg_class c "
 						  "LEFT JOIN pg_depend d ON "
 						  "(c.relkind = '%c' AND "
@@ -5301,7 +5307,7 @@ getTables(Archive *fout, int *numTables)
 						  "(c.oid = pip.objoid "
 						  "AND pip.classoid = 'pg_class'::regclass "
 						  "AND pip.objsubid = 0) "
-				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c') "
+				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c', '%c') "
 						  "ORDER BY c.oid",
 						  acl_subquery->data,
 						  racl_subquery->data,
@@ -5315,7 +5321,8 @@ getTables(Archive *fout, int *numTables)
 						  RELKIND_SEQUENCE,
 						  RELKIND_RELATION, RELKIND_SEQUENCE,
 						  RELKIND_VIEW, RELKIND_COMPOSITE_TYPE,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 
 		destroyPQExpBuffer(acl_subquery);
 		destroyPQExpBuffer(racl_subquery);
@@ -5877,6 +5884,7 @@ getTables(Archive *fout, int *numTables)
 	i_toastreloptions = PQfnumber(res, "toast_reloptions");
 	i_reloftype = PQfnumber(res, "reloftype");
 	i_changed_acl = PQfnumber(res, "changed_acl");
+	i_partkeydef = PQfnumber(res, "partkeydef");
 
 	if (dopt->lockWaitTimeout && fout->remoteVersion >= 70300)
 	{
@@ -5947,6 +5955,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 */
 
@@ -5991,7 +6000,9 @@ getTables(Archive *fout, int *numTables)
 		 * We only need to lock the table for certain components; see
 		 * pg_dump.h
 		 */
-		if (tblinfo[i].dobj.dump && tblinfo[i].relkind == RELKIND_RELATION &&
+		if (tblinfo[i].dobj.dump &&
+			(tblinfo[i].relkind == RELKIND_RELATION ||
+			 tblinfo->relkind == RELKIND_PARTITIONED_TABLE) &&
 			(tblinfo[i].dobj.dump & DUMP_COMPONENTS_REQUIRING_LOCK))
 		{
 			resetPQExpBuffer(query);
@@ -6093,7 +6104,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);
 
@@ -15446,6 +15460,9 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 				appendPQExpBufferChar(q, ')');
 			}
 
+			if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+				appendPQExpBuffer(q, "\nPARTITION BY %s", tbinfo->partkeydef);
+
 			if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 				appendPQExpBuffer(q, "\nSERVER %s", fmtId(srvname));
 		}
@@ -15506,6 +15523,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 		 */
 		if (dopt->binary_upgrade &&
 			(tbinfo->relkind == RELKIND_RELATION ||
+			 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 			 tbinfo->relkind == RELKIND_FOREIGN_TABLE))
 		{
 			for (j = 0; j < tbinfo->numatts; j++)
@@ -15524,7 +15542,8 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 					appendStringLiteralAH(q, fmtId(tbinfo->dobj.name), fout);
 					appendPQExpBufferStr(q, "::pg_catalog.regclass;\n");
 
-					if (tbinfo->relkind == RELKIND_RELATION)
+					if (tbinfo->relkind == RELKIND_RELATION ||
+						tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
 						appendPQExpBuffer(q, "ALTER TABLE ONLY %s ",
 										  fmtId(tbinfo->dobj.name));
 					else
@@ -15741,6 +15760,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 	 * dump properties we only have ALTER TABLE syntax for
 	 */
 	if ((tbinfo->relkind == RELKIND_RELATION ||
+		 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 		 tbinfo->relkind == RELKIND_MATVIEW) &&
 		tbinfo->relreplident != REPLICA_IDENTITY_DEFAULT)
 	{
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 2bfa2d9..0292859 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -310,6 +310,7 @@ typedef struct _tableInfo
 	bool	   *inhNotNull;		/* true if NOT NULL is inherited */
 	struct _attrDefInfo **attrdefs;		/* DEFAULT expressions */
 	struct _constraintInfo *checkexprs; /* CHECK constraints */
+	char	   *partkeydef;		/* partition key definition */
 
 	/*
 	 * Stuff computed only for dumpable tables.
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 6275a68..10d924a 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -861,6 +861,7 @@ permissionsList(const char *pattern)
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'S' THEN '%s'"
@@ -870,6 +871,7 @@ permissionsList(const char *pattern)
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("sequence"),
@@ -920,7 +922,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
@@ -1567,8 +1569,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)");
 	}
 
@@ -1633,6 +1635,14 @@ describeOneTableDetails(const char *schemaname,
 			printfPQExpBuffer(&title, _("Foreign table \"%s.%s\""),
 							  schemaname, relationname);
 			break;
+		case 'P':
+			if (tableinfo.relpersistence == 'u')
+				printfPQExpBuffer(&title, _("Unlogged table \"%s.%s\""),
+								  schemaname, relationname);
+			else
+				printfPQExpBuffer(&title, _("Table \"%s.%s\""),
+								  schemaname, relationname);
+			break;
 		default:
 			/* untranslated unknown relkind */
 			printfPQExpBuffer(&title, "?%c? \"%s.%s\"",
@@ -1646,8 +1656,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");
@@ -1667,12 +1677,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");
 	}
 
@@ -1772,7 +1782,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);
@@ -1780,14 +1790,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 (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 */
@@ -1926,7 +1955,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;
@@ -2485,7 +2514,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;
@@ -2696,7 +2725,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
@@ -3024,6 +3053,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'i' THEN '%s'"
@@ -3035,6 +3065,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("index"),
@@ -3079,7 +3110,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 019f75a..5e5c370 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/pg_proc.h b/src/include/catalog/pg_proc.h
index e2d08ba..b45688b 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -1980,6 +1980,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/utils/builtins.h b/src/include/utils/builtins.h
index 2ae212a..e800647 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -729,6 +729,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/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 708232d..2fec847 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -408,3 +408,29 @@ CREATE TABLE no_inh_con_parted (
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
 ERROR:  cannot add NO INHERIT constraint to partitioned table "no_inh_con_parted"
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+Table "public.describe_range_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition Key: RANGE ((a + b))
+
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+Table "public.describe_list_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition Key: LIST (a)
+
+DROP TABLE describe_range_key, describe_list_key;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 6e4a8be..4dd6a0a 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -402,3 +402,16 @@ CREATE TABLE no_inh_con_parted (
 	a int,
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
+
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+DROP TABLE describe_range_key, describe_list_key;
-- 
1.7.1


--------------4012470FC48539D6F6714CEA
Content-Type: text/x-diff;
 name="0003-Catalog-and-DDL-for-partitions-4.patch"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
 filename="0003-Catalog-and-DDL-for-partitions-4.patch"



^ permalink  raw  reply  [nested|flat] 110+ messages in thread

* [PATCH 2/9] psql and pg_dump support for partitioned tables.
@ 2016-07-12 08:20  amit <[email protected]>
  0 siblings, 0 replies; 110+ messages in thread

From: amit @ 2016-07-12 08:20 UTC (permalink / raw)

Takes care of both the partition key deparse stuff and the new relkind.
---
 src/backend/utils/adt/ruleutils.c          |  140 ++++++++++++++++++++++++++++
 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/pg_proc.h              |    2 +
 src/include/utils/builtins.h               |    1 +
 src/test/regress/expected/create_table.out |   26 +++++
 src/test/regress/sql/create_table.sql      |   13 +++
 9 files changed, 260 insertions(+), 26 deletions(-)

diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 8a81d7a..77ce807 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_table.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
@@ -315,6 +316,7 @@ static char *pg_get_indexdef_worker(Oid indexrelid, int colno,
 					   const Oid *excludeOps,
 					   bool attrsOnly, bool showTblSpc,
 					   int prettyFlags, bool missing_ok);
+static char *pg_get_partkeydef_worker(Oid relId, int prettyFlags);
 static char *pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 							int prettyFlags, bool missing_ok);
 static text *pg_get_expr_worker(text *expr, Oid relid, const char *relname,
@@ -1389,6 +1391,144 @@ 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 [ 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_table	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_table) GETSTRUCT(tuple);
+
+	Assert(form->partedrelid == relId);
+
+	/* Must get partclass, and partexprs the hard way */
+	datum = SysCacheGetAttr(PARTEDRELID, tuple,
+							Anum_pg_partitioned_table_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_table_partexprbin))
+	{
+		Datum		exprsDatum;
+		bool		isnull;
+		char	   *exprsString;
+
+		exprsDatum = SysCacheGetAttr(PARTEDRELID, tuple,
+									 Anum_pg_partitioned_table_partexprbin, &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);
+
+	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,
+											0, 0);
+
+			appendStringInfoString(&buf, 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/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index a5c2d09..c418ba7 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -1253,9 +1253,10 @@ expand_table_name_patterns(Archive *fout,
 						  "SELECT c.oid"
 						  "\nFROM pg_catalog.pg_class c"
 		"\n     LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace"
-					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c')\n",
+					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c')\n",
 						  RELKIND_RELATION, RELKIND_SEQUENCE, RELKIND_VIEW,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 		processSQLNamePattern(GetConnection(fout), query, cell->val, true,
 							  false, "n.nspname", "c.relname", NULL,
 							  "pg_catalog.pg_table_is_visible(c.oid)");
@@ -2117,6 +2118,9 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo, bool oids)
 	/* Skip FOREIGN TABLEs (no data to dump) */
 	if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 		return;
+	/* Skip partitioned tables (data in partitions) */
+	if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+		return;
 
 	/* Don't dump data in unlogged tables, if so requested */
 	if (tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED &&
@@ -5195,6 +5199,7 @@ getTables(Archive *fout, int *numTables)
 	int			i_reloftype;
 	int			i_relpages;
 	int			i_changed_acl;
+	int			i_partkeydef;
 
 	/* Make sure we are in proper schema */
 	selectSourceSchema(fout, "pg_catalog");
@@ -5280,7 +5285,8 @@ getTables(Archive *fout, int *numTables)
 						  "OR %s IS NOT NULL "
 						  "OR %s IS NOT NULL"
 						  "))"
-						  "AS changed_acl "
+						  "AS changed_acl, "
+						  "CASE WHEN c.relkind = 'P' THEN pg_catalog.pg_get_partkeydef(c.oid) ELSE NULL END AS partkeydef "
 						  "FROM pg_class c "
 						  "LEFT JOIN pg_depend d ON "
 						  "(c.relkind = '%c' AND "
@@ -5292,7 +5298,7 @@ getTables(Archive *fout, int *numTables)
 						  "(c.oid = pip.objoid "
 						  "AND pip.classoid = 'pg_class'::regclass "
 						  "AND pip.objsubid = 0) "
-				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c') "
+				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c', '%c') "
 						  "ORDER BY c.oid",
 						  acl_subquery->data,
 						  racl_subquery->data,
@@ -5306,7 +5312,8 @@ getTables(Archive *fout, int *numTables)
 						  RELKIND_SEQUENCE,
 						  RELKIND_RELATION, RELKIND_SEQUENCE,
 						  RELKIND_VIEW, RELKIND_COMPOSITE_TYPE,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 
 		destroyPQExpBuffer(acl_subquery);
 		destroyPQExpBuffer(racl_subquery);
@@ -5868,6 +5875,7 @@ getTables(Archive *fout, int *numTables)
 	i_toastreloptions = PQfnumber(res, "toast_reloptions");
 	i_reloftype = PQfnumber(res, "reloftype");
 	i_changed_acl = PQfnumber(res, "changed_acl");
+	i_partkeydef = PQfnumber(res, "partkeydef");
 
 	if (dopt->lockWaitTimeout && fout->remoteVersion >= 70300)
 	{
@@ -5938,6 +5946,7 @@ getTables(Archive *fout, int *numTables)
 		else
 			tblinfo[i].checkoption = pg_strdup(PQgetvalue(res, i, i_checkoption));
 		tblinfo[i].toast_reloptions = pg_strdup(PQgetvalue(res, i, i_toastreloptions));
+		tblinfo[i].partkeydef = pg_strdup(PQgetvalue(res, i, i_partkeydef));
 
 		/* other fields were zeroed above */
 
@@ -5982,7 +5991,9 @@ getTables(Archive *fout, int *numTables)
 		 * We only need to lock the table for certain components; see
 		 * pg_dump.h
 		 */
-		if (tblinfo[i].dobj.dump && tblinfo[i].relkind == RELKIND_RELATION &&
+		if (tblinfo[i].dobj.dump &&
+			(tblinfo[i].relkind == RELKIND_RELATION ||
+			 tblinfo->relkind == RELKIND_PARTITIONED_TABLE) &&
 			(tblinfo[i].dobj.dump & DUMP_COMPONENTS_REQUIRING_LOCK))
 		{
 			resetPQExpBuffer(query);
@@ -6084,7 +6095,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);
 
@@ -15437,6 +15451,9 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 				appendPQExpBufferChar(q, ')');
 			}
 
+			if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+				appendPQExpBuffer(q, "\nPARTITION BY %s", tbinfo->partkeydef);
+
 			if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 				appendPQExpBuffer(q, "\nSERVER %s", fmtId(srvname));
 		}
@@ -15497,6 +15514,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 		 */
 		if (dopt->binary_upgrade &&
 			(tbinfo->relkind == RELKIND_RELATION ||
+			 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 			 tbinfo->relkind == RELKIND_FOREIGN_TABLE))
 		{
 			for (j = 0; j < tbinfo->numatts; j++)
@@ -15515,7 +15533,8 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 					appendStringLiteralAH(q, fmtId(tbinfo->dobj.name), fout);
 					appendPQExpBufferStr(q, "::pg_catalog.regclass;\n");
 
-					if (tbinfo->relkind == RELKIND_RELATION)
+					if (tbinfo->relkind == RELKIND_RELATION ||
+						tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
 						appendPQExpBuffer(q, "ALTER TABLE ONLY %s ",
 										  fmtId(tbinfo->dobj.name));
 					else
@@ -15732,6 +15751,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 	 * dump properties we only have ALTER TABLE syntax for
 	 */
 	if ((tbinfo->relkind == RELKIND_RELATION ||
+		 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 		 tbinfo->relkind == RELKIND_MATVIEW) &&
 		tbinfo->relreplident != REPLICA_IDENTITY_DEFAULT)
 	{
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 2bfa2d9..0292859 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -310,6 +310,7 @@ typedef struct _tableInfo
 	bool	   *inhNotNull;		/* true if NOT NULL is inherited */
 	struct _attrDefInfo **attrdefs;		/* DEFAULT expressions */
 	struct _constraintInfo *checkexprs; /* CHECK constraints */
+	char	   *partkeydef;		/* partition key definition */
 
 	/*
 	 * Stuff computed only for dumpable tables.
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 6275a68..10d924a 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -861,6 +861,7 @@ permissionsList(const char *pattern)
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'S' THEN '%s'"
@@ -870,6 +871,7 @@ permissionsList(const char *pattern)
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("sequence"),
@@ -920,7 +922,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
@@ -1567,8 +1569,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)");
 	}
 
@@ -1633,6 +1635,14 @@ describeOneTableDetails(const char *schemaname,
 			printfPQExpBuffer(&title, _("Foreign table \"%s.%s\""),
 							  schemaname, relationname);
 			break;
+		case 'P':
+			if (tableinfo.relpersistence == 'u')
+				printfPQExpBuffer(&title, _("Unlogged table \"%s.%s\""),
+								  schemaname, relationname);
+			else
+				printfPQExpBuffer(&title, _("Table \"%s.%s\""),
+								  schemaname, relationname);
+			break;
 		default:
 			/* untranslated unknown relkind */
 			printfPQExpBuffer(&title, "?%c? \"%s.%s\"",
@@ -1646,8 +1656,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");
@@ -1667,12 +1677,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");
 	}
 
@@ -1772,7 +1782,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);
@@ -1780,14 +1790,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 (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 */
@@ -1926,7 +1955,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;
@@ -2485,7 +2514,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;
@@ -2696,7 +2725,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
@@ -3024,6 +3053,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'i' THEN '%s'"
@@ -3035,6 +3065,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("index"),
@@ -3079,7 +3110,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 019f75a..5e5c370 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/pg_proc.h b/src/include/catalog/pg_proc.h
index e2d08ba..b45688b 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -1980,6 +1980,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/utils/builtins.h b/src/include/utils/builtins.h
index 2ae212a..e800647 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -729,6 +729,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/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 708232d..2fec847 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -408,3 +408,29 @@ CREATE TABLE no_inh_con_parted (
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
 ERROR:  cannot add NO INHERIT constraint to partitioned table "no_inh_con_parted"
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+Table "public.describe_range_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition Key: RANGE ((a + b))
+
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+Table "public.describe_list_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition Key: LIST (a)
+
+DROP TABLE describe_range_key, describe_list_key;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 6e4a8be..4dd6a0a 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -402,3 +402,16 @@ CREATE TABLE no_inh_con_parted (
 	a int,
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
+
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+DROP TABLE describe_range_key, describe_list_key;
-- 
1.7.1


--------------6EB5B318125CB7DB1A4FD229
Content-Type: text/x-diff;
 name="0003-Catalog-and-DDL-for-partitions-3.patch"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
 filename="0003-Catalog-and-DDL-for-partitions-3.patch"



^ permalink  raw  reply  [nested|flat] 110+ messages in thread

* [PATCH 2/9] psql and pg_dump support for partitioned tables.
@ 2016-07-12 08:20  amit <[email protected]>
  0 siblings, 0 replies; 110+ messages in thread

From: amit @ 2016-07-12 08:20 UTC (permalink / raw)

Takes care of both the partition key deparse stuff and the new relkind.
---
 src/backend/utils/adt/ruleutils.c          |  140 ++++++++++++++++++++++++++++
 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/pg_proc.h              |    2 +
 src/include/utils/builtins.h               |    1 +
 src/test/regress/expected/create_table.out |   26 +++++
 src/test/regress/sql/create_table.sql      |   13 +++
 9 files changed, 260 insertions(+), 26 deletions(-)

diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 8a81d7a..03be202 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_table.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
@@ -315,6 +316,7 @@ static char *pg_get_indexdef_worker(Oid indexrelid, int colno,
 					   const Oid *excludeOps,
 					   bool attrsOnly, bool showTblSpc,
 					   int prettyFlags, bool missing_ok);
+static char *pg_get_partkeydef_worker(Oid relid, int prettyFlags);
 static char *pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 							int prettyFlags, bool missing_ok);
 static text *pg_get_expr_worker(text *expr, Oid relid, const char *relname,
@@ -1389,6 +1391,144 @@ 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 [ opclass_name ] [, ...])
+ */
+Datum
+pg_get_partkeydef(PG_FUNCTION_ARGS)
+{
+	Oid			relid = PG_GETARG_OID(0);
+	int			prettyFlags;
+
+	prettyFlags = PRETTYFLAG_INDENT;
+	PG_RETURN_TEXT_P(string_to_text(pg_get_partkeydef_worker(relid,
+									prettyFlags)));
+}
+
+/*
+ * Internal workhorse to decompile a partition key definition.
+ */
+static char *
+pg_get_partkeydef_worker(Oid relid, int prettyFlags)
+{
+	Form_pg_partitioned_table	form;
+	HeapTuple	tuple;
+	oidvector  *partclass;
+	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_table) GETSTRUCT(tuple);
+
+	Assert(form->partrelid == relid);
+
+	/* Must get partclass, and partexprs the hard way */
+	datum = SysCacheGetAttr(PARTEDRELID, tuple,
+							Anum_pg_partitioned_table_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_table_partexprbin))
+	{
+		Datum		exprsDatum;
+		bool		isnull;
+		char	   *exprsString;
+
+		exprsDatum = SysCacheGetAttr(PARTEDRELID, tuple,
+									 Anum_pg_partitioned_table_partexprbin, &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);
+
+	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,
+											0, 0);
+
+			appendStringInfoString(&buf, 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/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index ba9c276..c805a84 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -1253,9 +1253,10 @@ expand_table_name_patterns(Archive *fout,
 						  "SELECT c.oid"
 						  "\nFROM pg_catalog.pg_class c"
 		"\n     LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace"
-					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c')\n",
+					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c')\n",
 						  RELKIND_RELATION, RELKIND_SEQUENCE, RELKIND_VIEW,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 		processSQLNamePattern(GetConnection(fout), query, cell->val, true,
 							  false, "n.nspname", "c.relname", NULL,
 							  "pg_catalog.pg_table_is_visible(c.oid)");
@@ -2125,6 +2126,9 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo, bool oids)
 	/* Skip FOREIGN TABLEs (no data to dump) */
 	if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 		return;
+	/* Skip partitioned tables (data in partitions) */
+	if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+		return;
 
 	/* Don't dump data in unlogged tables, if so requested */
 	if (tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED &&
@@ -5204,6 +5208,7 @@ getTables(Archive *fout, int *numTables)
 	int			i_reloftype;
 	int			i_relpages;
 	int			i_changed_acl;
+	int			i_partkeydef;
 
 	/* Make sure we are in proper schema */
 	selectSourceSchema(fout, "pg_catalog");
@@ -5289,7 +5294,8 @@ getTables(Archive *fout, int *numTables)
 						  "OR %s IS NOT NULL "
 						  "OR %s IS NOT NULL"
 						  "))"
-						  "AS changed_acl "
+						  "AS changed_acl, "
+						  "CASE WHEN c.relkind = 'P' THEN pg_catalog.pg_get_partkeydef(c.oid) ELSE NULL END AS partkeydef "
 						  "FROM pg_class c "
 						  "LEFT JOIN pg_depend d ON "
 						  "(c.relkind = '%c' AND "
@@ -5301,7 +5307,7 @@ getTables(Archive *fout, int *numTables)
 						  "(c.oid = pip.objoid "
 						  "AND pip.classoid = 'pg_class'::regclass "
 						  "AND pip.objsubid = 0) "
-				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c') "
+				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c', '%c') "
 						  "ORDER BY c.oid",
 						  acl_subquery->data,
 						  racl_subquery->data,
@@ -5315,7 +5321,8 @@ getTables(Archive *fout, int *numTables)
 						  RELKIND_SEQUENCE,
 						  RELKIND_RELATION, RELKIND_SEQUENCE,
 						  RELKIND_VIEW, RELKIND_COMPOSITE_TYPE,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 
 		destroyPQExpBuffer(acl_subquery);
 		destroyPQExpBuffer(racl_subquery);
@@ -5877,6 +5884,7 @@ getTables(Archive *fout, int *numTables)
 	i_toastreloptions = PQfnumber(res, "toast_reloptions");
 	i_reloftype = PQfnumber(res, "reloftype");
 	i_changed_acl = PQfnumber(res, "changed_acl");
+	i_partkeydef = PQfnumber(res, "partkeydef");
 
 	if (dopt->lockWaitTimeout && fout->remoteVersion >= 70300)
 	{
@@ -5947,6 +5955,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 */
 
@@ -5991,7 +6000,9 @@ getTables(Archive *fout, int *numTables)
 		 * We only need to lock the table for certain components; see
 		 * pg_dump.h
 		 */
-		if (tblinfo[i].dobj.dump && tblinfo[i].relkind == RELKIND_RELATION &&
+		if (tblinfo[i].dobj.dump &&
+			(tblinfo[i].relkind == RELKIND_RELATION ||
+			 tblinfo->relkind == RELKIND_PARTITIONED_TABLE) &&
 			(tblinfo[i].dobj.dump & DUMP_COMPONENTS_REQUIRING_LOCK))
 		{
 			resetPQExpBuffer(query);
@@ -6093,7 +6104,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);
 
@@ -15446,6 +15460,9 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 				appendPQExpBufferChar(q, ')');
 			}
 
+			if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+				appendPQExpBuffer(q, "\nPARTITION BY %s", tbinfo->partkeydef);
+
 			if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 				appendPQExpBuffer(q, "\nSERVER %s", fmtId(srvname));
 		}
@@ -15506,6 +15523,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 		 */
 		if (dopt->binary_upgrade &&
 			(tbinfo->relkind == RELKIND_RELATION ||
+			 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 			 tbinfo->relkind == RELKIND_FOREIGN_TABLE))
 		{
 			for (j = 0; j < tbinfo->numatts; j++)
@@ -15524,7 +15542,8 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 					appendStringLiteralAH(q, fmtId(tbinfo->dobj.name), fout);
 					appendPQExpBufferStr(q, "::pg_catalog.regclass;\n");
 
-					if (tbinfo->relkind == RELKIND_RELATION)
+					if (tbinfo->relkind == RELKIND_RELATION ||
+						tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
 						appendPQExpBuffer(q, "ALTER TABLE ONLY %s ",
 										  fmtId(tbinfo->dobj.name));
 					else
@@ -15741,6 +15760,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 	 * dump properties we only have ALTER TABLE syntax for
 	 */
 	if ((tbinfo->relkind == RELKIND_RELATION ||
+		 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 		 tbinfo->relkind == RELKIND_MATVIEW) &&
 		tbinfo->relreplident != REPLICA_IDENTITY_DEFAULT)
 	{
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 2bfa2d9..0292859 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -310,6 +310,7 @@ typedef struct _tableInfo
 	bool	   *inhNotNull;		/* true if NOT NULL is inherited */
 	struct _attrDefInfo **attrdefs;		/* DEFAULT expressions */
 	struct _constraintInfo *checkexprs; /* CHECK constraints */
+	char	   *partkeydef;		/* partition key definition */
 
 	/*
 	 * Stuff computed only for dumpable tables.
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 6275a68..10d924a 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -861,6 +861,7 @@ permissionsList(const char *pattern)
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'S' THEN '%s'"
@@ -870,6 +871,7 @@ permissionsList(const char *pattern)
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("sequence"),
@@ -920,7 +922,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
@@ -1567,8 +1569,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)");
 	}
 
@@ -1633,6 +1635,14 @@ describeOneTableDetails(const char *schemaname,
 			printfPQExpBuffer(&title, _("Foreign table \"%s.%s\""),
 							  schemaname, relationname);
 			break;
+		case 'P':
+			if (tableinfo.relpersistence == 'u')
+				printfPQExpBuffer(&title, _("Unlogged table \"%s.%s\""),
+								  schemaname, relationname);
+			else
+				printfPQExpBuffer(&title, _("Table \"%s.%s\""),
+								  schemaname, relationname);
+			break;
 		default:
 			/* untranslated unknown relkind */
 			printfPQExpBuffer(&title, "?%c? \"%s.%s\"",
@@ -1646,8 +1656,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");
@@ -1667,12 +1677,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");
 	}
 
@@ -1772,7 +1782,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);
@@ -1780,14 +1790,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 (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 */
@@ -1926,7 +1955,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;
@@ -2485,7 +2514,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;
@@ -2696,7 +2725,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
@@ -3024,6 +3053,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'i' THEN '%s'"
@@ -3035,6 +3065,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("index"),
@@ -3079,7 +3110,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 019f75a..5e5c370 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/pg_proc.h b/src/include/catalog/pg_proc.h
index e2d08ba..b45688b 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -1980,6 +1980,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/utils/builtins.h b/src/include/utils/builtins.h
index 2ae212a..e800647 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -729,6 +729,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/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 708232d..2fec847 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -408,3 +408,29 @@ CREATE TABLE no_inh_con_parted (
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
 ERROR:  cannot add NO INHERIT constraint to partitioned table "no_inh_con_parted"
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+Table "public.describe_range_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition Key: RANGE ((a + b))
+
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+Table "public.describe_list_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition Key: LIST (a)
+
+DROP TABLE describe_range_key, describe_list_key;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 6e4a8be..4dd6a0a 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -402,3 +402,16 @@ CREATE TABLE no_inh_con_parted (
 	a int,
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
+
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+DROP TABLE describe_range_key, describe_list_key;
-- 
1.7.1


--------------4012470FC48539D6F6714CEA
Content-Type: text/x-diff;
 name="0003-Catalog-and-DDL-for-partitions-4.patch"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
 filename="0003-Catalog-and-DDL-for-partitions-4.patch"



^ permalink  raw  reply  [nested|flat] 110+ messages in thread

* [PATCH 2/9] psql and pg_dump support for partitioned tables.
@ 2016-07-12 08:20  amit <[email protected]>
  0 siblings, 0 replies; 110+ messages in thread

From: amit @ 2016-07-12 08:20 UTC (permalink / raw)

Takes care of both the partition key deparse stuff and the new relkind.
---
 src/backend/utils/adt/ruleutils.c          |  140 ++++++++++++++++++++++++++++
 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/pg_proc.h              |    2 +
 src/include/utils/builtins.h               |    1 +
 src/test/regress/expected/create_table.out |   26 +++++
 src/test/regress/sql/create_table.sql      |   13 +++
 9 files changed, 260 insertions(+), 26 deletions(-)

diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 8a81d7a..77ce807 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_table.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
@@ -315,6 +316,7 @@ static char *pg_get_indexdef_worker(Oid indexrelid, int colno,
 					   const Oid *excludeOps,
 					   bool attrsOnly, bool showTblSpc,
 					   int prettyFlags, bool missing_ok);
+static char *pg_get_partkeydef_worker(Oid relId, int prettyFlags);
 static char *pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 							int prettyFlags, bool missing_ok);
 static text *pg_get_expr_worker(text *expr, Oid relid, const char *relname,
@@ -1389,6 +1391,144 @@ 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 [ 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_table	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_table) GETSTRUCT(tuple);
+
+	Assert(form->partedrelid == relId);
+
+	/* Must get partclass, and partexprs the hard way */
+	datum = SysCacheGetAttr(PARTEDRELID, tuple,
+							Anum_pg_partitioned_table_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_table_partexprbin))
+	{
+		Datum		exprsDatum;
+		bool		isnull;
+		char	   *exprsString;
+
+		exprsDatum = SysCacheGetAttr(PARTEDRELID, tuple,
+									 Anum_pg_partitioned_table_partexprbin, &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);
+
+	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,
+											0, 0);
+
+			appendStringInfoString(&buf, 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/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index a5c2d09..c418ba7 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -1253,9 +1253,10 @@ expand_table_name_patterns(Archive *fout,
 						  "SELECT c.oid"
 						  "\nFROM pg_catalog.pg_class c"
 		"\n     LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace"
-					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c')\n",
+					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c')\n",
 						  RELKIND_RELATION, RELKIND_SEQUENCE, RELKIND_VIEW,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 		processSQLNamePattern(GetConnection(fout), query, cell->val, true,
 							  false, "n.nspname", "c.relname", NULL,
 							  "pg_catalog.pg_table_is_visible(c.oid)");
@@ -2117,6 +2118,9 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo, bool oids)
 	/* Skip FOREIGN TABLEs (no data to dump) */
 	if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 		return;
+	/* Skip partitioned tables (data in partitions) */
+	if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+		return;
 
 	/* Don't dump data in unlogged tables, if so requested */
 	if (tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED &&
@@ -5195,6 +5199,7 @@ getTables(Archive *fout, int *numTables)
 	int			i_reloftype;
 	int			i_relpages;
 	int			i_changed_acl;
+	int			i_partkeydef;
 
 	/* Make sure we are in proper schema */
 	selectSourceSchema(fout, "pg_catalog");
@@ -5280,7 +5285,8 @@ getTables(Archive *fout, int *numTables)
 						  "OR %s IS NOT NULL "
 						  "OR %s IS NOT NULL"
 						  "))"
-						  "AS changed_acl "
+						  "AS changed_acl, "
+						  "CASE WHEN c.relkind = 'P' THEN pg_catalog.pg_get_partkeydef(c.oid) ELSE NULL END AS partkeydef "
 						  "FROM pg_class c "
 						  "LEFT JOIN pg_depend d ON "
 						  "(c.relkind = '%c' AND "
@@ -5292,7 +5298,7 @@ getTables(Archive *fout, int *numTables)
 						  "(c.oid = pip.objoid "
 						  "AND pip.classoid = 'pg_class'::regclass "
 						  "AND pip.objsubid = 0) "
-				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c') "
+				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c', '%c') "
 						  "ORDER BY c.oid",
 						  acl_subquery->data,
 						  racl_subquery->data,
@@ -5306,7 +5312,8 @@ getTables(Archive *fout, int *numTables)
 						  RELKIND_SEQUENCE,
 						  RELKIND_RELATION, RELKIND_SEQUENCE,
 						  RELKIND_VIEW, RELKIND_COMPOSITE_TYPE,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 
 		destroyPQExpBuffer(acl_subquery);
 		destroyPQExpBuffer(racl_subquery);
@@ -5868,6 +5875,7 @@ getTables(Archive *fout, int *numTables)
 	i_toastreloptions = PQfnumber(res, "toast_reloptions");
 	i_reloftype = PQfnumber(res, "reloftype");
 	i_changed_acl = PQfnumber(res, "changed_acl");
+	i_partkeydef = PQfnumber(res, "partkeydef");
 
 	if (dopt->lockWaitTimeout && fout->remoteVersion >= 70300)
 	{
@@ -5938,6 +5946,7 @@ getTables(Archive *fout, int *numTables)
 		else
 			tblinfo[i].checkoption = pg_strdup(PQgetvalue(res, i, i_checkoption));
 		tblinfo[i].toast_reloptions = pg_strdup(PQgetvalue(res, i, i_toastreloptions));
+		tblinfo[i].partkeydef = pg_strdup(PQgetvalue(res, i, i_partkeydef));
 
 		/* other fields were zeroed above */
 
@@ -5982,7 +5991,9 @@ getTables(Archive *fout, int *numTables)
 		 * We only need to lock the table for certain components; see
 		 * pg_dump.h
 		 */
-		if (tblinfo[i].dobj.dump && tblinfo[i].relkind == RELKIND_RELATION &&
+		if (tblinfo[i].dobj.dump &&
+			(tblinfo[i].relkind == RELKIND_RELATION ||
+			 tblinfo->relkind == RELKIND_PARTITIONED_TABLE) &&
 			(tblinfo[i].dobj.dump & DUMP_COMPONENTS_REQUIRING_LOCK))
 		{
 			resetPQExpBuffer(query);
@@ -6084,7 +6095,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);
 
@@ -15437,6 +15451,9 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 				appendPQExpBufferChar(q, ')');
 			}
 
+			if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+				appendPQExpBuffer(q, "\nPARTITION BY %s", tbinfo->partkeydef);
+
 			if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 				appendPQExpBuffer(q, "\nSERVER %s", fmtId(srvname));
 		}
@@ -15497,6 +15514,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 		 */
 		if (dopt->binary_upgrade &&
 			(tbinfo->relkind == RELKIND_RELATION ||
+			 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 			 tbinfo->relkind == RELKIND_FOREIGN_TABLE))
 		{
 			for (j = 0; j < tbinfo->numatts; j++)
@@ -15515,7 +15533,8 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 					appendStringLiteralAH(q, fmtId(tbinfo->dobj.name), fout);
 					appendPQExpBufferStr(q, "::pg_catalog.regclass;\n");
 
-					if (tbinfo->relkind == RELKIND_RELATION)
+					if (tbinfo->relkind == RELKIND_RELATION ||
+						tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
 						appendPQExpBuffer(q, "ALTER TABLE ONLY %s ",
 										  fmtId(tbinfo->dobj.name));
 					else
@@ -15732,6 +15751,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 	 * dump properties we only have ALTER TABLE syntax for
 	 */
 	if ((tbinfo->relkind == RELKIND_RELATION ||
+		 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 		 tbinfo->relkind == RELKIND_MATVIEW) &&
 		tbinfo->relreplident != REPLICA_IDENTITY_DEFAULT)
 	{
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 2bfa2d9..0292859 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -310,6 +310,7 @@ typedef struct _tableInfo
 	bool	   *inhNotNull;		/* true if NOT NULL is inherited */
 	struct _attrDefInfo **attrdefs;		/* DEFAULT expressions */
 	struct _constraintInfo *checkexprs; /* CHECK constraints */
+	char	   *partkeydef;		/* partition key definition */
 
 	/*
 	 * Stuff computed only for dumpable tables.
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 6275a68..10d924a 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -861,6 +861,7 @@ permissionsList(const char *pattern)
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'S' THEN '%s'"
@@ -870,6 +871,7 @@ permissionsList(const char *pattern)
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("sequence"),
@@ -920,7 +922,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
@@ -1567,8 +1569,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)");
 	}
 
@@ -1633,6 +1635,14 @@ describeOneTableDetails(const char *schemaname,
 			printfPQExpBuffer(&title, _("Foreign table \"%s.%s\""),
 							  schemaname, relationname);
 			break;
+		case 'P':
+			if (tableinfo.relpersistence == 'u')
+				printfPQExpBuffer(&title, _("Unlogged table \"%s.%s\""),
+								  schemaname, relationname);
+			else
+				printfPQExpBuffer(&title, _("Table \"%s.%s\""),
+								  schemaname, relationname);
+			break;
 		default:
 			/* untranslated unknown relkind */
 			printfPQExpBuffer(&title, "?%c? \"%s.%s\"",
@@ -1646,8 +1656,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");
@@ -1667,12 +1677,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");
 	}
 
@@ -1772,7 +1782,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);
@@ -1780,14 +1790,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 (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 */
@@ -1926,7 +1955,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;
@@ -2485,7 +2514,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;
@@ -2696,7 +2725,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
@@ -3024,6 +3053,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'i' THEN '%s'"
@@ -3035,6 +3065,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("index"),
@@ -3079,7 +3110,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 019f75a..5e5c370 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/pg_proc.h b/src/include/catalog/pg_proc.h
index e2d08ba..b45688b 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -1980,6 +1980,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/utils/builtins.h b/src/include/utils/builtins.h
index 2ae212a..e800647 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -729,6 +729,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/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 708232d..2fec847 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -408,3 +408,29 @@ CREATE TABLE no_inh_con_parted (
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
 ERROR:  cannot add NO INHERIT constraint to partitioned table "no_inh_con_parted"
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+Table "public.describe_range_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition Key: RANGE ((a + b))
+
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+Table "public.describe_list_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition Key: LIST (a)
+
+DROP TABLE describe_range_key, describe_list_key;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 6e4a8be..4dd6a0a 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -402,3 +402,16 @@ CREATE TABLE no_inh_con_parted (
 	a int,
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
+
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+DROP TABLE describe_range_key, describe_list_key;
-- 
1.7.1


--------------6EB5B318125CB7DB1A4FD229
Content-Type: text/x-diff;
 name="0003-Catalog-and-DDL-for-partitions-3.patch"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
 filename="0003-Catalog-and-DDL-for-partitions-3.patch"



^ permalink  raw  reply  [nested|flat] 110+ messages in thread

* [PATCH 2/9] psql and pg_dump support for partitioned tables.
@ 2016-07-12 08:20  amit <[email protected]>
  0 siblings, 0 replies; 110+ messages in thread

From: amit @ 2016-07-12 08:20 UTC (permalink / raw)

Takes care of both the partition key deparse stuff and the new relkind.
---
 src/backend/utils/adt/ruleutils.c          |  140 ++++++++++++++++++++++++++++
 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/pg_proc.h              |    2 +
 src/include/utils/builtins.h               |    1 +
 src/test/regress/expected/create_table.out |   26 +++++
 src/test/regress/sql/create_table.sql      |   13 +++
 9 files changed, 260 insertions(+), 26 deletions(-)

diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 8a81d7a..03be202 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_table.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
@@ -315,6 +316,7 @@ static char *pg_get_indexdef_worker(Oid indexrelid, int colno,
 					   const Oid *excludeOps,
 					   bool attrsOnly, bool showTblSpc,
 					   int prettyFlags, bool missing_ok);
+static char *pg_get_partkeydef_worker(Oid relid, int prettyFlags);
 static char *pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 							int prettyFlags, bool missing_ok);
 static text *pg_get_expr_worker(text *expr, Oid relid, const char *relname,
@@ -1389,6 +1391,144 @@ 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 [ opclass_name ] [, ...])
+ */
+Datum
+pg_get_partkeydef(PG_FUNCTION_ARGS)
+{
+	Oid			relid = PG_GETARG_OID(0);
+	int			prettyFlags;
+
+	prettyFlags = PRETTYFLAG_INDENT;
+	PG_RETURN_TEXT_P(string_to_text(pg_get_partkeydef_worker(relid,
+									prettyFlags)));
+}
+
+/*
+ * Internal workhorse to decompile a partition key definition.
+ */
+static char *
+pg_get_partkeydef_worker(Oid relid, int prettyFlags)
+{
+	Form_pg_partitioned_table	form;
+	HeapTuple	tuple;
+	oidvector  *partclass;
+	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_table) GETSTRUCT(tuple);
+
+	Assert(form->partrelid == relid);
+
+	/* Must get partclass, and partexprs the hard way */
+	datum = SysCacheGetAttr(PARTEDRELID, tuple,
+							Anum_pg_partitioned_table_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_table_partexprbin))
+	{
+		Datum		exprsDatum;
+		bool		isnull;
+		char	   *exprsString;
+
+		exprsDatum = SysCacheGetAttr(PARTEDRELID, tuple,
+									 Anum_pg_partitioned_table_partexprbin, &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);
+
+	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,
+											0, 0);
+
+			appendStringInfoString(&buf, 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/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index ba9c276..c805a84 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -1253,9 +1253,10 @@ expand_table_name_patterns(Archive *fout,
 						  "SELECT c.oid"
 						  "\nFROM pg_catalog.pg_class c"
 		"\n     LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace"
-					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c')\n",
+					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c')\n",
 						  RELKIND_RELATION, RELKIND_SEQUENCE, RELKIND_VIEW,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 		processSQLNamePattern(GetConnection(fout), query, cell->val, true,
 							  false, "n.nspname", "c.relname", NULL,
 							  "pg_catalog.pg_table_is_visible(c.oid)");
@@ -2125,6 +2126,9 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo, bool oids)
 	/* Skip FOREIGN TABLEs (no data to dump) */
 	if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 		return;
+	/* Skip partitioned tables (data in partitions) */
+	if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+		return;
 
 	/* Don't dump data in unlogged tables, if so requested */
 	if (tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED &&
@@ -5204,6 +5208,7 @@ getTables(Archive *fout, int *numTables)
 	int			i_reloftype;
 	int			i_relpages;
 	int			i_changed_acl;
+	int			i_partkeydef;
 
 	/* Make sure we are in proper schema */
 	selectSourceSchema(fout, "pg_catalog");
@@ -5289,7 +5294,8 @@ getTables(Archive *fout, int *numTables)
 						  "OR %s IS NOT NULL "
 						  "OR %s IS NOT NULL"
 						  "))"
-						  "AS changed_acl "
+						  "AS changed_acl, "
+						  "CASE WHEN c.relkind = 'P' THEN pg_catalog.pg_get_partkeydef(c.oid) ELSE NULL END AS partkeydef "
 						  "FROM pg_class c "
 						  "LEFT JOIN pg_depend d ON "
 						  "(c.relkind = '%c' AND "
@@ -5301,7 +5307,7 @@ getTables(Archive *fout, int *numTables)
 						  "(c.oid = pip.objoid "
 						  "AND pip.classoid = 'pg_class'::regclass "
 						  "AND pip.objsubid = 0) "
-				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c') "
+				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c', '%c') "
 						  "ORDER BY c.oid",
 						  acl_subquery->data,
 						  racl_subquery->data,
@@ -5315,7 +5321,8 @@ getTables(Archive *fout, int *numTables)
 						  RELKIND_SEQUENCE,
 						  RELKIND_RELATION, RELKIND_SEQUENCE,
 						  RELKIND_VIEW, RELKIND_COMPOSITE_TYPE,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 
 		destroyPQExpBuffer(acl_subquery);
 		destroyPQExpBuffer(racl_subquery);
@@ -5877,6 +5884,7 @@ getTables(Archive *fout, int *numTables)
 	i_toastreloptions = PQfnumber(res, "toast_reloptions");
 	i_reloftype = PQfnumber(res, "reloftype");
 	i_changed_acl = PQfnumber(res, "changed_acl");
+	i_partkeydef = PQfnumber(res, "partkeydef");
 
 	if (dopt->lockWaitTimeout && fout->remoteVersion >= 70300)
 	{
@@ -5947,6 +5955,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 */
 
@@ -5991,7 +6000,9 @@ getTables(Archive *fout, int *numTables)
 		 * We only need to lock the table for certain components; see
 		 * pg_dump.h
 		 */
-		if (tblinfo[i].dobj.dump && tblinfo[i].relkind == RELKIND_RELATION &&
+		if (tblinfo[i].dobj.dump &&
+			(tblinfo[i].relkind == RELKIND_RELATION ||
+			 tblinfo->relkind == RELKIND_PARTITIONED_TABLE) &&
 			(tblinfo[i].dobj.dump & DUMP_COMPONENTS_REQUIRING_LOCK))
 		{
 			resetPQExpBuffer(query);
@@ -6093,7 +6104,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);
 
@@ -15446,6 +15460,9 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 				appendPQExpBufferChar(q, ')');
 			}
 
+			if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+				appendPQExpBuffer(q, "\nPARTITION BY %s", tbinfo->partkeydef);
+
 			if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 				appendPQExpBuffer(q, "\nSERVER %s", fmtId(srvname));
 		}
@@ -15506,6 +15523,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 		 */
 		if (dopt->binary_upgrade &&
 			(tbinfo->relkind == RELKIND_RELATION ||
+			 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 			 tbinfo->relkind == RELKIND_FOREIGN_TABLE))
 		{
 			for (j = 0; j < tbinfo->numatts; j++)
@@ -15524,7 +15542,8 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 					appendStringLiteralAH(q, fmtId(tbinfo->dobj.name), fout);
 					appendPQExpBufferStr(q, "::pg_catalog.regclass;\n");
 
-					if (tbinfo->relkind == RELKIND_RELATION)
+					if (tbinfo->relkind == RELKIND_RELATION ||
+						tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
 						appendPQExpBuffer(q, "ALTER TABLE ONLY %s ",
 										  fmtId(tbinfo->dobj.name));
 					else
@@ -15741,6 +15760,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 	 * dump properties we only have ALTER TABLE syntax for
 	 */
 	if ((tbinfo->relkind == RELKIND_RELATION ||
+		 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 		 tbinfo->relkind == RELKIND_MATVIEW) &&
 		tbinfo->relreplident != REPLICA_IDENTITY_DEFAULT)
 	{
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 2bfa2d9..0292859 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -310,6 +310,7 @@ typedef struct _tableInfo
 	bool	   *inhNotNull;		/* true if NOT NULL is inherited */
 	struct _attrDefInfo **attrdefs;		/* DEFAULT expressions */
 	struct _constraintInfo *checkexprs; /* CHECK constraints */
+	char	   *partkeydef;		/* partition key definition */
 
 	/*
 	 * Stuff computed only for dumpable tables.
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 6275a68..10d924a 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -861,6 +861,7 @@ permissionsList(const char *pattern)
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'S' THEN '%s'"
@@ -870,6 +871,7 @@ permissionsList(const char *pattern)
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("sequence"),
@@ -920,7 +922,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
@@ -1567,8 +1569,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)");
 	}
 
@@ -1633,6 +1635,14 @@ describeOneTableDetails(const char *schemaname,
 			printfPQExpBuffer(&title, _("Foreign table \"%s.%s\""),
 							  schemaname, relationname);
 			break;
+		case 'P':
+			if (tableinfo.relpersistence == 'u')
+				printfPQExpBuffer(&title, _("Unlogged table \"%s.%s\""),
+								  schemaname, relationname);
+			else
+				printfPQExpBuffer(&title, _("Table \"%s.%s\""),
+								  schemaname, relationname);
+			break;
 		default:
 			/* untranslated unknown relkind */
 			printfPQExpBuffer(&title, "?%c? \"%s.%s\"",
@@ -1646,8 +1656,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");
@@ -1667,12 +1677,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");
 	}
 
@@ -1772,7 +1782,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);
@@ -1780,14 +1790,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 (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 */
@@ -1926,7 +1955,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;
@@ -2485,7 +2514,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;
@@ -2696,7 +2725,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
@@ -3024,6 +3053,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'i' THEN '%s'"
@@ -3035,6 +3065,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("index"),
@@ -3079,7 +3110,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 50a45eb..8284a9c 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/pg_proc.h b/src/include/catalog/pg_proc.h
index e2d08ba..b45688b 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -1980,6 +1980,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/utils/builtins.h b/src/include/utils/builtins.h
index 2ae212a..e800647 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -729,6 +729,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/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 0af1422..91ba1be 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -412,3 +412,29 @@ CREATE TABLE no_inh_con_parted (
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
 ERROR:  cannot add NO INHERIT constraint to partitioned table "no_inh_con_parted"
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+Table "public.describe_range_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition Key: RANGE ((a + b))
+
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+Table "public.describe_list_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition Key: LIST (a)
+
+DROP TABLE describe_range_key, describe_list_key;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 418ac55..b75542d 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -406,3 +406,16 @@ CREATE TABLE no_inh_con_parted (
 	a int,
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
+
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+DROP TABLE describe_range_key, describe_list_key;
-- 
1.7.1


--------------D008BA677674C4836D7B65AD
Content-Type: text/x-diff;
 name="0003-Catalog-and-DDL-for-partitions-5.patch"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
 filename="0003-Catalog-and-DDL-for-partitions-5.patch"



^ permalink  raw  reply  [nested|flat] 110+ messages in thread

* [PATCH 2/9] psql and pg_dump support for partitioned tables.
@ 2016-07-12 08:20  amit <[email protected]>
  0 siblings, 0 replies; 110+ messages in thread

From: amit @ 2016-07-12 08:20 UTC (permalink / raw)

Takes care of both the partition key deparse stuff and the new relkind.
---
 src/backend/utils/adt/ruleutils.c          |  140 ++++++++++++++++++++++++++++
 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/pg_proc.h              |    2 +
 src/include/utils/builtins.h               |    1 +
 src/test/regress/expected/create_table.out |   26 +++++
 src/test/regress/sql/create_table.sql      |   13 +++
 9 files changed, 260 insertions(+), 26 deletions(-)

diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 8a81d7a..77ce807 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_table.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
@@ -315,6 +316,7 @@ static char *pg_get_indexdef_worker(Oid indexrelid, int colno,
 					   const Oid *excludeOps,
 					   bool attrsOnly, bool showTblSpc,
 					   int prettyFlags, bool missing_ok);
+static char *pg_get_partkeydef_worker(Oid relId, int prettyFlags);
 static char *pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 							int prettyFlags, bool missing_ok);
 static text *pg_get_expr_worker(text *expr, Oid relid, const char *relname,
@@ -1389,6 +1391,144 @@ 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 [ 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_table	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_table) GETSTRUCT(tuple);
+
+	Assert(form->partedrelid == relId);
+
+	/* Must get partclass, and partexprs the hard way */
+	datum = SysCacheGetAttr(PARTEDRELID, tuple,
+							Anum_pg_partitioned_table_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_table_partexprbin))
+	{
+		Datum		exprsDatum;
+		bool		isnull;
+		char	   *exprsString;
+
+		exprsDatum = SysCacheGetAttr(PARTEDRELID, tuple,
+									 Anum_pg_partitioned_table_partexprbin, &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);
+
+	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,
+											0, 0);
+
+			appendStringInfoString(&buf, 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/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index a5c2d09..c418ba7 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -1253,9 +1253,10 @@ expand_table_name_patterns(Archive *fout,
 						  "SELECT c.oid"
 						  "\nFROM pg_catalog.pg_class c"
 		"\n     LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace"
-					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c')\n",
+					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c')\n",
 						  RELKIND_RELATION, RELKIND_SEQUENCE, RELKIND_VIEW,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 		processSQLNamePattern(GetConnection(fout), query, cell->val, true,
 							  false, "n.nspname", "c.relname", NULL,
 							  "pg_catalog.pg_table_is_visible(c.oid)");
@@ -2117,6 +2118,9 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo, bool oids)
 	/* Skip FOREIGN TABLEs (no data to dump) */
 	if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 		return;
+	/* Skip partitioned tables (data in partitions) */
+	if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+		return;
 
 	/* Don't dump data in unlogged tables, if so requested */
 	if (tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED &&
@@ -5195,6 +5199,7 @@ getTables(Archive *fout, int *numTables)
 	int			i_reloftype;
 	int			i_relpages;
 	int			i_changed_acl;
+	int			i_partkeydef;
 
 	/* Make sure we are in proper schema */
 	selectSourceSchema(fout, "pg_catalog");
@@ -5280,7 +5285,8 @@ getTables(Archive *fout, int *numTables)
 						  "OR %s IS NOT NULL "
 						  "OR %s IS NOT NULL"
 						  "))"
-						  "AS changed_acl "
+						  "AS changed_acl, "
+						  "CASE WHEN c.relkind = 'P' THEN pg_catalog.pg_get_partkeydef(c.oid) ELSE NULL END AS partkeydef "
 						  "FROM pg_class c "
 						  "LEFT JOIN pg_depend d ON "
 						  "(c.relkind = '%c' AND "
@@ -5292,7 +5298,7 @@ getTables(Archive *fout, int *numTables)
 						  "(c.oid = pip.objoid "
 						  "AND pip.classoid = 'pg_class'::regclass "
 						  "AND pip.objsubid = 0) "
-				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c') "
+				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c', '%c') "
 						  "ORDER BY c.oid",
 						  acl_subquery->data,
 						  racl_subquery->data,
@@ -5306,7 +5312,8 @@ getTables(Archive *fout, int *numTables)
 						  RELKIND_SEQUENCE,
 						  RELKIND_RELATION, RELKIND_SEQUENCE,
 						  RELKIND_VIEW, RELKIND_COMPOSITE_TYPE,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 
 		destroyPQExpBuffer(acl_subquery);
 		destroyPQExpBuffer(racl_subquery);
@@ -5868,6 +5875,7 @@ getTables(Archive *fout, int *numTables)
 	i_toastreloptions = PQfnumber(res, "toast_reloptions");
 	i_reloftype = PQfnumber(res, "reloftype");
 	i_changed_acl = PQfnumber(res, "changed_acl");
+	i_partkeydef = PQfnumber(res, "partkeydef");
 
 	if (dopt->lockWaitTimeout && fout->remoteVersion >= 70300)
 	{
@@ -5938,6 +5946,7 @@ getTables(Archive *fout, int *numTables)
 		else
 			tblinfo[i].checkoption = pg_strdup(PQgetvalue(res, i, i_checkoption));
 		tblinfo[i].toast_reloptions = pg_strdup(PQgetvalue(res, i, i_toastreloptions));
+		tblinfo[i].partkeydef = pg_strdup(PQgetvalue(res, i, i_partkeydef));
 
 		/* other fields were zeroed above */
 
@@ -5982,7 +5991,9 @@ getTables(Archive *fout, int *numTables)
 		 * We only need to lock the table for certain components; see
 		 * pg_dump.h
 		 */
-		if (tblinfo[i].dobj.dump && tblinfo[i].relkind == RELKIND_RELATION &&
+		if (tblinfo[i].dobj.dump &&
+			(tblinfo[i].relkind == RELKIND_RELATION ||
+			 tblinfo->relkind == RELKIND_PARTITIONED_TABLE) &&
 			(tblinfo[i].dobj.dump & DUMP_COMPONENTS_REQUIRING_LOCK))
 		{
 			resetPQExpBuffer(query);
@@ -6084,7 +6095,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);
 
@@ -15437,6 +15451,9 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 				appendPQExpBufferChar(q, ')');
 			}
 
+			if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+				appendPQExpBuffer(q, "\nPARTITION BY %s", tbinfo->partkeydef);
+
 			if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 				appendPQExpBuffer(q, "\nSERVER %s", fmtId(srvname));
 		}
@@ -15497,6 +15514,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 		 */
 		if (dopt->binary_upgrade &&
 			(tbinfo->relkind == RELKIND_RELATION ||
+			 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 			 tbinfo->relkind == RELKIND_FOREIGN_TABLE))
 		{
 			for (j = 0; j < tbinfo->numatts; j++)
@@ -15515,7 +15533,8 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 					appendStringLiteralAH(q, fmtId(tbinfo->dobj.name), fout);
 					appendPQExpBufferStr(q, "::pg_catalog.regclass;\n");
 
-					if (tbinfo->relkind == RELKIND_RELATION)
+					if (tbinfo->relkind == RELKIND_RELATION ||
+						tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
 						appendPQExpBuffer(q, "ALTER TABLE ONLY %s ",
 										  fmtId(tbinfo->dobj.name));
 					else
@@ -15732,6 +15751,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 	 * dump properties we only have ALTER TABLE syntax for
 	 */
 	if ((tbinfo->relkind == RELKIND_RELATION ||
+		 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 		 tbinfo->relkind == RELKIND_MATVIEW) &&
 		tbinfo->relreplident != REPLICA_IDENTITY_DEFAULT)
 	{
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 2bfa2d9..0292859 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -310,6 +310,7 @@ typedef struct _tableInfo
 	bool	   *inhNotNull;		/* true if NOT NULL is inherited */
 	struct _attrDefInfo **attrdefs;		/* DEFAULT expressions */
 	struct _constraintInfo *checkexprs; /* CHECK constraints */
+	char	   *partkeydef;		/* partition key definition */
 
 	/*
 	 * Stuff computed only for dumpable tables.
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 6275a68..10d924a 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -861,6 +861,7 @@ permissionsList(const char *pattern)
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'S' THEN '%s'"
@@ -870,6 +871,7 @@ permissionsList(const char *pattern)
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("sequence"),
@@ -920,7 +922,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
@@ -1567,8 +1569,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)");
 	}
 
@@ -1633,6 +1635,14 @@ describeOneTableDetails(const char *schemaname,
 			printfPQExpBuffer(&title, _("Foreign table \"%s.%s\""),
 							  schemaname, relationname);
 			break;
+		case 'P':
+			if (tableinfo.relpersistence == 'u')
+				printfPQExpBuffer(&title, _("Unlogged table \"%s.%s\""),
+								  schemaname, relationname);
+			else
+				printfPQExpBuffer(&title, _("Table \"%s.%s\""),
+								  schemaname, relationname);
+			break;
 		default:
 			/* untranslated unknown relkind */
 			printfPQExpBuffer(&title, "?%c? \"%s.%s\"",
@@ -1646,8 +1656,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");
@@ -1667,12 +1677,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");
 	}
 
@@ -1772,7 +1782,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);
@@ -1780,14 +1790,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 (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 */
@@ -1926,7 +1955,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;
@@ -2485,7 +2514,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;
@@ -2696,7 +2725,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
@@ -3024,6 +3053,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'i' THEN '%s'"
@@ -3035,6 +3065,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("index"),
@@ -3079,7 +3110,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 019f75a..5e5c370 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/pg_proc.h b/src/include/catalog/pg_proc.h
index e2d08ba..b45688b 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -1980,6 +1980,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/utils/builtins.h b/src/include/utils/builtins.h
index 2ae212a..e800647 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -729,6 +729,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/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 708232d..2fec847 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -408,3 +408,29 @@ CREATE TABLE no_inh_con_parted (
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
 ERROR:  cannot add NO INHERIT constraint to partitioned table "no_inh_con_parted"
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+Table "public.describe_range_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition Key: RANGE ((a + b))
+
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+Table "public.describe_list_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition Key: LIST (a)
+
+DROP TABLE describe_range_key, describe_list_key;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 6e4a8be..4dd6a0a 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -402,3 +402,16 @@ CREATE TABLE no_inh_con_parted (
 	a int,
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
+
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+DROP TABLE describe_range_key, describe_list_key;
-- 
1.7.1


--------------6EB5B318125CB7DB1A4FD229
Content-Type: text/x-diff;
 name="0003-Catalog-and-DDL-for-partitions-3.patch"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
 filename="0003-Catalog-and-DDL-for-partitions-3.patch"



^ permalink  raw  reply  [nested|flat] 110+ messages in thread

* [PATCH 2/9] psql and pg_dump support for partitioned tables.
@ 2016-07-12 08:20  amit <[email protected]>
  0 siblings, 0 replies; 110+ messages in thread

From: amit @ 2016-07-12 08:20 UTC (permalink / raw)

Takes care of both the partition key deparse stuff and the new relkind.
---
 src/backend/utils/adt/ruleutils.c          |  140 ++++++++++++++++++++++++++++
 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/pg_proc.h              |    2 +
 src/include/utils/builtins.h               |    1 +
 src/test/regress/expected/create_table.out |   26 +++++
 src/test/regress/sql/create_table.sql      |   13 +++
 9 files changed, 260 insertions(+), 26 deletions(-)

diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 8a81d7a..03be202 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_table.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
@@ -315,6 +316,7 @@ static char *pg_get_indexdef_worker(Oid indexrelid, int colno,
 					   const Oid *excludeOps,
 					   bool attrsOnly, bool showTblSpc,
 					   int prettyFlags, bool missing_ok);
+static char *pg_get_partkeydef_worker(Oid relid, int prettyFlags);
 static char *pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 							int prettyFlags, bool missing_ok);
 static text *pg_get_expr_worker(text *expr, Oid relid, const char *relname,
@@ -1389,6 +1391,144 @@ 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 [ opclass_name ] [, ...])
+ */
+Datum
+pg_get_partkeydef(PG_FUNCTION_ARGS)
+{
+	Oid			relid = PG_GETARG_OID(0);
+	int			prettyFlags;
+
+	prettyFlags = PRETTYFLAG_INDENT;
+	PG_RETURN_TEXT_P(string_to_text(pg_get_partkeydef_worker(relid,
+									prettyFlags)));
+}
+
+/*
+ * Internal workhorse to decompile a partition key definition.
+ */
+static char *
+pg_get_partkeydef_worker(Oid relid, int prettyFlags)
+{
+	Form_pg_partitioned_table	form;
+	HeapTuple	tuple;
+	oidvector  *partclass;
+	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_table) GETSTRUCT(tuple);
+
+	Assert(form->partrelid == relid);
+
+	/* Must get partclass, and partexprs the hard way */
+	datum = SysCacheGetAttr(PARTEDRELID, tuple,
+							Anum_pg_partitioned_table_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_table_partexprbin))
+	{
+		Datum		exprsDatum;
+		bool		isnull;
+		char	   *exprsString;
+
+		exprsDatum = SysCacheGetAttr(PARTEDRELID, tuple,
+									 Anum_pg_partitioned_table_partexprbin, &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);
+
+	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,
+											0, 0);
+
+			appendStringInfoString(&buf, 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/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index ba9c276..c805a84 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -1253,9 +1253,10 @@ expand_table_name_patterns(Archive *fout,
 						  "SELECT c.oid"
 						  "\nFROM pg_catalog.pg_class c"
 		"\n     LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace"
-					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c')\n",
+					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c')\n",
 						  RELKIND_RELATION, RELKIND_SEQUENCE, RELKIND_VIEW,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 		processSQLNamePattern(GetConnection(fout), query, cell->val, true,
 							  false, "n.nspname", "c.relname", NULL,
 							  "pg_catalog.pg_table_is_visible(c.oid)");
@@ -2125,6 +2126,9 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo, bool oids)
 	/* Skip FOREIGN TABLEs (no data to dump) */
 	if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 		return;
+	/* Skip partitioned tables (data in partitions) */
+	if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+		return;
 
 	/* Don't dump data in unlogged tables, if so requested */
 	if (tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED &&
@@ -5204,6 +5208,7 @@ getTables(Archive *fout, int *numTables)
 	int			i_reloftype;
 	int			i_relpages;
 	int			i_changed_acl;
+	int			i_partkeydef;
 
 	/* Make sure we are in proper schema */
 	selectSourceSchema(fout, "pg_catalog");
@@ -5289,7 +5294,8 @@ getTables(Archive *fout, int *numTables)
 						  "OR %s IS NOT NULL "
 						  "OR %s IS NOT NULL"
 						  "))"
-						  "AS changed_acl "
+						  "AS changed_acl, "
+						  "CASE WHEN c.relkind = 'P' THEN pg_catalog.pg_get_partkeydef(c.oid) ELSE NULL END AS partkeydef "
 						  "FROM pg_class c "
 						  "LEFT JOIN pg_depend d ON "
 						  "(c.relkind = '%c' AND "
@@ -5301,7 +5307,7 @@ getTables(Archive *fout, int *numTables)
 						  "(c.oid = pip.objoid "
 						  "AND pip.classoid = 'pg_class'::regclass "
 						  "AND pip.objsubid = 0) "
-				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c') "
+				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c', '%c') "
 						  "ORDER BY c.oid",
 						  acl_subquery->data,
 						  racl_subquery->data,
@@ -5315,7 +5321,8 @@ getTables(Archive *fout, int *numTables)
 						  RELKIND_SEQUENCE,
 						  RELKIND_RELATION, RELKIND_SEQUENCE,
 						  RELKIND_VIEW, RELKIND_COMPOSITE_TYPE,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 
 		destroyPQExpBuffer(acl_subquery);
 		destroyPQExpBuffer(racl_subquery);
@@ -5877,6 +5884,7 @@ getTables(Archive *fout, int *numTables)
 	i_toastreloptions = PQfnumber(res, "toast_reloptions");
 	i_reloftype = PQfnumber(res, "reloftype");
 	i_changed_acl = PQfnumber(res, "changed_acl");
+	i_partkeydef = PQfnumber(res, "partkeydef");
 
 	if (dopt->lockWaitTimeout && fout->remoteVersion >= 70300)
 	{
@@ -5947,6 +5955,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 */
 
@@ -5991,7 +6000,9 @@ getTables(Archive *fout, int *numTables)
 		 * We only need to lock the table for certain components; see
 		 * pg_dump.h
 		 */
-		if (tblinfo[i].dobj.dump && tblinfo[i].relkind == RELKIND_RELATION &&
+		if (tblinfo[i].dobj.dump &&
+			(tblinfo[i].relkind == RELKIND_RELATION ||
+			 tblinfo->relkind == RELKIND_PARTITIONED_TABLE) &&
 			(tblinfo[i].dobj.dump & DUMP_COMPONENTS_REQUIRING_LOCK))
 		{
 			resetPQExpBuffer(query);
@@ -6093,7 +6104,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);
 
@@ -15446,6 +15460,9 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 				appendPQExpBufferChar(q, ')');
 			}
 
+			if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+				appendPQExpBuffer(q, "\nPARTITION BY %s", tbinfo->partkeydef);
+
 			if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 				appendPQExpBuffer(q, "\nSERVER %s", fmtId(srvname));
 		}
@@ -15506,6 +15523,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 		 */
 		if (dopt->binary_upgrade &&
 			(tbinfo->relkind == RELKIND_RELATION ||
+			 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 			 tbinfo->relkind == RELKIND_FOREIGN_TABLE))
 		{
 			for (j = 0; j < tbinfo->numatts; j++)
@@ -15524,7 +15542,8 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 					appendStringLiteralAH(q, fmtId(tbinfo->dobj.name), fout);
 					appendPQExpBufferStr(q, "::pg_catalog.regclass;\n");
 
-					if (tbinfo->relkind == RELKIND_RELATION)
+					if (tbinfo->relkind == RELKIND_RELATION ||
+						tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
 						appendPQExpBuffer(q, "ALTER TABLE ONLY %s ",
 										  fmtId(tbinfo->dobj.name));
 					else
@@ -15741,6 +15760,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 	 * dump properties we only have ALTER TABLE syntax for
 	 */
 	if ((tbinfo->relkind == RELKIND_RELATION ||
+		 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 		 tbinfo->relkind == RELKIND_MATVIEW) &&
 		tbinfo->relreplident != REPLICA_IDENTITY_DEFAULT)
 	{
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 2bfa2d9..0292859 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -310,6 +310,7 @@ typedef struct _tableInfo
 	bool	   *inhNotNull;		/* true if NOT NULL is inherited */
 	struct _attrDefInfo **attrdefs;		/* DEFAULT expressions */
 	struct _constraintInfo *checkexprs; /* CHECK constraints */
+	char	   *partkeydef;		/* partition key definition */
 
 	/*
 	 * Stuff computed only for dumpable tables.
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 6275a68..10d924a 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -861,6 +861,7 @@ permissionsList(const char *pattern)
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'S' THEN '%s'"
@@ -870,6 +871,7 @@ permissionsList(const char *pattern)
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("sequence"),
@@ -920,7 +922,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
@@ -1567,8 +1569,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)");
 	}
 
@@ -1633,6 +1635,14 @@ describeOneTableDetails(const char *schemaname,
 			printfPQExpBuffer(&title, _("Foreign table \"%s.%s\""),
 							  schemaname, relationname);
 			break;
+		case 'P':
+			if (tableinfo.relpersistence == 'u')
+				printfPQExpBuffer(&title, _("Unlogged table \"%s.%s\""),
+								  schemaname, relationname);
+			else
+				printfPQExpBuffer(&title, _("Table \"%s.%s\""),
+								  schemaname, relationname);
+			break;
 		default:
 			/* untranslated unknown relkind */
 			printfPQExpBuffer(&title, "?%c? \"%s.%s\"",
@@ -1646,8 +1656,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");
@@ -1667,12 +1677,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");
 	}
 
@@ -1772,7 +1782,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);
@@ -1780,14 +1790,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 (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 */
@@ -1926,7 +1955,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;
@@ -2485,7 +2514,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;
@@ -2696,7 +2725,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
@@ -3024,6 +3053,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'i' THEN '%s'"
@@ -3035,6 +3065,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("index"),
@@ -3079,7 +3110,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 50a45eb..8284a9c 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/pg_proc.h b/src/include/catalog/pg_proc.h
index e2d08ba..b45688b 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -1980,6 +1980,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/utils/builtins.h b/src/include/utils/builtins.h
index 2ae212a..e800647 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -729,6 +729,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/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 0af1422..91ba1be 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -412,3 +412,29 @@ CREATE TABLE no_inh_con_parted (
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
 ERROR:  cannot add NO INHERIT constraint to partitioned table "no_inh_con_parted"
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+Table "public.describe_range_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition Key: RANGE ((a + b))
+
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+Table "public.describe_list_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition Key: LIST (a)
+
+DROP TABLE describe_range_key, describe_list_key;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 418ac55..b75542d 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -406,3 +406,16 @@ CREATE TABLE no_inh_con_parted (
 	a int,
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
+
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+DROP TABLE describe_range_key, describe_list_key;
-- 
1.7.1


--------------D008BA677674C4836D7B65AD
Content-Type: text/x-diff;
 name="0003-Catalog-and-DDL-for-partitions-5.patch"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
 filename="0003-Catalog-and-DDL-for-partitions-5.patch"



^ permalink  raw  reply  [nested|flat] 110+ messages in thread

* [PATCH 2/8] psql and pg_dump support for partitioned tables.
@ 2016-07-12 08:20  amit <[email protected]>
  0 siblings, 0 replies; 110+ messages in thread

From: amit @ 2016-07-12 08:20 UTC (permalink / raw)

Takes care of both the partition key deparse stuff and the new relkind.
---
 src/backend/utils/adt/ruleutils.c          |  147 ++++++++++++++++++++++++++++
 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/pg_proc.h              |    2 +
 src/include/utils/builtins.h               |    1 +
 src/test/regress/expected/create_table.out |   26 +++++
 src/test/regress/sql/create_table.sql      |   13 +++
 9 files changed, 267 insertions(+), 26 deletions(-)

diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index ec966c7..4691b5b 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, bool missing_ok);
+static char *pg_get_partkeydef_worker(Oid relId, int prettyFlags);
 static char *pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 							int prettyFlags, bool missing_ok);
 static text *pg_get_expr_worker(text *expr, Oid relid, const char *relname,
@@ -1389,6 +1391,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 [ 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/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 4ee10fc..a127672 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -1253,9 +1253,10 @@ expand_table_name_patterns(Archive *fout,
 						  "SELECT c.oid"
 						  "\nFROM pg_catalog.pg_class c"
 		"\n     LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace"
-					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c')\n",
+					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c')\n",
 						  RELKIND_RELATION, RELKIND_SEQUENCE, RELKIND_VIEW,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_REL);
 		processSQLNamePattern(GetConnection(fout), query, cell->val, true,
 							  false, "n.nspname", "c.relname", NULL,
 							  "pg_catalog.pg_table_is_visible(c.oid)");
@@ -2117,6 +2118,9 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo, bool oids)
 	/* Skip FOREIGN TABLEs (no data to dump) */
 	if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 		return;
+	/* Skip partitioned tables (data in partitions) */
+	if (tbinfo->relkind == RELKIND_PARTITIONED_REL)
+		return;
 
 	/* Don't dump data in unlogged tables, if so requested */
 	if (tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED &&
@@ -5195,6 +5199,7 @@ getTables(Archive *fout, int *numTables)
 	int			i_reloftype;
 	int			i_relpages;
 	int			i_changed_acl;
+	int			i_partkeydef;
 
 	/* Make sure we are in proper schema */
 	selectSourceSchema(fout, "pg_catalog");
@@ -5280,7 +5285,8 @@ getTables(Archive *fout, int *numTables)
 						  "OR %s IS NOT NULL "
 						  "OR %s IS NOT NULL"
 						  "))"
-						  "AS changed_acl "
+						  "AS changed_acl, "
+						  "CASE WHEN c.relkind = 'P' THEN pg_catalog.pg_get_partkeydef(c.oid) ELSE NULL END AS partkeydef "
 						  "FROM pg_class c "
 						  "LEFT JOIN pg_depend d ON "
 						  "(c.relkind = '%c' AND "
@@ -5292,7 +5298,7 @@ getTables(Archive *fout, int *numTables)
 						  "(c.oid = pip.objoid "
 						  "AND pip.classoid = 'pg_class'::regclass "
 						  "AND pip.objsubid = 0) "
-				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c') "
+				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c', '%c') "
 						  "ORDER BY c.oid",
 						  acl_subquery->data,
 						  racl_subquery->data,
@@ -5306,7 +5312,8 @@ getTables(Archive *fout, int *numTables)
 						  RELKIND_SEQUENCE,
 						  RELKIND_RELATION, RELKIND_SEQUENCE,
 						  RELKIND_VIEW, RELKIND_COMPOSITE_TYPE,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_REL);
 
 		destroyPQExpBuffer(acl_subquery);
 		destroyPQExpBuffer(racl_subquery);
@@ -5868,6 +5875,7 @@ getTables(Archive *fout, int *numTables)
 	i_toastreloptions = PQfnumber(res, "toast_reloptions");
 	i_reloftype = PQfnumber(res, "reloftype");
 	i_changed_acl = PQfnumber(res, "changed_acl");
+	i_partkeydef = PQfnumber(res, "partkeydef");
 
 	if (dopt->lockWaitTimeout && fout->remoteVersion >= 70300)
 	{
@@ -5938,6 +5946,7 @@ getTables(Archive *fout, int *numTables)
 		else
 			tblinfo[i].checkoption = pg_strdup(PQgetvalue(res, i, i_checkoption));
 		tblinfo[i].toast_reloptions = pg_strdup(PQgetvalue(res, i, i_toastreloptions));
+		tblinfo[i].partkeydef = pg_strdup(PQgetvalue(res, i, i_partkeydef));
 
 		/* other fields were zeroed above */
 
@@ -5982,7 +5991,9 @@ getTables(Archive *fout, int *numTables)
 		 * We only need to lock the table for certain components; see
 		 * pg_dump.h
 		 */
-		if (tblinfo[i].dobj.dump && tblinfo[i].relkind == RELKIND_RELATION &&
+		if (tblinfo[i].dobj.dump &&
+			(tblinfo[i].relkind == RELKIND_RELATION ||
+			 tblinfo->relkind == RELKIND_PARTITIONED_REL) &&
 			(tblinfo[i].dobj.dump & DUMP_COMPONENTS_REQUIRING_LOCK))
 		{
 			resetPQExpBuffer(query);
@@ -6083,7 +6094,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);
 
@@ -15436,6 +15450,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));
 		}
@@ -15496,6 +15513,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++)
@@ -15514,7 +15532,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
@@ -15731,6 +15750,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 	 * dump properties we only have ALTER TABLE syntax for
 	 */
 	if ((tbinfo->relkind == RELKIND_RELATION ||
+		 tbinfo->relkind == RELKIND_PARTITIONED_REL ||
 		 tbinfo->relkind == RELKIND_MATVIEW) &&
 		tbinfo->relreplident != REPLICA_IDENTITY_DEFAULT)
 	{
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 2bfa2d9..0292859 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -310,6 +310,7 @@ typedef struct _tableInfo
 	bool	   *inhNotNull;		/* true if NOT NULL is inherited */
 	struct _attrDefInfo **attrdefs;		/* DEFAULT expressions */
 	struct _constraintInfo *checkexprs; /* CHECK constraints */
+	char	   *partkeydef;		/* partition key definition */
 
 	/*
 	 * Stuff computed only for dumpable tables.
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 27be102..3ba8a90 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -856,6 +856,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"),
@@ -865,6 +866,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");
@@ -911,7 +913,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
@@ -1555,8 +1557,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)");
 	}
 
@@ -1621,6 +1623,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\"",
@@ -1634,8 +1644,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");
@@ -1655,12 +1665,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");
 	}
 
@@ -1760,7 +1770,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);
@@ -1768,14 +1778,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 */
@@ -1914,7 +1943,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;
@@ -2473,7 +2502,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;
@@ -2684,7 +2713,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
@@ -3018,6 +3047,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"),
@@ -3029,6 +3059,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"));
 
@@ -3067,7 +3098,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 
 	appendPQExpBufferStr(&buf, "\nWHERE c.relkind IN (");
 	if (showTables)
-		appendPQExpBufferStr(&buf, "'r',");
+		appendPQExpBufferStr(&buf, "'r', 'P',");
 	if (showViews)
 		appendPQExpBufferStr(&buf, "'v',");
 	if (showMatViews)
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 8469d9f..cd4eb7c 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -427,7 +427,7 @@ static const SchemaQuery Query_for_list_of_tables = {
 	/* catname */
 	"pg_catalog.pg_class c",
 	/* selcondition */
-	"c.relkind IN ('r')",
+	"c.relkind IN ('r', 'P')",
 	/* viscondition */
 	"pg_catalog.pg_table_is_visible(c.oid)",
 	/* namespace */
@@ -458,7 +458,7 @@ static const SchemaQuery Query_for_list_of_updatables = {
 	/* catname */
 	"pg_catalog.pg_class c",
 	/* selcondition */
-	"c.relkind IN ('r', 'f', 'v')",
+	"c.relkind IN ('r', 'f', 'v', 'P')",
 	/* viscondition */
 	"pg_catalog.pg_table_is_visible(c.oid)",
 	/* namespace */
@@ -488,7 +488,7 @@ static const SchemaQuery Query_for_list_of_tsvmf = {
 	/* catname */
 	"pg_catalog.pg_class c",
 	/* selcondition */
-	"c.relkind IN ('r', 'S', 'v', 'm', 'f')",
+	"c.relkind IN ('r', 'S', 'v', 'm', 'f', 'P')",
 	/* viscondition */
 	"pg_catalog.pg_table_is_visible(c.oid)",
 	/* namespace */
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index 270dd21..5a444d9 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/utils/builtins.h b/src/include/utils/builtins.h
index 8cebc86..351bfd3 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -722,6 +722,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/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index fb7a1b3..4fc8c06 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -413,3 +413,29 @@ SELECT connoinherit FROM pg_constraint WHERE conrelid = 'no_inh_con_parted'::reg
 (1 row)
 
 DROP TABLE no_inh_con_parted;
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+Partitioned table "public.describe_range_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition Key: PARTITION BY RANGE (((a + b)))
+
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+Partitioned table "public.describe_list_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition Key: PARTITION BY LIST (a)
+
+DROP TABLE describe_range_key, describe_list_key;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index cb17517..7c7a18a 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -404,3 +404,16 @@ CREATE TABLE no_inh_con_parted (
 ) PARTITION BY RANGE (a);
 SELECT connoinherit FROM pg_constraint WHERE conrelid = 'no_inh_con_parted'::regclass;
 DROP TABLE no_inh_con_parted;
+
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+DROP TABLE describe_range_key, describe_list_key;
-- 
1.7.1


--------------8C4C48E640631A327D3D2826
Content-Type: text/x-diff;
 name="0003-Catalog-and-DDL-for-partitions.patch"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
 filename="0003-Catalog-and-DDL-for-partitions.patch"



^ permalink  raw  reply  [nested|flat] 110+ messages in thread

* [PATCH 2/9] psql and pg_dump support for partitioned tables.
@ 2016-07-12 08:20  amit <[email protected]>
  0 siblings, 0 replies; 110+ messages in thread

From: amit @ 2016-07-12 08:20 UTC (permalink / raw)

Takes care of both the partition key deparse stuff and the new relkind.
---
 src/backend/utils/adt/ruleutils.c          |  140 ++++++++++++++++++++++++++++
 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/pg_proc.h              |    2 +
 src/include/utils/builtins.h               |    1 +
 src/test/regress/expected/create_table.out |   26 +++++
 src/test/regress/sql/create_table.sql      |   13 +++
 9 files changed, 260 insertions(+), 26 deletions(-)

diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 8a81d7a..77ce807 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_table.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
@@ -315,6 +316,7 @@ static char *pg_get_indexdef_worker(Oid indexrelid, int colno,
 					   const Oid *excludeOps,
 					   bool attrsOnly, bool showTblSpc,
 					   int prettyFlags, bool missing_ok);
+static char *pg_get_partkeydef_worker(Oid relId, int prettyFlags);
 static char *pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 							int prettyFlags, bool missing_ok);
 static text *pg_get_expr_worker(text *expr, Oid relid, const char *relname,
@@ -1389,6 +1391,144 @@ 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 [ 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_table	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_table) GETSTRUCT(tuple);
+
+	Assert(form->partedrelid == relId);
+
+	/* Must get partclass, and partexprs the hard way */
+	datum = SysCacheGetAttr(PARTEDRELID, tuple,
+							Anum_pg_partitioned_table_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_table_partexprbin))
+	{
+		Datum		exprsDatum;
+		bool		isnull;
+		char	   *exprsString;
+
+		exprsDatum = SysCacheGetAttr(PARTEDRELID, tuple,
+									 Anum_pg_partitioned_table_partexprbin, &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);
+
+	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,
+											0, 0);
+
+			appendStringInfoString(&buf, 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/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index a5c2d09..c418ba7 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -1253,9 +1253,10 @@ expand_table_name_patterns(Archive *fout,
 						  "SELECT c.oid"
 						  "\nFROM pg_catalog.pg_class c"
 		"\n     LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace"
-					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c')\n",
+					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c')\n",
 						  RELKIND_RELATION, RELKIND_SEQUENCE, RELKIND_VIEW,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 		processSQLNamePattern(GetConnection(fout), query, cell->val, true,
 							  false, "n.nspname", "c.relname", NULL,
 							  "pg_catalog.pg_table_is_visible(c.oid)");
@@ -2117,6 +2118,9 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo, bool oids)
 	/* Skip FOREIGN TABLEs (no data to dump) */
 	if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 		return;
+	/* Skip partitioned tables (data in partitions) */
+	if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+		return;
 
 	/* Don't dump data in unlogged tables, if so requested */
 	if (tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED &&
@@ -5195,6 +5199,7 @@ getTables(Archive *fout, int *numTables)
 	int			i_reloftype;
 	int			i_relpages;
 	int			i_changed_acl;
+	int			i_partkeydef;
 
 	/* Make sure we are in proper schema */
 	selectSourceSchema(fout, "pg_catalog");
@@ -5280,7 +5285,8 @@ getTables(Archive *fout, int *numTables)
 						  "OR %s IS NOT NULL "
 						  "OR %s IS NOT NULL"
 						  "))"
-						  "AS changed_acl "
+						  "AS changed_acl, "
+						  "CASE WHEN c.relkind = 'P' THEN pg_catalog.pg_get_partkeydef(c.oid) ELSE NULL END AS partkeydef "
 						  "FROM pg_class c "
 						  "LEFT JOIN pg_depend d ON "
 						  "(c.relkind = '%c' AND "
@@ -5292,7 +5298,7 @@ getTables(Archive *fout, int *numTables)
 						  "(c.oid = pip.objoid "
 						  "AND pip.classoid = 'pg_class'::regclass "
 						  "AND pip.objsubid = 0) "
-				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c') "
+				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c', '%c') "
 						  "ORDER BY c.oid",
 						  acl_subquery->data,
 						  racl_subquery->data,
@@ -5306,7 +5312,8 @@ getTables(Archive *fout, int *numTables)
 						  RELKIND_SEQUENCE,
 						  RELKIND_RELATION, RELKIND_SEQUENCE,
 						  RELKIND_VIEW, RELKIND_COMPOSITE_TYPE,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 
 		destroyPQExpBuffer(acl_subquery);
 		destroyPQExpBuffer(racl_subquery);
@@ -5868,6 +5875,7 @@ getTables(Archive *fout, int *numTables)
 	i_toastreloptions = PQfnumber(res, "toast_reloptions");
 	i_reloftype = PQfnumber(res, "reloftype");
 	i_changed_acl = PQfnumber(res, "changed_acl");
+	i_partkeydef = PQfnumber(res, "partkeydef");
 
 	if (dopt->lockWaitTimeout && fout->remoteVersion >= 70300)
 	{
@@ -5938,6 +5946,7 @@ getTables(Archive *fout, int *numTables)
 		else
 			tblinfo[i].checkoption = pg_strdup(PQgetvalue(res, i, i_checkoption));
 		tblinfo[i].toast_reloptions = pg_strdup(PQgetvalue(res, i, i_toastreloptions));
+		tblinfo[i].partkeydef = pg_strdup(PQgetvalue(res, i, i_partkeydef));
 
 		/* other fields were zeroed above */
 
@@ -5982,7 +5991,9 @@ getTables(Archive *fout, int *numTables)
 		 * We only need to lock the table for certain components; see
 		 * pg_dump.h
 		 */
-		if (tblinfo[i].dobj.dump && tblinfo[i].relkind == RELKIND_RELATION &&
+		if (tblinfo[i].dobj.dump &&
+			(tblinfo[i].relkind == RELKIND_RELATION ||
+			 tblinfo->relkind == RELKIND_PARTITIONED_TABLE) &&
 			(tblinfo[i].dobj.dump & DUMP_COMPONENTS_REQUIRING_LOCK))
 		{
 			resetPQExpBuffer(query);
@@ -6084,7 +6095,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);
 
@@ -15437,6 +15451,9 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 				appendPQExpBufferChar(q, ')');
 			}
 
+			if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+				appendPQExpBuffer(q, "\nPARTITION BY %s", tbinfo->partkeydef);
+
 			if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 				appendPQExpBuffer(q, "\nSERVER %s", fmtId(srvname));
 		}
@@ -15497,6 +15514,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 		 */
 		if (dopt->binary_upgrade &&
 			(tbinfo->relkind == RELKIND_RELATION ||
+			 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 			 tbinfo->relkind == RELKIND_FOREIGN_TABLE))
 		{
 			for (j = 0; j < tbinfo->numatts; j++)
@@ -15515,7 +15533,8 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 					appendStringLiteralAH(q, fmtId(tbinfo->dobj.name), fout);
 					appendPQExpBufferStr(q, "::pg_catalog.regclass;\n");
 
-					if (tbinfo->relkind == RELKIND_RELATION)
+					if (tbinfo->relkind == RELKIND_RELATION ||
+						tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
 						appendPQExpBuffer(q, "ALTER TABLE ONLY %s ",
 										  fmtId(tbinfo->dobj.name));
 					else
@@ -15732,6 +15751,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 	 * dump properties we only have ALTER TABLE syntax for
 	 */
 	if ((tbinfo->relkind == RELKIND_RELATION ||
+		 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 		 tbinfo->relkind == RELKIND_MATVIEW) &&
 		tbinfo->relreplident != REPLICA_IDENTITY_DEFAULT)
 	{
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 2bfa2d9..0292859 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -310,6 +310,7 @@ typedef struct _tableInfo
 	bool	   *inhNotNull;		/* true if NOT NULL is inherited */
 	struct _attrDefInfo **attrdefs;		/* DEFAULT expressions */
 	struct _constraintInfo *checkexprs; /* CHECK constraints */
+	char	   *partkeydef;		/* partition key definition */
 
 	/*
 	 * Stuff computed only for dumpable tables.
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 6275a68..10d924a 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -861,6 +861,7 @@ permissionsList(const char *pattern)
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'S' THEN '%s'"
@@ -870,6 +871,7 @@ permissionsList(const char *pattern)
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("sequence"),
@@ -920,7 +922,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
@@ -1567,8 +1569,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)");
 	}
 
@@ -1633,6 +1635,14 @@ describeOneTableDetails(const char *schemaname,
 			printfPQExpBuffer(&title, _("Foreign table \"%s.%s\""),
 							  schemaname, relationname);
 			break;
+		case 'P':
+			if (tableinfo.relpersistence == 'u')
+				printfPQExpBuffer(&title, _("Unlogged table \"%s.%s\""),
+								  schemaname, relationname);
+			else
+				printfPQExpBuffer(&title, _("Table \"%s.%s\""),
+								  schemaname, relationname);
+			break;
 		default:
 			/* untranslated unknown relkind */
 			printfPQExpBuffer(&title, "?%c? \"%s.%s\"",
@@ -1646,8 +1656,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");
@@ -1667,12 +1677,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");
 	}
 
@@ -1772,7 +1782,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);
@@ -1780,14 +1790,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 (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 */
@@ -1926,7 +1955,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;
@@ -2485,7 +2514,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;
@@ -2696,7 +2725,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
@@ -3024,6 +3053,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'i' THEN '%s'"
@@ -3035,6 +3065,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("index"),
@@ -3079,7 +3110,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 1345e4e..4686cd1 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/pg_proc.h b/src/include/catalog/pg_proc.h
index e2d08ba..b45688b 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -1980,6 +1980,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/utils/builtins.h b/src/include/utils/builtins.h
index 2ae212a..e800647 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -729,6 +729,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/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 708232d..2fec847 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -408,3 +408,29 @@ CREATE TABLE no_inh_con_parted (
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
 ERROR:  cannot add NO INHERIT constraint to partitioned table "no_inh_con_parted"
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+Table "public.describe_range_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition Key: RANGE ((a + b))
+
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+Table "public.describe_list_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition Key: LIST (a)
+
+DROP TABLE describe_range_key, describe_list_key;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 6e4a8be..4dd6a0a 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -402,3 +402,16 @@ CREATE TABLE no_inh_con_parted (
 	a int,
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
+
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+DROP TABLE describe_range_key, describe_list_key;
-- 
1.7.1


--------------954458C17BBEB1BF6FC758E7
Content-Type: text/x-diff;
 name="0003-Catalog-and-DDL-for-partitions-2.patch"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
 filename="0003-Catalog-and-DDL-for-partitions-2.patch"



^ permalink  raw  reply  [nested|flat] 110+ messages in thread

* [PATCH 2/9] psql and pg_dump support for partitioned tables.
@ 2016-07-12 08:20  amit <[email protected]>
  0 siblings, 0 replies; 110+ messages in thread

From: amit @ 2016-07-12 08:20 UTC (permalink / raw)

Takes care of both the partition key deparse stuff and the new relkind.
---
 src/backend/utils/adt/ruleutils.c          |  140 ++++++++++++++++++++++++++++
 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/pg_proc.h              |    2 +
 src/include/utils/builtins.h               |    1 +
 src/test/regress/expected/create_table.out |   26 +++++
 src/test/regress/sql/create_table.sql      |   13 +++
 9 files changed, 260 insertions(+), 26 deletions(-)

diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 8a81d7a..77ce807 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_table.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
@@ -315,6 +316,7 @@ static char *pg_get_indexdef_worker(Oid indexrelid, int colno,
 					   const Oid *excludeOps,
 					   bool attrsOnly, bool showTblSpc,
 					   int prettyFlags, bool missing_ok);
+static char *pg_get_partkeydef_worker(Oid relId, int prettyFlags);
 static char *pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 							int prettyFlags, bool missing_ok);
 static text *pg_get_expr_worker(text *expr, Oid relid, const char *relname,
@@ -1389,6 +1391,144 @@ 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 [ 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_table	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_table) GETSTRUCT(tuple);
+
+	Assert(form->partedrelid == relId);
+
+	/* Must get partclass, and partexprs the hard way */
+	datum = SysCacheGetAttr(PARTEDRELID, tuple,
+							Anum_pg_partitioned_table_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_table_partexprbin))
+	{
+		Datum		exprsDatum;
+		bool		isnull;
+		char	   *exprsString;
+
+		exprsDatum = SysCacheGetAttr(PARTEDRELID, tuple,
+									 Anum_pg_partitioned_table_partexprbin, &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);
+
+	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,
+											0, 0);
+
+			appendStringInfoString(&buf, 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/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index a5c2d09..c418ba7 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -1253,9 +1253,10 @@ expand_table_name_patterns(Archive *fout,
 						  "SELECT c.oid"
 						  "\nFROM pg_catalog.pg_class c"
 		"\n     LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace"
-					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c')\n",
+					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c')\n",
 						  RELKIND_RELATION, RELKIND_SEQUENCE, RELKIND_VIEW,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 		processSQLNamePattern(GetConnection(fout), query, cell->val, true,
 							  false, "n.nspname", "c.relname", NULL,
 							  "pg_catalog.pg_table_is_visible(c.oid)");
@@ -2117,6 +2118,9 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo, bool oids)
 	/* Skip FOREIGN TABLEs (no data to dump) */
 	if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 		return;
+	/* Skip partitioned tables (data in partitions) */
+	if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+		return;
 
 	/* Don't dump data in unlogged tables, if so requested */
 	if (tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED &&
@@ -5195,6 +5199,7 @@ getTables(Archive *fout, int *numTables)
 	int			i_reloftype;
 	int			i_relpages;
 	int			i_changed_acl;
+	int			i_partkeydef;
 
 	/* Make sure we are in proper schema */
 	selectSourceSchema(fout, "pg_catalog");
@@ -5280,7 +5285,8 @@ getTables(Archive *fout, int *numTables)
 						  "OR %s IS NOT NULL "
 						  "OR %s IS NOT NULL"
 						  "))"
-						  "AS changed_acl "
+						  "AS changed_acl, "
+						  "CASE WHEN c.relkind = 'P' THEN pg_catalog.pg_get_partkeydef(c.oid) ELSE NULL END AS partkeydef "
 						  "FROM pg_class c "
 						  "LEFT JOIN pg_depend d ON "
 						  "(c.relkind = '%c' AND "
@@ -5292,7 +5298,7 @@ getTables(Archive *fout, int *numTables)
 						  "(c.oid = pip.objoid "
 						  "AND pip.classoid = 'pg_class'::regclass "
 						  "AND pip.objsubid = 0) "
-				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c') "
+				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c', '%c') "
 						  "ORDER BY c.oid",
 						  acl_subquery->data,
 						  racl_subquery->data,
@@ -5306,7 +5312,8 @@ getTables(Archive *fout, int *numTables)
 						  RELKIND_SEQUENCE,
 						  RELKIND_RELATION, RELKIND_SEQUENCE,
 						  RELKIND_VIEW, RELKIND_COMPOSITE_TYPE,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 
 		destroyPQExpBuffer(acl_subquery);
 		destroyPQExpBuffer(racl_subquery);
@@ -5868,6 +5875,7 @@ getTables(Archive *fout, int *numTables)
 	i_toastreloptions = PQfnumber(res, "toast_reloptions");
 	i_reloftype = PQfnumber(res, "reloftype");
 	i_changed_acl = PQfnumber(res, "changed_acl");
+	i_partkeydef = PQfnumber(res, "partkeydef");
 
 	if (dopt->lockWaitTimeout && fout->remoteVersion >= 70300)
 	{
@@ -5938,6 +5946,7 @@ getTables(Archive *fout, int *numTables)
 		else
 			tblinfo[i].checkoption = pg_strdup(PQgetvalue(res, i, i_checkoption));
 		tblinfo[i].toast_reloptions = pg_strdup(PQgetvalue(res, i, i_toastreloptions));
+		tblinfo[i].partkeydef = pg_strdup(PQgetvalue(res, i, i_partkeydef));
 
 		/* other fields were zeroed above */
 
@@ -5982,7 +5991,9 @@ getTables(Archive *fout, int *numTables)
 		 * We only need to lock the table for certain components; see
 		 * pg_dump.h
 		 */
-		if (tblinfo[i].dobj.dump && tblinfo[i].relkind == RELKIND_RELATION &&
+		if (tblinfo[i].dobj.dump &&
+			(tblinfo[i].relkind == RELKIND_RELATION ||
+			 tblinfo->relkind == RELKIND_PARTITIONED_TABLE) &&
 			(tblinfo[i].dobj.dump & DUMP_COMPONENTS_REQUIRING_LOCK))
 		{
 			resetPQExpBuffer(query);
@@ -6084,7 +6095,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);
 
@@ -15437,6 +15451,9 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 				appendPQExpBufferChar(q, ')');
 			}
 
+			if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+				appendPQExpBuffer(q, "\nPARTITION BY %s", tbinfo->partkeydef);
+
 			if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 				appendPQExpBuffer(q, "\nSERVER %s", fmtId(srvname));
 		}
@@ -15497,6 +15514,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 		 */
 		if (dopt->binary_upgrade &&
 			(tbinfo->relkind == RELKIND_RELATION ||
+			 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 			 tbinfo->relkind == RELKIND_FOREIGN_TABLE))
 		{
 			for (j = 0; j < tbinfo->numatts; j++)
@@ -15515,7 +15533,8 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 					appendStringLiteralAH(q, fmtId(tbinfo->dobj.name), fout);
 					appendPQExpBufferStr(q, "::pg_catalog.regclass;\n");
 
-					if (tbinfo->relkind == RELKIND_RELATION)
+					if (tbinfo->relkind == RELKIND_RELATION ||
+						tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
 						appendPQExpBuffer(q, "ALTER TABLE ONLY %s ",
 										  fmtId(tbinfo->dobj.name));
 					else
@@ -15732,6 +15751,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 	 * dump properties we only have ALTER TABLE syntax for
 	 */
 	if ((tbinfo->relkind == RELKIND_RELATION ||
+		 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 		 tbinfo->relkind == RELKIND_MATVIEW) &&
 		tbinfo->relreplident != REPLICA_IDENTITY_DEFAULT)
 	{
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 2bfa2d9..0292859 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -310,6 +310,7 @@ typedef struct _tableInfo
 	bool	   *inhNotNull;		/* true if NOT NULL is inherited */
 	struct _attrDefInfo **attrdefs;		/* DEFAULT expressions */
 	struct _constraintInfo *checkexprs; /* CHECK constraints */
+	char	   *partkeydef;		/* partition key definition */
 
 	/*
 	 * Stuff computed only for dumpable tables.
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 6275a68..10d924a 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -861,6 +861,7 @@ permissionsList(const char *pattern)
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'S' THEN '%s'"
@@ -870,6 +871,7 @@ permissionsList(const char *pattern)
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("sequence"),
@@ -920,7 +922,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
@@ -1567,8 +1569,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)");
 	}
 
@@ -1633,6 +1635,14 @@ describeOneTableDetails(const char *schemaname,
 			printfPQExpBuffer(&title, _("Foreign table \"%s.%s\""),
 							  schemaname, relationname);
 			break;
+		case 'P':
+			if (tableinfo.relpersistence == 'u')
+				printfPQExpBuffer(&title, _("Unlogged table \"%s.%s\""),
+								  schemaname, relationname);
+			else
+				printfPQExpBuffer(&title, _("Table \"%s.%s\""),
+								  schemaname, relationname);
+			break;
 		default:
 			/* untranslated unknown relkind */
 			printfPQExpBuffer(&title, "?%c? \"%s.%s\"",
@@ -1646,8 +1656,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");
@@ -1667,12 +1677,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");
 	}
 
@@ -1772,7 +1782,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);
@@ -1780,14 +1790,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 (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 */
@@ -1926,7 +1955,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;
@@ -2485,7 +2514,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;
@@ -2696,7 +2725,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
@@ -3024,6 +3053,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'i' THEN '%s'"
@@ -3035,6 +3065,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("index"),
@@ -3079,7 +3110,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 019f75a..5e5c370 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/pg_proc.h b/src/include/catalog/pg_proc.h
index e2d08ba..b45688b 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -1980,6 +1980,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/utils/builtins.h b/src/include/utils/builtins.h
index 2ae212a..e800647 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -729,6 +729,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/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 708232d..2fec847 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -408,3 +408,29 @@ CREATE TABLE no_inh_con_parted (
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
 ERROR:  cannot add NO INHERIT constraint to partitioned table "no_inh_con_parted"
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+Table "public.describe_range_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition Key: RANGE ((a + b))
+
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+Table "public.describe_list_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition Key: LIST (a)
+
+DROP TABLE describe_range_key, describe_list_key;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 6e4a8be..4dd6a0a 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -402,3 +402,16 @@ CREATE TABLE no_inh_con_parted (
 	a int,
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
+
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+DROP TABLE describe_range_key, describe_list_key;
-- 
1.7.1


--------------6EB5B318125CB7DB1A4FD229
Content-Type: text/x-diff;
 name="0003-Catalog-and-DDL-for-partitions-3.patch"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
 filename="0003-Catalog-and-DDL-for-partitions-3.patch"



^ permalink  raw  reply  [nested|flat] 110+ messages in thread

* [PATCH 2/9] psql and pg_dump support for partitioned tables.
@ 2016-07-12 08:20  amit <[email protected]>
  0 siblings, 0 replies; 110+ messages in thread

From: amit @ 2016-07-12 08:20 UTC (permalink / raw)

Takes care of both the partition key deparse stuff and the new relkind.
---
 src/backend/utils/adt/ruleutils.c          |  140 ++++++++++++++++++++++++++++
 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/pg_proc.h              |    2 +
 src/include/utils/builtins.h               |    1 +
 src/test/regress/expected/create_table.out |   26 +++++
 src/test/regress/sql/create_table.sql      |   13 +++
 9 files changed, 260 insertions(+), 26 deletions(-)

diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 8a81d7a..03be202 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_table.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
@@ -315,6 +316,7 @@ static char *pg_get_indexdef_worker(Oid indexrelid, int colno,
 					   const Oid *excludeOps,
 					   bool attrsOnly, bool showTblSpc,
 					   int prettyFlags, bool missing_ok);
+static char *pg_get_partkeydef_worker(Oid relid, int prettyFlags);
 static char *pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 							int prettyFlags, bool missing_ok);
 static text *pg_get_expr_worker(text *expr, Oid relid, const char *relname,
@@ -1389,6 +1391,144 @@ 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 [ opclass_name ] [, ...])
+ */
+Datum
+pg_get_partkeydef(PG_FUNCTION_ARGS)
+{
+	Oid			relid = PG_GETARG_OID(0);
+	int			prettyFlags;
+
+	prettyFlags = PRETTYFLAG_INDENT;
+	PG_RETURN_TEXT_P(string_to_text(pg_get_partkeydef_worker(relid,
+									prettyFlags)));
+}
+
+/*
+ * Internal workhorse to decompile a partition key definition.
+ */
+static char *
+pg_get_partkeydef_worker(Oid relid, int prettyFlags)
+{
+	Form_pg_partitioned_table	form;
+	HeapTuple	tuple;
+	oidvector  *partclass;
+	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_table) GETSTRUCT(tuple);
+
+	Assert(form->partrelid == relid);
+
+	/* Must get partclass, and partexprs the hard way */
+	datum = SysCacheGetAttr(PARTEDRELID, tuple,
+							Anum_pg_partitioned_table_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_table_partexprbin))
+	{
+		Datum		exprsDatum;
+		bool		isnull;
+		char	   *exprsString;
+
+		exprsDatum = SysCacheGetAttr(PARTEDRELID, tuple,
+									 Anum_pg_partitioned_table_partexprbin, &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);
+
+	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,
+											0, 0);
+
+			appendStringInfoString(&buf, 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/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index ba9c276..c805a84 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -1253,9 +1253,10 @@ expand_table_name_patterns(Archive *fout,
 						  "SELECT c.oid"
 						  "\nFROM pg_catalog.pg_class c"
 		"\n     LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace"
-					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c')\n",
+					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c')\n",
 						  RELKIND_RELATION, RELKIND_SEQUENCE, RELKIND_VIEW,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 		processSQLNamePattern(GetConnection(fout), query, cell->val, true,
 							  false, "n.nspname", "c.relname", NULL,
 							  "pg_catalog.pg_table_is_visible(c.oid)");
@@ -2125,6 +2126,9 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo, bool oids)
 	/* Skip FOREIGN TABLEs (no data to dump) */
 	if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 		return;
+	/* Skip partitioned tables (data in partitions) */
+	if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+		return;
 
 	/* Don't dump data in unlogged tables, if so requested */
 	if (tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED &&
@@ -5204,6 +5208,7 @@ getTables(Archive *fout, int *numTables)
 	int			i_reloftype;
 	int			i_relpages;
 	int			i_changed_acl;
+	int			i_partkeydef;
 
 	/* Make sure we are in proper schema */
 	selectSourceSchema(fout, "pg_catalog");
@@ -5289,7 +5294,8 @@ getTables(Archive *fout, int *numTables)
 						  "OR %s IS NOT NULL "
 						  "OR %s IS NOT NULL"
 						  "))"
-						  "AS changed_acl "
+						  "AS changed_acl, "
+						  "CASE WHEN c.relkind = 'P' THEN pg_catalog.pg_get_partkeydef(c.oid) ELSE NULL END AS partkeydef "
 						  "FROM pg_class c "
 						  "LEFT JOIN pg_depend d ON "
 						  "(c.relkind = '%c' AND "
@@ -5301,7 +5307,7 @@ getTables(Archive *fout, int *numTables)
 						  "(c.oid = pip.objoid "
 						  "AND pip.classoid = 'pg_class'::regclass "
 						  "AND pip.objsubid = 0) "
-				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c') "
+				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c', '%c') "
 						  "ORDER BY c.oid",
 						  acl_subquery->data,
 						  racl_subquery->data,
@@ -5315,7 +5321,8 @@ getTables(Archive *fout, int *numTables)
 						  RELKIND_SEQUENCE,
 						  RELKIND_RELATION, RELKIND_SEQUENCE,
 						  RELKIND_VIEW, RELKIND_COMPOSITE_TYPE,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 
 		destroyPQExpBuffer(acl_subquery);
 		destroyPQExpBuffer(racl_subquery);
@@ -5877,6 +5884,7 @@ getTables(Archive *fout, int *numTables)
 	i_toastreloptions = PQfnumber(res, "toast_reloptions");
 	i_reloftype = PQfnumber(res, "reloftype");
 	i_changed_acl = PQfnumber(res, "changed_acl");
+	i_partkeydef = PQfnumber(res, "partkeydef");
 
 	if (dopt->lockWaitTimeout && fout->remoteVersion >= 70300)
 	{
@@ -5947,6 +5955,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 */
 
@@ -5991,7 +6000,9 @@ getTables(Archive *fout, int *numTables)
 		 * We only need to lock the table for certain components; see
 		 * pg_dump.h
 		 */
-		if (tblinfo[i].dobj.dump && tblinfo[i].relkind == RELKIND_RELATION &&
+		if (tblinfo[i].dobj.dump &&
+			(tblinfo[i].relkind == RELKIND_RELATION ||
+			 tblinfo->relkind == RELKIND_PARTITIONED_TABLE) &&
 			(tblinfo[i].dobj.dump & DUMP_COMPONENTS_REQUIRING_LOCK))
 		{
 			resetPQExpBuffer(query);
@@ -6093,7 +6104,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);
 
@@ -15446,6 +15460,9 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 				appendPQExpBufferChar(q, ')');
 			}
 
+			if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+				appendPQExpBuffer(q, "\nPARTITION BY %s", tbinfo->partkeydef);
+
 			if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 				appendPQExpBuffer(q, "\nSERVER %s", fmtId(srvname));
 		}
@@ -15506,6 +15523,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 		 */
 		if (dopt->binary_upgrade &&
 			(tbinfo->relkind == RELKIND_RELATION ||
+			 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 			 tbinfo->relkind == RELKIND_FOREIGN_TABLE))
 		{
 			for (j = 0; j < tbinfo->numatts; j++)
@@ -15524,7 +15542,8 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 					appendStringLiteralAH(q, fmtId(tbinfo->dobj.name), fout);
 					appendPQExpBufferStr(q, "::pg_catalog.regclass;\n");
 
-					if (tbinfo->relkind == RELKIND_RELATION)
+					if (tbinfo->relkind == RELKIND_RELATION ||
+						tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
 						appendPQExpBuffer(q, "ALTER TABLE ONLY %s ",
 										  fmtId(tbinfo->dobj.name));
 					else
@@ -15741,6 +15760,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 	 * dump properties we only have ALTER TABLE syntax for
 	 */
 	if ((tbinfo->relkind == RELKIND_RELATION ||
+		 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 		 tbinfo->relkind == RELKIND_MATVIEW) &&
 		tbinfo->relreplident != REPLICA_IDENTITY_DEFAULT)
 	{
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 2bfa2d9..0292859 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -310,6 +310,7 @@ typedef struct _tableInfo
 	bool	   *inhNotNull;		/* true if NOT NULL is inherited */
 	struct _attrDefInfo **attrdefs;		/* DEFAULT expressions */
 	struct _constraintInfo *checkexprs; /* CHECK constraints */
+	char	   *partkeydef;		/* partition key definition */
 
 	/*
 	 * Stuff computed only for dumpable tables.
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 6275a68..10d924a 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -861,6 +861,7 @@ permissionsList(const char *pattern)
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'S' THEN '%s'"
@@ -870,6 +871,7 @@ permissionsList(const char *pattern)
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("sequence"),
@@ -920,7 +922,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
@@ -1567,8 +1569,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)");
 	}
 
@@ -1633,6 +1635,14 @@ describeOneTableDetails(const char *schemaname,
 			printfPQExpBuffer(&title, _("Foreign table \"%s.%s\""),
 							  schemaname, relationname);
 			break;
+		case 'P':
+			if (tableinfo.relpersistence == 'u')
+				printfPQExpBuffer(&title, _("Unlogged table \"%s.%s\""),
+								  schemaname, relationname);
+			else
+				printfPQExpBuffer(&title, _("Table \"%s.%s\""),
+								  schemaname, relationname);
+			break;
 		default:
 			/* untranslated unknown relkind */
 			printfPQExpBuffer(&title, "?%c? \"%s.%s\"",
@@ -1646,8 +1656,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");
@@ -1667,12 +1677,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");
 	}
 
@@ -1772,7 +1782,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);
@@ -1780,14 +1790,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 (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 */
@@ -1926,7 +1955,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;
@@ -2485,7 +2514,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;
@@ -2696,7 +2725,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
@@ -3024,6 +3053,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'i' THEN '%s'"
@@ -3035,6 +3065,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("index"),
@@ -3079,7 +3110,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 50a45eb..8284a9c 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/pg_proc.h b/src/include/catalog/pg_proc.h
index e2d08ba..b45688b 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -1980,6 +1980,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/utils/builtins.h b/src/include/utils/builtins.h
index 2ae212a..e800647 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -729,6 +729,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/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 0af1422..91ba1be 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -412,3 +412,29 @@ CREATE TABLE no_inh_con_parted (
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
 ERROR:  cannot add NO INHERIT constraint to partitioned table "no_inh_con_parted"
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+Table "public.describe_range_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition Key: RANGE ((a + b))
+
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+Table "public.describe_list_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition Key: LIST (a)
+
+DROP TABLE describe_range_key, describe_list_key;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 418ac55..b75542d 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -406,3 +406,16 @@ CREATE TABLE no_inh_con_parted (
 	a int,
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
+
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+DROP TABLE describe_range_key, describe_list_key;
-- 
1.7.1


--------------D008BA677674C4836D7B65AD
Content-Type: text/x-diff;
 name="0003-Catalog-and-DDL-for-partitions-5.patch"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
 filename="0003-Catalog-and-DDL-for-partitions-5.patch"



^ permalink  raw  reply  [nested|flat] 110+ messages in thread

* [PATCH 2/8] psql and pg_dump support for partitioned tables.
@ 2016-07-12 08:20  amit <[email protected]>
  0 siblings, 0 replies; 110+ messages in thread

From: amit @ 2016-07-12 08:20 UTC (permalink / raw)

Takes care of both the partition key deparse stuff and the new relkind.
---
 src/backend/utils/adt/ruleutils.c          |  147 ++++++++++++++++++++++++++++
 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/pg_proc.h              |    2 +
 src/include/utils/builtins.h               |    1 +
 src/test/regress/expected/create_table.out |   26 +++++
 src/test/regress/sql/create_table.sql      |   13 +++
 9 files changed, 267 insertions(+), 26 deletions(-)

diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index ec966c7..4691b5b 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, bool missing_ok);
+static char *pg_get_partkeydef_worker(Oid relId, int prettyFlags);
 static char *pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 							int prettyFlags, bool missing_ok);
 static text *pg_get_expr_worker(text *expr, Oid relid, const char *relname,
@@ -1389,6 +1391,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 [ 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/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 4ee10fc..a127672 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -1253,9 +1253,10 @@ expand_table_name_patterns(Archive *fout,
 						  "SELECT c.oid"
 						  "\nFROM pg_catalog.pg_class c"
 		"\n     LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace"
-					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c')\n",
+					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c')\n",
 						  RELKIND_RELATION, RELKIND_SEQUENCE, RELKIND_VIEW,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_REL);
 		processSQLNamePattern(GetConnection(fout), query, cell->val, true,
 							  false, "n.nspname", "c.relname", NULL,
 							  "pg_catalog.pg_table_is_visible(c.oid)");
@@ -2117,6 +2118,9 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo, bool oids)
 	/* Skip FOREIGN TABLEs (no data to dump) */
 	if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 		return;
+	/* Skip partitioned tables (data in partitions) */
+	if (tbinfo->relkind == RELKIND_PARTITIONED_REL)
+		return;
 
 	/* Don't dump data in unlogged tables, if so requested */
 	if (tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED &&
@@ -5195,6 +5199,7 @@ getTables(Archive *fout, int *numTables)
 	int			i_reloftype;
 	int			i_relpages;
 	int			i_changed_acl;
+	int			i_partkeydef;
 
 	/* Make sure we are in proper schema */
 	selectSourceSchema(fout, "pg_catalog");
@@ -5280,7 +5285,8 @@ getTables(Archive *fout, int *numTables)
 						  "OR %s IS NOT NULL "
 						  "OR %s IS NOT NULL"
 						  "))"
-						  "AS changed_acl "
+						  "AS changed_acl, "
+						  "CASE WHEN c.relkind = 'P' THEN pg_catalog.pg_get_partkeydef(c.oid) ELSE NULL END AS partkeydef "
 						  "FROM pg_class c "
 						  "LEFT JOIN pg_depend d ON "
 						  "(c.relkind = '%c' AND "
@@ -5292,7 +5298,7 @@ getTables(Archive *fout, int *numTables)
 						  "(c.oid = pip.objoid "
 						  "AND pip.classoid = 'pg_class'::regclass "
 						  "AND pip.objsubid = 0) "
-				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c') "
+				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c', '%c') "
 						  "ORDER BY c.oid",
 						  acl_subquery->data,
 						  racl_subquery->data,
@@ -5306,7 +5312,8 @@ getTables(Archive *fout, int *numTables)
 						  RELKIND_SEQUENCE,
 						  RELKIND_RELATION, RELKIND_SEQUENCE,
 						  RELKIND_VIEW, RELKIND_COMPOSITE_TYPE,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_REL);
 
 		destroyPQExpBuffer(acl_subquery);
 		destroyPQExpBuffer(racl_subquery);
@@ -5868,6 +5875,7 @@ getTables(Archive *fout, int *numTables)
 	i_toastreloptions = PQfnumber(res, "toast_reloptions");
 	i_reloftype = PQfnumber(res, "reloftype");
 	i_changed_acl = PQfnumber(res, "changed_acl");
+	i_partkeydef = PQfnumber(res, "partkeydef");
 
 	if (dopt->lockWaitTimeout && fout->remoteVersion >= 70300)
 	{
@@ -5938,6 +5946,7 @@ getTables(Archive *fout, int *numTables)
 		else
 			tblinfo[i].checkoption = pg_strdup(PQgetvalue(res, i, i_checkoption));
 		tblinfo[i].toast_reloptions = pg_strdup(PQgetvalue(res, i, i_toastreloptions));
+		tblinfo[i].partkeydef = pg_strdup(PQgetvalue(res, i, i_partkeydef));
 
 		/* other fields were zeroed above */
 
@@ -5982,7 +5991,9 @@ getTables(Archive *fout, int *numTables)
 		 * We only need to lock the table for certain components; see
 		 * pg_dump.h
 		 */
-		if (tblinfo[i].dobj.dump && tblinfo[i].relkind == RELKIND_RELATION &&
+		if (tblinfo[i].dobj.dump &&
+			(tblinfo[i].relkind == RELKIND_RELATION ||
+			 tblinfo->relkind == RELKIND_PARTITIONED_REL) &&
 			(tblinfo[i].dobj.dump & DUMP_COMPONENTS_REQUIRING_LOCK))
 		{
 			resetPQExpBuffer(query);
@@ -6083,7 +6094,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);
 
@@ -15436,6 +15450,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));
 		}
@@ -15496,6 +15513,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++)
@@ -15514,7 +15532,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
@@ -15731,6 +15750,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 	 * dump properties we only have ALTER TABLE syntax for
 	 */
 	if ((tbinfo->relkind == RELKIND_RELATION ||
+		 tbinfo->relkind == RELKIND_PARTITIONED_REL ||
 		 tbinfo->relkind == RELKIND_MATVIEW) &&
 		tbinfo->relreplident != REPLICA_IDENTITY_DEFAULT)
 	{
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 2bfa2d9..0292859 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -310,6 +310,7 @@ typedef struct _tableInfo
 	bool	   *inhNotNull;		/* true if NOT NULL is inherited */
 	struct _attrDefInfo **attrdefs;		/* DEFAULT expressions */
 	struct _constraintInfo *checkexprs; /* CHECK constraints */
+	char	   *partkeydef;		/* partition key definition */
 
 	/*
 	 * Stuff computed only for dumpable tables.
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 27be102..3ba8a90 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -856,6 +856,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"),
@@ -865,6 +866,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");
@@ -911,7 +913,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
@@ -1555,8 +1557,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)");
 	}
 
@@ -1621,6 +1623,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\"",
@@ -1634,8 +1644,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");
@@ -1655,12 +1665,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");
 	}
 
@@ -1760,7 +1770,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);
@@ -1768,14 +1778,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 */
@@ -1914,7 +1943,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;
@@ -2473,7 +2502,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;
@@ -2684,7 +2713,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
@@ -3018,6 +3047,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"),
@@ -3029,6 +3059,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"));
 
@@ -3067,7 +3098,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 
 	appendPQExpBufferStr(&buf, "\nWHERE c.relkind IN (");
 	if (showTables)
-		appendPQExpBufferStr(&buf, "'r',");
+		appendPQExpBufferStr(&buf, "'r', 'P',");
 	if (showViews)
 		appendPQExpBufferStr(&buf, "'v',");
 	if (showMatViews)
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 8469d9f..cd4eb7c 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -427,7 +427,7 @@ static const SchemaQuery Query_for_list_of_tables = {
 	/* catname */
 	"pg_catalog.pg_class c",
 	/* selcondition */
-	"c.relkind IN ('r')",
+	"c.relkind IN ('r', 'P')",
 	/* viscondition */
 	"pg_catalog.pg_table_is_visible(c.oid)",
 	/* namespace */
@@ -458,7 +458,7 @@ static const SchemaQuery Query_for_list_of_updatables = {
 	/* catname */
 	"pg_catalog.pg_class c",
 	/* selcondition */
-	"c.relkind IN ('r', 'f', 'v')",
+	"c.relkind IN ('r', 'f', 'v', 'P')",
 	/* viscondition */
 	"pg_catalog.pg_table_is_visible(c.oid)",
 	/* namespace */
@@ -488,7 +488,7 @@ static const SchemaQuery Query_for_list_of_tsvmf = {
 	/* catname */
 	"pg_catalog.pg_class c",
 	/* selcondition */
-	"c.relkind IN ('r', 'S', 'v', 'm', 'f')",
+	"c.relkind IN ('r', 'S', 'v', 'm', 'f', 'P')",
 	/* viscondition */
 	"pg_catalog.pg_table_is_visible(c.oid)",
 	/* namespace */
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index 270dd21..5a444d9 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/utils/builtins.h b/src/include/utils/builtins.h
index 8cebc86..351bfd3 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -722,6 +722,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/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index fb7a1b3..4fc8c06 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -413,3 +413,29 @@ SELECT connoinherit FROM pg_constraint WHERE conrelid = 'no_inh_con_parted'::reg
 (1 row)
 
 DROP TABLE no_inh_con_parted;
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+Partitioned table "public.describe_range_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition Key: PARTITION BY RANGE (((a + b)))
+
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+Partitioned table "public.describe_list_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition Key: PARTITION BY LIST (a)
+
+DROP TABLE describe_range_key, describe_list_key;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index cb17517..7c7a18a 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -404,3 +404,16 @@ CREATE TABLE no_inh_con_parted (
 ) PARTITION BY RANGE (a);
 SELECT connoinherit FROM pg_constraint WHERE conrelid = 'no_inh_con_parted'::regclass;
 DROP TABLE no_inh_con_parted;
+
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+DROP TABLE describe_range_key, describe_list_key;
-- 
1.7.1


--------------8C4C48E640631A327D3D2826
Content-Type: text/x-diff;
 name="0003-Catalog-and-DDL-for-partitions.patch"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
 filename="0003-Catalog-and-DDL-for-partitions.patch"



^ permalink  raw  reply  [nested|flat] 110+ messages in thread

* [PATCH 2/9] psql and pg_dump support for partitioned tables.
@ 2016-07-12 08:20  amit <[email protected]>
  0 siblings, 0 replies; 110+ messages in thread

From: amit @ 2016-07-12 08:20 UTC (permalink / raw)

Takes care of both the partition key deparse stuff and the new relkind.
---
 src/backend/utils/adt/ruleutils.c          |  140 ++++++++++++++++++++++++++++
 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/pg_proc.h              |    2 +
 src/include/utils/builtins.h               |    1 +
 src/test/regress/expected/create_table.out |   26 +++++
 src/test/regress/sql/create_table.sql      |   13 +++
 9 files changed, 260 insertions(+), 26 deletions(-)

diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 8a81d7a..77ce807 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_table.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
@@ -315,6 +316,7 @@ static char *pg_get_indexdef_worker(Oid indexrelid, int colno,
 					   const Oid *excludeOps,
 					   bool attrsOnly, bool showTblSpc,
 					   int prettyFlags, bool missing_ok);
+static char *pg_get_partkeydef_worker(Oid relId, int prettyFlags);
 static char *pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 							int prettyFlags, bool missing_ok);
 static text *pg_get_expr_worker(text *expr, Oid relid, const char *relname,
@@ -1389,6 +1391,144 @@ 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 [ 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_table	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_table) GETSTRUCT(tuple);
+
+	Assert(form->partedrelid == relId);
+
+	/* Must get partclass, and partexprs the hard way */
+	datum = SysCacheGetAttr(PARTEDRELID, tuple,
+							Anum_pg_partitioned_table_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_table_partexprbin))
+	{
+		Datum		exprsDatum;
+		bool		isnull;
+		char	   *exprsString;
+
+		exprsDatum = SysCacheGetAttr(PARTEDRELID, tuple,
+									 Anum_pg_partitioned_table_partexprbin, &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);
+
+	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,
+											0, 0);
+
+			appendStringInfoString(&buf, 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/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index a5c2d09..c418ba7 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -1253,9 +1253,10 @@ expand_table_name_patterns(Archive *fout,
 						  "SELECT c.oid"
 						  "\nFROM pg_catalog.pg_class c"
 		"\n     LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace"
-					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c')\n",
+					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c')\n",
 						  RELKIND_RELATION, RELKIND_SEQUENCE, RELKIND_VIEW,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 		processSQLNamePattern(GetConnection(fout), query, cell->val, true,
 							  false, "n.nspname", "c.relname", NULL,
 							  "pg_catalog.pg_table_is_visible(c.oid)");
@@ -2117,6 +2118,9 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo, bool oids)
 	/* Skip FOREIGN TABLEs (no data to dump) */
 	if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 		return;
+	/* Skip partitioned tables (data in partitions) */
+	if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+		return;
 
 	/* Don't dump data in unlogged tables, if so requested */
 	if (tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED &&
@@ -5195,6 +5199,7 @@ getTables(Archive *fout, int *numTables)
 	int			i_reloftype;
 	int			i_relpages;
 	int			i_changed_acl;
+	int			i_partkeydef;
 
 	/* Make sure we are in proper schema */
 	selectSourceSchema(fout, "pg_catalog");
@@ -5280,7 +5285,8 @@ getTables(Archive *fout, int *numTables)
 						  "OR %s IS NOT NULL "
 						  "OR %s IS NOT NULL"
 						  "))"
-						  "AS changed_acl "
+						  "AS changed_acl, "
+						  "CASE WHEN c.relkind = 'P' THEN pg_catalog.pg_get_partkeydef(c.oid) ELSE NULL END AS partkeydef "
 						  "FROM pg_class c "
 						  "LEFT JOIN pg_depend d ON "
 						  "(c.relkind = '%c' AND "
@@ -5292,7 +5298,7 @@ getTables(Archive *fout, int *numTables)
 						  "(c.oid = pip.objoid "
 						  "AND pip.classoid = 'pg_class'::regclass "
 						  "AND pip.objsubid = 0) "
-				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c') "
+				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c', '%c') "
 						  "ORDER BY c.oid",
 						  acl_subquery->data,
 						  racl_subquery->data,
@@ -5306,7 +5312,8 @@ getTables(Archive *fout, int *numTables)
 						  RELKIND_SEQUENCE,
 						  RELKIND_RELATION, RELKIND_SEQUENCE,
 						  RELKIND_VIEW, RELKIND_COMPOSITE_TYPE,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 
 		destroyPQExpBuffer(acl_subquery);
 		destroyPQExpBuffer(racl_subquery);
@@ -5868,6 +5875,7 @@ getTables(Archive *fout, int *numTables)
 	i_toastreloptions = PQfnumber(res, "toast_reloptions");
 	i_reloftype = PQfnumber(res, "reloftype");
 	i_changed_acl = PQfnumber(res, "changed_acl");
+	i_partkeydef = PQfnumber(res, "partkeydef");
 
 	if (dopt->lockWaitTimeout && fout->remoteVersion >= 70300)
 	{
@@ -5938,6 +5946,7 @@ getTables(Archive *fout, int *numTables)
 		else
 			tblinfo[i].checkoption = pg_strdup(PQgetvalue(res, i, i_checkoption));
 		tblinfo[i].toast_reloptions = pg_strdup(PQgetvalue(res, i, i_toastreloptions));
+		tblinfo[i].partkeydef = pg_strdup(PQgetvalue(res, i, i_partkeydef));
 
 		/* other fields were zeroed above */
 
@@ -5982,7 +5991,9 @@ getTables(Archive *fout, int *numTables)
 		 * We only need to lock the table for certain components; see
 		 * pg_dump.h
 		 */
-		if (tblinfo[i].dobj.dump && tblinfo[i].relkind == RELKIND_RELATION &&
+		if (tblinfo[i].dobj.dump &&
+			(tblinfo[i].relkind == RELKIND_RELATION ||
+			 tblinfo->relkind == RELKIND_PARTITIONED_TABLE) &&
 			(tblinfo[i].dobj.dump & DUMP_COMPONENTS_REQUIRING_LOCK))
 		{
 			resetPQExpBuffer(query);
@@ -6084,7 +6095,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);
 
@@ -15437,6 +15451,9 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 				appendPQExpBufferChar(q, ')');
 			}
 
+			if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+				appendPQExpBuffer(q, "\nPARTITION BY %s", tbinfo->partkeydef);
+
 			if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 				appendPQExpBuffer(q, "\nSERVER %s", fmtId(srvname));
 		}
@@ -15497,6 +15514,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 		 */
 		if (dopt->binary_upgrade &&
 			(tbinfo->relkind == RELKIND_RELATION ||
+			 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 			 tbinfo->relkind == RELKIND_FOREIGN_TABLE))
 		{
 			for (j = 0; j < tbinfo->numatts; j++)
@@ -15515,7 +15533,8 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 					appendStringLiteralAH(q, fmtId(tbinfo->dobj.name), fout);
 					appendPQExpBufferStr(q, "::pg_catalog.regclass;\n");
 
-					if (tbinfo->relkind == RELKIND_RELATION)
+					if (tbinfo->relkind == RELKIND_RELATION ||
+						tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
 						appendPQExpBuffer(q, "ALTER TABLE ONLY %s ",
 										  fmtId(tbinfo->dobj.name));
 					else
@@ -15732,6 +15751,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 	 * dump properties we only have ALTER TABLE syntax for
 	 */
 	if ((tbinfo->relkind == RELKIND_RELATION ||
+		 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 		 tbinfo->relkind == RELKIND_MATVIEW) &&
 		tbinfo->relreplident != REPLICA_IDENTITY_DEFAULT)
 	{
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 2bfa2d9..0292859 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -310,6 +310,7 @@ typedef struct _tableInfo
 	bool	   *inhNotNull;		/* true if NOT NULL is inherited */
 	struct _attrDefInfo **attrdefs;		/* DEFAULT expressions */
 	struct _constraintInfo *checkexprs; /* CHECK constraints */
+	char	   *partkeydef;		/* partition key definition */
 
 	/*
 	 * Stuff computed only for dumpable tables.
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 6275a68..10d924a 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -861,6 +861,7 @@ permissionsList(const char *pattern)
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'S' THEN '%s'"
@@ -870,6 +871,7 @@ permissionsList(const char *pattern)
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("sequence"),
@@ -920,7 +922,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
@@ -1567,8 +1569,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)");
 	}
 
@@ -1633,6 +1635,14 @@ describeOneTableDetails(const char *schemaname,
 			printfPQExpBuffer(&title, _("Foreign table \"%s.%s\""),
 							  schemaname, relationname);
 			break;
+		case 'P':
+			if (tableinfo.relpersistence == 'u')
+				printfPQExpBuffer(&title, _("Unlogged table \"%s.%s\""),
+								  schemaname, relationname);
+			else
+				printfPQExpBuffer(&title, _("Table \"%s.%s\""),
+								  schemaname, relationname);
+			break;
 		default:
 			/* untranslated unknown relkind */
 			printfPQExpBuffer(&title, "?%c? \"%s.%s\"",
@@ -1646,8 +1656,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");
@@ -1667,12 +1677,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");
 	}
 
@@ -1772,7 +1782,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);
@@ -1780,14 +1790,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 (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 */
@@ -1926,7 +1955,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;
@@ -2485,7 +2514,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;
@@ -2696,7 +2725,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
@@ -3024,6 +3053,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'i' THEN '%s'"
@@ -3035,6 +3065,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("index"),
@@ -3079,7 +3110,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 1345e4e..4686cd1 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/pg_proc.h b/src/include/catalog/pg_proc.h
index e2d08ba..b45688b 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -1980,6 +1980,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/utils/builtins.h b/src/include/utils/builtins.h
index 2ae212a..e800647 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -729,6 +729,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/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 708232d..2fec847 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -408,3 +408,29 @@ CREATE TABLE no_inh_con_parted (
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
 ERROR:  cannot add NO INHERIT constraint to partitioned table "no_inh_con_parted"
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+Table "public.describe_range_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition Key: RANGE ((a + b))
+
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+Table "public.describe_list_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition Key: LIST (a)
+
+DROP TABLE describe_range_key, describe_list_key;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 6e4a8be..4dd6a0a 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -402,3 +402,16 @@ CREATE TABLE no_inh_con_parted (
 	a int,
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
+
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+DROP TABLE describe_range_key, describe_list_key;
-- 
1.7.1


--------------954458C17BBEB1BF6FC758E7
Content-Type: text/x-diff;
 name="0003-Catalog-and-DDL-for-partitions-2.patch"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
 filename="0003-Catalog-and-DDL-for-partitions-2.patch"



^ permalink  raw  reply  [nested|flat] 110+ messages in thread

* [PATCH 2/9] psql and pg_dump support for partitioned tables.
@ 2016-07-12 08:20  amit <[email protected]>
  0 siblings, 0 replies; 110+ messages in thread

From: amit @ 2016-07-12 08:20 UTC (permalink / raw)

Takes care of both the partition key deparse stuff and the new relkind.
---
 src/backend/utils/adt/ruleutils.c          |  140 ++++++++++++++++++++++++++++
 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/pg_proc.h              |    2 +
 src/include/utils/builtins.h               |    1 +
 src/test/regress/expected/create_table.out |   26 +++++
 src/test/regress/sql/create_table.sql      |   13 +++
 9 files changed, 260 insertions(+), 26 deletions(-)

diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 8a81d7a..03be202 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_table.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
@@ -315,6 +316,7 @@ static char *pg_get_indexdef_worker(Oid indexrelid, int colno,
 					   const Oid *excludeOps,
 					   bool attrsOnly, bool showTblSpc,
 					   int prettyFlags, bool missing_ok);
+static char *pg_get_partkeydef_worker(Oid relid, int prettyFlags);
 static char *pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 							int prettyFlags, bool missing_ok);
 static text *pg_get_expr_worker(text *expr, Oid relid, const char *relname,
@@ -1389,6 +1391,144 @@ 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 [ opclass_name ] [, ...])
+ */
+Datum
+pg_get_partkeydef(PG_FUNCTION_ARGS)
+{
+	Oid			relid = PG_GETARG_OID(0);
+	int			prettyFlags;
+
+	prettyFlags = PRETTYFLAG_INDENT;
+	PG_RETURN_TEXT_P(string_to_text(pg_get_partkeydef_worker(relid,
+									prettyFlags)));
+}
+
+/*
+ * Internal workhorse to decompile a partition key definition.
+ */
+static char *
+pg_get_partkeydef_worker(Oid relid, int prettyFlags)
+{
+	Form_pg_partitioned_table	form;
+	HeapTuple	tuple;
+	oidvector  *partclass;
+	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_table) GETSTRUCT(tuple);
+
+	Assert(form->partrelid == relid);
+
+	/* Must get partclass, and partexprs the hard way */
+	datum = SysCacheGetAttr(PARTEDRELID, tuple,
+							Anum_pg_partitioned_table_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_table_partexprbin))
+	{
+		Datum		exprsDatum;
+		bool		isnull;
+		char	   *exprsString;
+
+		exprsDatum = SysCacheGetAttr(PARTEDRELID, tuple,
+									 Anum_pg_partitioned_table_partexprbin, &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);
+
+	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,
+											0, 0);
+
+			appendStringInfoString(&buf, 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/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 51b8a1a..854dddc 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -1253,9 +1253,10 @@ expand_table_name_patterns(Archive *fout,
 						  "SELECT c.oid"
 						  "\nFROM pg_catalog.pg_class c"
 		"\n     LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace"
-					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c')\n",
+					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c')\n",
 						  RELKIND_RELATION, RELKIND_SEQUENCE, RELKIND_VIEW,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 		processSQLNamePattern(GetConnection(fout), query, cell->val, true,
 							  false, "n.nspname", "c.relname", NULL,
 							  "pg_catalog.pg_table_is_visible(c.oid)");
@@ -2125,6 +2126,9 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo, bool oids)
 	/* Skip FOREIGN TABLEs (no data to dump) */
 	if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 		return;
+	/* Skip partitioned tables (data in partitions) */
+	if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+		return;
 
 	/* Don't dump data in unlogged tables, if so requested */
 	if (tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED &&
@@ -5211,6 +5215,7 @@ getTables(Archive *fout, int *numTables)
 	int			i_reloftype;
 	int			i_relpages;
 	int			i_changed_acl;
+	int			i_partkeydef;
 
 	/* Make sure we are in proper schema */
 	selectSourceSchema(fout, "pg_catalog");
@@ -5296,7 +5301,8 @@ getTables(Archive *fout, int *numTables)
 						  "OR %s IS NOT NULL "
 						  "OR %s IS NOT NULL"
 						  "))"
-						  "AS changed_acl "
+						  "AS changed_acl, "
+						  "CASE WHEN c.relkind = 'P' THEN pg_catalog.pg_get_partkeydef(c.oid) ELSE NULL END AS partkeydef "
 						  "FROM pg_class c "
 						  "LEFT JOIN pg_depend d ON "
 						  "(c.relkind = '%c' AND "
@@ -5308,7 +5314,7 @@ getTables(Archive *fout, int *numTables)
 						  "(c.oid = pip.objoid "
 						  "AND pip.classoid = 'pg_class'::regclass "
 						  "AND pip.objsubid = 0) "
-				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c') "
+				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c', '%c') "
 						  "ORDER BY c.oid",
 						  acl_subquery->data,
 						  racl_subquery->data,
@@ -5322,7 +5328,8 @@ getTables(Archive *fout, int *numTables)
 						  RELKIND_SEQUENCE,
 						  RELKIND_RELATION, RELKIND_SEQUENCE,
 						  RELKIND_VIEW, RELKIND_COMPOSITE_TYPE,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 
 		destroyPQExpBuffer(acl_subquery);
 		destroyPQExpBuffer(racl_subquery);
@@ -5884,6 +5891,7 @@ getTables(Archive *fout, int *numTables)
 	i_toastreloptions = PQfnumber(res, "toast_reloptions");
 	i_reloftype = PQfnumber(res, "reloftype");
 	i_changed_acl = PQfnumber(res, "changed_acl");
+	i_partkeydef = PQfnumber(res, "partkeydef");
 
 	if (dopt->lockWaitTimeout && fout->remoteVersion >= 70300)
 	{
@@ -5954,6 +5962,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 */
 
@@ -5998,7 +6007,9 @@ getTables(Archive *fout, int *numTables)
 		 * We only need to lock the table for certain components; see
 		 * pg_dump.h
 		 */
-		if (tblinfo[i].dobj.dump && tblinfo[i].relkind == RELKIND_RELATION &&
+		if (tblinfo[i].dobj.dump &&
+			(tblinfo[i].relkind == RELKIND_RELATION ||
+			 tblinfo->relkind == RELKIND_PARTITIONED_TABLE) &&
 			(tblinfo[i].dobj.dump & DUMP_COMPONENTS_REQUIRING_LOCK))
 		{
 			resetPQExpBuffer(query);
@@ -6100,7 +6111,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);
 
@@ -15453,6 +15467,9 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 				appendPQExpBufferChar(q, ')');
 			}
 
+			if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+				appendPQExpBuffer(q, "\nPARTITION BY %s", tbinfo->partkeydef);
+
 			if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 				appendPQExpBuffer(q, "\nSERVER %s", fmtId(srvname));
 		}
@@ -15513,6 +15530,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 		 */
 		if (dopt->binary_upgrade &&
 			(tbinfo->relkind == RELKIND_RELATION ||
+			 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 			 tbinfo->relkind == RELKIND_FOREIGN_TABLE))
 		{
 			for (j = 0; j < tbinfo->numatts; j++)
@@ -15531,7 +15549,8 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 					appendStringLiteralAH(q, fmtId(tbinfo->dobj.name), fout);
 					appendPQExpBufferStr(q, "::pg_catalog.regclass;\n");
 
-					if (tbinfo->relkind == RELKIND_RELATION)
+					if (tbinfo->relkind == RELKIND_RELATION ||
+						tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
 						appendPQExpBuffer(q, "ALTER TABLE ONLY %s ",
 										  fmtId(tbinfo->dobj.name));
 					else
@@ -15748,6 +15767,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 	 * dump properties we only have ALTER TABLE syntax for
 	 */
 	if ((tbinfo->relkind == RELKIND_RELATION ||
+		 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 		 tbinfo->relkind == RELKIND_MATVIEW) &&
 		tbinfo->relreplident != REPLICA_IDENTITY_DEFAULT)
 	{
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 2bfa2d9..0292859 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -310,6 +310,7 @@ typedef struct _tableInfo
 	bool	   *inhNotNull;		/* true if NOT NULL is inherited */
 	struct _attrDefInfo **attrdefs;		/* DEFAULT expressions */
 	struct _constraintInfo *checkexprs; /* CHECK constraints */
+	char	   *partkeydef;		/* partition key definition */
 
 	/*
 	 * Stuff computed only for dumpable tables.
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 6275a68..db6dc5c 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -861,6 +861,7 @@ permissionsList(const char *pattern)
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'S' THEN '%s'"
@@ -870,6 +871,7 @@ permissionsList(const char *pattern)
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("sequence"),
@@ -920,7 +922,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
@@ -1567,8 +1569,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)");
 	}
 
@@ -1633,6 +1635,14 @@ describeOneTableDetails(const char *schemaname,
 			printfPQExpBuffer(&title, _("Foreign table \"%s.%s\""),
 							  schemaname, relationname);
 			break;
+		case 'P':
+			if (tableinfo.relpersistence == 'u')
+				printfPQExpBuffer(&title, _("Unlogged table \"%s.%s\""),
+								  schemaname, relationname);
+			else
+				printfPQExpBuffer(&title, _("Table \"%s.%s\""),
+								  schemaname, relationname);
+			break;
 		default:
 			/* untranslated unknown relkind */
 			printfPQExpBuffer(&title, "?%c? \"%s.%s\"",
@@ -1646,8 +1656,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");
@@ -1667,12 +1677,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");
 	}
 
@@ -1772,7 +1782,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);
@@ -1780,14 +1790,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 (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 */
@@ -1926,7 +1955,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;
@@ -2485,7 +2514,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;
@@ -2696,7 +2725,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
@@ -3024,6 +3053,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'i' THEN '%s'"
@@ -3035,6 +3065,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("index"),
@@ -3079,7 +3110,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 50a45eb..8284a9c 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/pg_proc.h b/src/include/catalog/pg_proc.h
index e2d08ba..b45688b 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -1980,6 +1980,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/utils/builtins.h b/src/include/utils/builtins.h
index 2ae212a..e800647 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -729,6 +729,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/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 1394362..260ec34 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -407,3 +407,29 @@ CREATE TABLE no_inh_con_parted (
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
 ERROR:  cannot add NO INHERIT constraint to partitioned table "no_inh_con_parted"
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+Table "public.describe_range_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition key: RANGE ((a + b))
+
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+Table "public.describe_list_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition key: LIST (a)
+
+DROP TABLE describe_range_key, describe_list_key;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index d383f58..7a1f8de 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -401,3 +401,16 @@ CREATE TABLE no_inh_con_parted (
 	a int,
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
+
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+DROP TABLE describe_range_key, describe_list_key;
-- 
1.7.1


--------------C3235EE96C69D8DAE4DB543E
Content-Type: text/x-diff;
 name="0003-Catalog-and-DDL-for-partitions-6.patch"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
 filename="0003-Catalog-and-DDL-for-partitions-6.patch"



^ permalink  raw  reply  [nested|flat] 110+ messages in thread

* [PATCH 2/9] psql and pg_dump support for partitioned tables.
@ 2016-07-12 08:20  amit <[email protected]>
  0 siblings, 0 replies; 110+ messages in thread

From: amit @ 2016-07-12 08:20 UTC (permalink / raw)

Takes care of both the partition key deparse stuff and the new relkind.
---
 src/backend/utils/adt/ruleutils.c          |  140 ++++++++++++++++++++++++++++
 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/pg_proc.h              |    2 +
 src/include/utils/builtins.h               |    1 +
 src/test/regress/expected/create_table.out |   26 +++++
 src/test/regress/sql/create_table.sql      |   13 +++
 9 files changed, 260 insertions(+), 26 deletions(-)

diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 8a81d7a..03be202 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_table.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
@@ -315,6 +316,7 @@ static char *pg_get_indexdef_worker(Oid indexrelid, int colno,
 					   const Oid *excludeOps,
 					   bool attrsOnly, bool showTblSpc,
 					   int prettyFlags, bool missing_ok);
+static char *pg_get_partkeydef_worker(Oid relid, int prettyFlags);
 static char *pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 							int prettyFlags, bool missing_ok);
 static text *pg_get_expr_worker(text *expr, Oid relid, const char *relname,
@@ -1389,6 +1391,144 @@ 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 [ opclass_name ] [, ...])
+ */
+Datum
+pg_get_partkeydef(PG_FUNCTION_ARGS)
+{
+	Oid			relid = PG_GETARG_OID(0);
+	int			prettyFlags;
+
+	prettyFlags = PRETTYFLAG_INDENT;
+	PG_RETURN_TEXT_P(string_to_text(pg_get_partkeydef_worker(relid,
+									prettyFlags)));
+}
+
+/*
+ * Internal workhorse to decompile a partition key definition.
+ */
+static char *
+pg_get_partkeydef_worker(Oid relid, int prettyFlags)
+{
+	Form_pg_partitioned_table	form;
+	HeapTuple	tuple;
+	oidvector  *partclass;
+	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_table) GETSTRUCT(tuple);
+
+	Assert(form->partrelid == relid);
+
+	/* Must get partclass, and partexprs the hard way */
+	datum = SysCacheGetAttr(PARTEDRELID, tuple,
+							Anum_pg_partitioned_table_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_table_partexprbin))
+	{
+		Datum		exprsDatum;
+		bool		isnull;
+		char	   *exprsString;
+
+		exprsDatum = SysCacheGetAttr(PARTEDRELID, tuple,
+									 Anum_pg_partitioned_table_partexprbin, &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);
+
+	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,
+											0, 0);
+
+			appendStringInfoString(&buf, 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/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index ba9c276..c805a84 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -1253,9 +1253,10 @@ expand_table_name_patterns(Archive *fout,
 						  "SELECT c.oid"
 						  "\nFROM pg_catalog.pg_class c"
 		"\n     LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace"
-					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c')\n",
+					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c')\n",
 						  RELKIND_RELATION, RELKIND_SEQUENCE, RELKIND_VIEW,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 		processSQLNamePattern(GetConnection(fout), query, cell->val, true,
 							  false, "n.nspname", "c.relname", NULL,
 							  "pg_catalog.pg_table_is_visible(c.oid)");
@@ -2125,6 +2126,9 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo, bool oids)
 	/* Skip FOREIGN TABLEs (no data to dump) */
 	if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 		return;
+	/* Skip partitioned tables (data in partitions) */
+	if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+		return;
 
 	/* Don't dump data in unlogged tables, if so requested */
 	if (tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED &&
@@ -5204,6 +5208,7 @@ getTables(Archive *fout, int *numTables)
 	int			i_reloftype;
 	int			i_relpages;
 	int			i_changed_acl;
+	int			i_partkeydef;
 
 	/* Make sure we are in proper schema */
 	selectSourceSchema(fout, "pg_catalog");
@@ -5289,7 +5294,8 @@ getTables(Archive *fout, int *numTables)
 						  "OR %s IS NOT NULL "
 						  "OR %s IS NOT NULL"
 						  "))"
-						  "AS changed_acl "
+						  "AS changed_acl, "
+						  "CASE WHEN c.relkind = 'P' THEN pg_catalog.pg_get_partkeydef(c.oid) ELSE NULL END AS partkeydef "
 						  "FROM pg_class c "
 						  "LEFT JOIN pg_depend d ON "
 						  "(c.relkind = '%c' AND "
@@ -5301,7 +5307,7 @@ getTables(Archive *fout, int *numTables)
 						  "(c.oid = pip.objoid "
 						  "AND pip.classoid = 'pg_class'::regclass "
 						  "AND pip.objsubid = 0) "
-				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c') "
+				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c', '%c') "
 						  "ORDER BY c.oid",
 						  acl_subquery->data,
 						  racl_subquery->data,
@@ -5315,7 +5321,8 @@ getTables(Archive *fout, int *numTables)
 						  RELKIND_SEQUENCE,
 						  RELKIND_RELATION, RELKIND_SEQUENCE,
 						  RELKIND_VIEW, RELKIND_COMPOSITE_TYPE,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 
 		destroyPQExpBuffer(acl_subquery);
 		destroyPQExpBuffer(racl_subquery);
@@ -5877,6 +5884,7 @@ getTables(Archive *fout, int *numTables)
 	i_toastreloptions = PQfnumber(res, "toast_reloptions");
 	i_reloftype = PQfnumber(res, "reloftype");
 	i_changed_acl = PQfnumber(res, "changed_acl");
+	i_partkeydef = PQfnumber(res, "partkeydef");
 
 	if (dopt->lockWaitTimeout && fout->remoteVersion >= 70300)
 	{
@@ -5947,6 +5955,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 */
 
@@ -5991,7 +6000,9 @@ getTables(Archive *fout, int *numTables)
 		 * We only need to lock the table for certain components; see
 		 * pg_dump.h
 		 */
-		if (tblinfo[i].dobj.dump && tblinfo[i].relkind == RELKIND_RELATION &&
+		if (tblinfo[i].dobj.dump &&
+			(tblinfo[i].relkind == RELKIND_RELATION ||
+			 tblinfo->relkind == RELKIND_PARTITIONED_TABLE) &&
 			(tblinfo[i].dobj.dump & DUMP_COMPONENTS_REQUIRING_LOCK))
 		{
 			resetPQExpBuffer(query);
@@ -6093,7 +6104,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);
 
@@ -15446,6 +15460,9 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 				appendPQExpBufferChar(q, ')');
 			}
 
+			if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+				appendPQExpBuffer(q, "\nPARTITION BY %s", tbinfo->partkeydef);
+
 			if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 				appendPQExpBuffer(q, "\nSERVER %s", fmtId(srvname));
 		}
@@ -15506,6 +15523,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 		 */
 		if (dopt->binary_upgrade &&
 			(tbinfo->relkind == RELKIND_RELATION ||
+			 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 			 tbinfo->relkind == RELKIND_FOREIGN_TABLE))
 		{
 			for (j = 0; j < tbinfo->numatts; j++)
@@ -15524,7 +15542,8 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 					appendStringLiteralAH(q, fmtId(tbinfo->dobj.name), fout);
 					appendPQExpBufferStr(q, "::pg_catalog.regclass;\n");
 
-					if (tbinfo->relkind == RELKIND_RELATION)
+					if (tbinfo->relkind == RELKIND_RELATION ||
+						tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
 						appendPQExpBuffer(q, "ALTER TABLE ONLY %s ",
 										  fmtId(tbinfo->dobj.name));
 					else
@@ -15741,6 +15760,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 	 * dump properties we only have ALTER TABLE syntax for
 	 */
 	if ((tbinfo->relkind == RELKIND_RELATION ||
+		 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 		 tbinfo->relkind == RELKIND_MATVIEW) &&
 		tbinfo->relreplident != REPLICA_IDENTITY_DEFAULT)
 	{
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 2bfa2d9..0292859 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -310,6 +310,7 @@ typedef struct _tableInfo
 	bool	   *inhNotNull;		/* true if NOT NULL is inherited */
 	struct _attrDefInfo **attrdefs;		/* DEFAULT expressions */
 	struct _constraintInfo *checkexprs; /* CHECK constraints */
+	char	   *partkeydef;		/* partition key definition */
 
 	/*
 	 * Stuff computed only for dumpable tables.
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 6275a68..10d924a 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -861,6 +861,7 @@ permissionsList(const char *pattern)
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'S' THEN '%s'"
@@ -870,6 +871,7 @@ permissionsList(const char *pattern)
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("sequence"),
@@ -920,7 +922,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
@@ -1567,8 +1569,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)");
 	}
 
@@ -1633,6 +1635,14 @@ describeOneTableDetails(const char *schemaname,
 			printfPQExpBuffer(&title, _("Foreign table \"%s.%s\""),
 							  schemaname, relationname);
 			break;
+		case 'P':
+			if (tableinfo.relpersistence == 'u')
+				printfPQExpBuffer(&title, _("Unlogged table \"%s.%s\""),
+								  schemaname, relationname);
+			else
+				printfPQExpBuffer(&title, _("Table \"%s.%s\""),
+								  schemaname, relationname);
+			break;
 		default:
 			/* untranslated unknown relkind */
 			printfPQExpBuffer(&title, "?%c? \"%s.%s\"",
@@ -1646,8 +1656,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");
@@ -1667,12 +1677,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");
 	}
 
@@ -1772,7 +1782,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);
@@ -1780,14 +1790,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 (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 */
@@ -1926,7 +1955,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;
@@ -2485,7 +2514,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;
@@ -2696,7 +2725,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
@@ -3024,6 +3053,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'i' THEN '%s'"
@@ -3035,6 +3065,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("index"),
@@ -3079,7 +3110,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 019f75a..5e5c370 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/pg_proc.h b/src/include/catalog/pg_proc.h
index e2d08ba..b45688b 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -1980,6 +1980,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/utils/builtins.h b/src/include/utils/builtins.h
index 2ae212a..e800647 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -729,6 +729,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/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 708232d..2fec847 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -408,3 +408,29 @@ CREATE TABLE no_inh_con_parted (
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
 ERROR:  cannot add NO INHERIT constraint to partitioned table "no_inh_con_parted"
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+Table "public.describe_range_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition Key: RANGE ((a + b))
+
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+Table "public.describe_list_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition Key: LIST (a)
+
+DROP TABLE describe_range_key, describe_list_key;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 6e4a8be..4dd6a0a 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -402,3 +402,16 @@ CREATE TABLE no_inh_con_parted (
 	a int,
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
+
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+DROP TABLE describe_range_key, describe_list_key;
-- 
1.7.1


--------------4012470FC48539D6F6714CEA
Content-Type: text/x-diff;
 name="0003-Catalog-and-DDL-for-partitions-4.patch"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
 filename="0003-Catalog-and-DDL-for-partitions-4.patch"



^ permalink  raw  reply  [nested|flat] 110+ messages in thread

* [PATCH 2/9] psql and pg_dump support for partitioned tables.
@ 2016-07-12 08:20  amit <[email protected]>
  0 siblings, 0 replies; 110+ messages in thread

From: amit @ 2016-07-12 08:20 UTC (permalink / raw)

Takes care of both the partition key deparse stuff and the new relkind.
---
 src/backend/utils/adt/ruleutils.c          |  140 ++++++++++++++++++++++++++++
 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/pg_proc.h              |    2 +
 src/include/utils/builtins.h               |    1 +
 src/test/regress/expected/create_table.out |   26 +++++
 src/test/regress/sql/create_table.sql      |   13 +++
 9 files changed, 260 insertions(+), 26 deletions(-)

diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 8a81d7a..77ce807 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_table.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
@@ -315,6 +316,7 @@ static char *pg_get_indexdef_worker(Oid indexrelid, int colno,
 					   const Oid *excludeOps,
 					   bool attrsOnly, bool showTblSpc,
 					   int prettyFlags, bool missing_ok);
+static char *pg_get_partkeydef_worker(Oid relId, int prettyFlags);
 static char *pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 							int prettyFlags, bool missing_ok);
 static text *pg_get_expr_worker(text *expr, Oid relid, const char *relname,
@@ -1389,6 +1391,144 @@ 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 [ 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_table	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_table) GETSTRUCT(tuple);
+
+	Assert(form->partedrelid == relId);
+
+	/* Must get partclass, and partexprs the hard way */
+	datum = SysCacheGetAttr(PARTEDRELID, tuple,
+							Anum_pg_partitioned_table_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_table_partexprbin))
+	{
+		Datum		exprsDatum;
+		bool		isnull;
+		char	   *exprsString;
+
+		exprsDatum = SysCacheGetAttr(PARTEDRELID, tuple,
+									 Anum_pg_partitioned_table_partexprbin, &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);
+
+	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,
+											0, 0);
+
+			appendStringInfoString(&buf, 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/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index a5c2d09..c418ba7 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -1253,9 +1253,10 @@ expand_table_name_patterns(Archive *fout,
 						  "SELECT c.oid"
 						  "\nFROM pg_catalog.pg_class c"
 		"\n     LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace"
-					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c')\n",
+					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c')\n",
 						  RELKIND_RELATION, RELKIND_SEQUENCE, RELKIND_VIEW,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 		processSQLNamePattern(GetConnection(fout), query, cell->val, true,
 							  false, "n.nspname", "c.relname", NULL,
 							  "pg_catalog.pg_table_is_visible(c.oid)");
@@ -2117,6 +2118,9 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo, bool oids)
 	/* Skip FOREIGN TABLEs (no data to dump) */
 	if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 		return;
+	/* Skip partitioned tables (data in partitions) */
+	if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+		return;
 
 	/* Don't dump data in unlogged tables, if so requested */
 	if (tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED &&
@@ -5195,6 +5199,7 @@ getTables(Archive *fout, int *numTables)
 	int			i_reloftype;
 	int			i_relpages;
 	int			i_changed_acl;
+	int			i_partkeydef;
 
 	/* Make sure we are in proper schema */
 	selectSourceSchema(fout, "pg_catalog");
@@ -5280,7 +5285,8 @@ getTables(Archive *fout, int *numTables)
 						  "OR %s IS NOT NULL "
 						  "OR %s IS NOT NULL"
 						  "))"
-						  "AS changed_acl "
+						  "AS changed_acl, "
+						  "CASE WHEN c.relkind = 'P' THEN pg_catalog.pg_get_partkeydef(c.oid) ELSE NULL END AS partkeydef "
 						  "FROM pg_class c "
 						  "LEFT JOIN pg_depend d ON "
 						  "(c.relkind = '%c' AND "
@@ -5292,7 +5298,7 @@ getTables(Archive *fout, int *numTables)
 						  "(c.oid = pip.objoid "
 						  "AND pip.classoid = 'pg_class'::regclass "
 						  "AND pip.objsubid = 0) "
-				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c') "
+				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c', '%c') "
 						  "ORDER BY c.oid",
 						  acl_subquery->data,
 						  racl_subquery->data,
@@ -5306,7 +5312,8 @@ getTables(Archive *fout, int *numTables)
 						  RELKIND_SEQUENCE,
 						  RELKIND_RELATION, RELKIND_SEQUENCE,
 						  RELKIND_VIEW, RELKIND_COMPOSITE_TYPE,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 
 		destroyPQExpBuffer(acl_subquery);
 		destroyPQExpBuffer(racl_subquery);
@@ -5868,6 +5875,7 @@ getTables(Archive *fout, int *numTables)
 	i_toastreloptions = PQfnumber(res, "toast_reloptions");
 	i_reloftype = PQfnumber(res, "reloftype");
 	i_changed_acl = PQfnumber(res, "changed_acl");
+	i_partkeydef = PQfnumber(res, "partkeydef");
 
 	if (dopt->lockWaitTimeout && fout->remoteVersion >= 70300)
 	{
@@ -5938,6 +5946,7 @@ getTables(Archive *fout, int *numTables)
 		else
 			tblinfo[i].checkoption = pg_strdup(PQgetvalue(res, i, i_checkoption));
 		tblinfo[i].toast_reloptions = pg_strdup(PQgetvalue(res, i, i_toastreloptions));
+		tblinfo[i].partkeydef = pg_strdup(PQgetvalue(res, i, i_partkeydef));
 
 		/* other fields were zeroed above */
 
@@ -5982,7 +5991,9 @@ getTables(Archive *fout, int *numTables)
 		 * We only need to lock the table for certain components; see
 		 * pg_dump.h
 		 */
-		if (tblinfo[i].dobj.dump && tblinfo[i].relkind == RELKIND_RELATION &&
+		if (tblinfo[i].dobj.dump &&
+			(tblinfo[i].relkind == RELKIND_RELATION ||
+			 tblinfo->relkind == RELKIND_PARTITIONED_TABLE) &&
 			(tblinfo[i].dobj.dump & DUMP_COMPONENTS_REQUIRING_LOCK))
 		{
 			resetPQExpBuffer(query);
@@ -6084,7 +6095,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);
 
@@ -15437,6 +15451,9 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 				appendPQExpBufferChar(q, ')');
 			}
 
+			if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+				appendPQExpBuffer(q, "\nPARTITION BY %s", tbinfo->partkeydef);
+
 			if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 				appendPQExpBuffer(q, "\nSERVER %s", fmtId(srvname));
 		}
@@ -15497,6 +15514,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 		 */
 		if (dopt->binary_upgrade &&
 			(tbinfo->relkind == RELKIND_RELATION ||
+			 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 			 tbinfo->relkind == RELKIND_FOREIGN_TABLE))
 		{
 			for (j = 0; j < tbinfo->numatts; j++)
@@ -15515,7 +15533,8 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 					appendStringLiteralAH(q, fmtId(tbinfo->dobj.name), fout);
 					appendPQExpBufferStr(q, "::pg_catalog.regclass;\n");
 
-					if (tbinfo->relkind == RELKIND_RELATION)
+					if (tbinfo->relkind == RELKIND_RELATION ||
+						tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
 						appendPQExpBuffer(q, "ALTER TABLE ONLY %s ",
 										  fmtId(tbinfo->dobj.name));
 					else
@@ -15732,6 +15751,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 	 * dump properties we only have ALTER TABLE syntax for
 	 */
 	if ((tbinfo->relkind == RELKIND_RELATION ||
+		 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 		 tbinfo->relkind == RELKIND_MATVIEW) &&
 		tbinfo->relreplident != REPLICA_IDENTITY_DEFAULT)
 	{
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 2bfa2d9..0292859 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -310,6 +310,7 @@ typedef struct _tableInfo
 	bool	   *inhNotNull;		/* true if NOT NULL is inherited */
 	struct _attrDefInfo **attrdefs;		/* DEFAULT expressions */
 	struct _constraintInfo *checkexprs; /* CHECK constraints */
+	char	   *partkeydef;		/* partition key definition */
 
 	/*
 	 * Stuff computed only for dumpable tables.
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 6275a68..10d924a 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -861,6 +861,7 @@ permissionsList(const char *pattern)
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'S' THEN '%s'"
@@ -870,6 +871,7 @@ permissionsList(const char *pattern)
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("sequence"),
@@ -920,7 +922,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
@@ -1567,8 +1569,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)");
 	}
 
@@ -1633,6 +1635,14 @@ describeOneTableDetails(const char *schemaname,
 			printfPQExpBuffer(&title, _("Foreign table \"%s.%s\""),
 							  schemaname, relationname);
 			break;
+		case 'P':
+			if (tableinfo.relpersistence == 'u')
+				printfPQExpBuffer(&title, _("Unlogged table \"%s.%s\""),
+								  schemaname, relationname);
+			else
+				printfPQExpBuffer(&title, _("Table \"%s.%s\""),
+								  schemaname, relationname);
+			break;
 		default:
 			/* untranslated unknown relkind */
 			printfPQExpBuffer(&title, "?%c? \"%s.%s\"",
@@ -1646,8 +1656,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");
@@ -1667,12 +1677,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");
 	}
 
@@ -1772,7 +1782,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);
@@ -1780,14 +1790,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 (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 */
@@ -1926,7 +1955,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;
@@ -2485,7 +2514,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;
@@ -2696,7 +2725,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
@@ -3024,6 +3053,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'i' THEN '%s'"
@@ -3035,6 +3065,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("index"),
@@ -3079,7 +3110,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 019f75a..5e5c370 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/pg_proc.h b/src/include/catalog/pg_proc.h
index e2d08ba..b45688b 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -1980,6 +1980,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/utils/builtins.h b/src/include/utils/builtins.h
index 2ae212a..e800647 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -729,6 +729,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/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 708232d..2fec847 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -408,3 +408,29 @@ CREATE TABLE no_inh_con_parted (
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
 ERROR:  cannot add NO INHERIT constraint to partitioned table "no_inh_con_parted"
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+Table "public.describe_range_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition Key: RANGE ((a + b))
+
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+Table "public.describe_list_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition Key: LIST (a)
+
+DROP TABLE describe_range_key, describe_list_key;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 6e4a8be..4dd6a0a 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -402,3 +402,16 @@ CREATE TABLE no_inh_con_parted (
 	a int,
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
+
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+DROP TABLE describe_range_key, describe_list_key;
-- 
1.7.1


--------------6EB5B318125CB7DB1A4FD229
Content-Type: text/x-diff;
 name="0003-Catalog-and-DDL-for-partitions-3.patch"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
 filename="0003-Catalog-and-DDL-for-partitions-3.patch"



^ permalink  raw  reply  [nested|flat] 110+ messages in thread

* [PATCH 2/9] psql and pg_dump support for partitioned tables.
@ 2016-07-12 08:20  amit <[email protected]>
  0 siblings, 0 replies; 110+ messages in thread

From: amit @ 2016-07-12 08:20 UTC (permalink / raw)

Takes care of both the partition key deparse stuff and the new relkind.
---
 src/backend/utils/adt/ruleutils.c          |  140 ++++++++++++++++++++++++++++
 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/pg_proc.h              |    2 +
 src/include/utils/builtins.h               |    1 +
 src/test/regress/expected/create_table.out |   26 +++++
 src/test/regress/sql/create_table.sql      |   13 +++
 9 files changed, 260 insertions(+), 26 deletions(-)

diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 8a81d7a..03be202 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_table.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
@@ -315,6 +316,7 @@ static char *pg_get_indexdef_worker(Oid indexrelid, int colno,
 					   const Oid *excludeOps,
 					   bool attrsOnly, bool showTblSpc,
 					   int prettyFlags, bool missing_ok);
+static char *pg_get_partkeydef_worker(Oid relid, int prettyFlags);
 static char *pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 							int prettyFlags, bool missing_ok);
 static text *pg_get_expr_worker(text *expr, Oid relid, const char *relname,
@@ -1389,6 +1391,144 @@ 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 [ opclass_name ] [, ...])
+ */
+Datum
+pg_get_partkeydef(PG_FUNCTION_ARGS)
+{
+	Oid			relid = PG_GETARG_OID(0);
+	int			prettyFlags;
+
+	prettyFlags = PRETTYFLAG_INDENT;
+	PG_RETURN_TEXT_P(string_to_text(pg_get_partkeydef_worker(relid,
+									prettyFlags)));
+}
+
+/*
+ * Internal workhorse to decompile a partition key definition.
+ */
+static char *
+pg_get_partkeydef_worker(Oid relid, int prettyFlags)
+{
+	Form_pg_partitioned_table	form;
+	HeapTuple	tuple;
+	oidvector  *partclass;
+	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_table) GETSTRUCT(tuple);
+
+	Assert(form->partrelid == relid);
+
+	/* Must get partclass, and partexprs the hard way */
+	datum = SysCacheGetAttr(PARTEDRELID, tuple,
+							Anum_pg_partitioned_table_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_table_partexprbin))
+	{
+		Datum		exprsDatum;
+		bool		isnull;
+		char	   *exprsString;
+
+		exprsDatum = SysCacheGetAttr(PARTEDRELID, tuple,
+									 Anum_pg_partitioned_table_partexprbin, &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);
+
+	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,
+											0, 0);
+
+			appendStringInfoString(&buf, 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/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index ba9c276..c805a84 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -1253,9 +1253,10 @@ expand_table_name_patterns(Archive *fout,
 						  "SELECT c.oid"
 						  "\nFROM pg_catalog.pg_class c"
 		"\n     LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace"
-					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c')\n",
+					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c')\n",
 						  RELKIND_RELATION, RELKIND_SEQUENCE, RELKIND_VIEW,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 		processSQLNamePattern(GetConnection(fout), query, cell->val, true,
 							  false, "n.nspname", "c.relname", NULL,
 							  "pg_catalog.pg_table_is_visible(c.oid)");
@@ -2125,6 +2126,9 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo, bool oids)
 	/* Skip FOREIGN TABLEs (no data to dump) */
 	if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 		return;
+	/* Skip partitioned tables (data in partitions) */
+	if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+		return;
 
 	/* Don't dump data in unlogged tables, if so requested */
 	if (tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED &&
@@ -5204,6 +5208,7 @@ getTables(Archive *fout, int *numTables)
 	int			i_reloftype;
 	int			i_relpages;
 	int			i_changed_acl;
+	int			i_partkeydef;
 
 	/* Make sure we are in proper schema */
 	selectSourceSchema(fout, "pg_catalog");
@@ -5289,7 +5294,8 @@ getTables(Archive *fout, int *numTables)
 						  "OR %s IS NOT NULL "
 						  "OR %s IS NOT NULL"
 						  "))"
-						  "AS changed_acl "
+						  "AS changed_acl, "
+						  "CASE WHEN c.relkind = 'P' THEN pg_catalog.pg_get_partkeydef(c.oid) ELSE NULL END AS partkeydef "
 						  "FROM pg_class c "
 						  "LEFT JOIN pg_depend d ON "
 						  "(c.relkind = '%c' AND "
@@ -5301,7 +5307,7 @@ getTables(Archive *fout, int *numTables)
 						  "(c.oid = pip.objoid "
 						  "AND pip.classoid = 'pg_class'::regclass "
 						  "AND pip.objsubid = 0) "
-				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c') "
+				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c', '%c') "
 						  "ORDER BY c.oid",
 						  acl_subquery->data,
 						  racl_subquery->data,
@@ -5315,7 +5321,8 @@ getTables(Archive *fout, int *numTables)
 						  RELKIND_SEQUENCE,
 						  RELKIND_RELATION, RELKIND_SEQUENCE,
 						  RELKIND_VIEW, RELKIND_COMPOSITE_TYPE,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 
 		destroyPQExpBuffer(acl_subquery);
 		destroyPQExpBuffer(racl_subquery);
@@ -5877,6 +5884,7 @@ getTables(Archive *fout, int *numTables)
 	i_toastreloptions = PQfnumber(res, "toast_reloptions");
 	i_reloftype = PQfnumber(res, "reloftype");
 	i_changed_acl = PQfnumber(res, "changed_acl");
+	i_partkeydef = PQfnumber(res, "partkeydef");
 
 	if (dopt->lockWaitTimeout && fout->remoteVersion >= 70300)
 	{
@@ -5947,6 +5955,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 */
 
@@ -5991,7 +6000,9 @@ getTables(Archive *fout, int *numTables)
 		 * We only need to lock the table for certain components; see
 		 * pg_dump.h
 		 */
-		if (tblinfo[i].dobj.dump && tblinfo[i].relkind == RELKIND_RELATION &&
+		if (tblinfo[i].dobj.dump &&
+			(tblinfo[i].relkind == RELKIND_RELATION ||
+			 tblinfo->relkind == RELKIND_PARTITIONED_TABLE) &&
 			(tblinfo[i].dobj.dump & DUMP_COMPONENTS_REQUIRING_LOCK))
 		{
 			resetPQExpBuffer(query);
@@ -6093,7 +6104,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);
 
@@ -15446,6 +15460,9 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 				appendPQExpBufferChar(q, ')');
 			}
 
+			if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+				appendPQExpBuffer(q, "\nPARTITION BY %s", tbinfo->partkeydef);
+
 			if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 				appendPQExpBuffer(q, "\nSERVER %s", fmtId(srvname));
 		}
@@ -15506,6 +15523,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 		 */
 		if (dopt->binary_upgrade &&
 			(tbinfo->relkind == RELKIND_RELATION ||
+			 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 			 tbinfo->relkind == RELKIND_FOREIGN_TABLE))
 		{
 			for (j = 0; j < tbinfo->numatts; j++)
@@ -15524,7 +15542,8 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 					appendStringLiteralAH(q, fmtId(tbinfo->dobj.name), fout);
 					appendPQExpBufferStr(q, "::pg_catalog.regclass;\n");
 
-					if (tbinfo->relkind == RELKIND_RELATION)
+					if (tbinfo->relkind == RELKIND_RELATION ||
+						tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
 						appendPQExpBuffer(q, "ALTER TABLE ONLY %s ",
 										  fmtId(tbinfo->dobj.name));
 					else
@@ -15741,6 +15760,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 	 * dump properties we only have ALTER TABLE syntax for
 	 */
 	if ((tbinfo->relkind == RELKIND_RELATION ||
+		 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 		 tbinfo->relkind == RELKIND_MATVIEW) &&
 		tbinfo->relreplident != REPLICA_IDENTITY_DEFAULT)
 	{
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 2bfa2d9..0292859 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -310,6 +310,7 @@ typedef struct _tableInfo
 	bool	   *inhNotNull;		/* true if NOT NULL is inherited */
 	struct _attrDefInfo **attrdefs;		/* DEFAULT expressions */
 	struct _constraintInfo *checkexprs; /* CHECK constraints */
+	char	   *partkeydef;		/* partition key definition */
 
 	/*
 	 * Stuff computed only for dumpable tables.
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 6275a68..10d924a 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -861,6 +861,7 @@ permissionsList(const char *pattern)
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'S' THEN '%s'"
@@ -870,6 +871,7 @@ permissionsList(const char *pattern)
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("sequence"),
@@ -920,7 +922,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
@@ -1567,8 +1569,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)");
 	}
 
@@ -1633,6 +1635,14 @@ describeOneTableDetails(const char *schemaname,
 			printfPQExpBuffer(&title, _("Foreign table \"%s.%s\""),
 							  schemaname, relationname);
 			break;
+		case 'P':
+			if (tableinfo.relpersistence == 'u')
+				printfPQExpBuffer(&title, _("Unlogged table \"%s.%s\""),
+								  schemaname, relationname);
+			else
+				printfPQExpBuffer(&title, _("Table \"%s.%s\""),
+								  schemaname, relationname);
+			break;
 		default:
 			/* untranslated unknown relkind */
 			printfPQExpBuffer(&title, "?%c? \"%s.%s\"",
@@ -1646,8 +1656,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");
@@ -1667,12 +1677,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");
 	}
 
@@ -1772,7 +1782,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);
@@ -1780,14 +1790,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 (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 */
@@ -1926,7 +1955,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;
@@ -2485,7 +2514,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;
@@ -2696,7 +2725,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
@@ -3024,6 +3053,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'i' THEN '%s'"
@@ -3035,6 +3065,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("index"),
@@ -3079,7 +3110,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 50a45eb..8284a9c 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/pg_proc.h b/src/include/catalog/pg_proc.h
index e2d08ba..b45688b 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -1980,6 +1980,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/utils/builtins.h b/src/include/utils/builtins.h
index 2ae212a..e800647 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -729,6 +729,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/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 0af1422..91ba1be 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -412,3 +412,29 @@ CREATE TABLE no_inh_con_parted (
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
 ERROR:  cannot add NO INHERIT constraint to partitioned table "no_inh_con_parted"
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+Table "public.describe_range_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition Key: RANGE ((a + b))
+
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+Table "public.describe_list_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition Key: LIST (a)
+
+DROP TABLE describe_range_key, describe_list_key;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 418ac55..b75542d 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -406,3 +406,16 @@ CREATE TABLE no_inh_con_parted (
 	a int,
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
+
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+DROP TABLE describe_range_key, describe_list_key;
-- 
1.7.1


--------------D008BA677674C4836D7B65AD
Content-Type: text/x-diff;
 name="0003-Catalog-and-DDL-for-partitions-5.patch"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
 filename="0003-Catalog-and-DDL-for-partitions-5.patch"



^ permalink  raw  reply  [nested|flat] 110+ messages in thread

* [PATCH 2/9] psql and pg_dump support for partitioned tables.
@ 2016-07-12 08:20  amit <[email protected]>
  0 siblings, 0 replies; 110+ messages in thread

From: amit @ 2016-07-12 08:20 UTC (permalink / raw)

Takes care of both the partition key deparse stuff and the new relkind.
---
 src/backend/utils/adt/ruleutils.c          |  140 ++++++++++++++++++++++++++++
 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/pg_proc.h              |    2 +
 src/include/utils/builtins.h               |    1 +
 src/test/regress/expected/create_table.out |   26 +++++
 src/test/regress/sql/create_table.sql      |   13 +++
 9 files changed, 260 insertions(+), 26 deletions(-)

diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 8a81d7a..77ce807 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_table.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
@@ -315,6 +316,7 @@ static char *pg_get_indexdef_worker(Oid indexrelid, int colno,
 					   const Oid *excludeOps,
 					   bool attrsOnly, bool showTblSpc,
 					   int prettyFlags, bool missing_ok);
+static char *pg_get_partkeydef_worker(Oid relId, int prettyFlags);
 static char *pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 							int prettyFlags, bool missing_ok);
 static text *pg_get_expr_worker(text *expr, Oid relid, const char *relname,
@@ -1389,6 +1391,144 @@ 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 [ 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_table	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_table) GETSTRUCT(tuple);
+
+	Assert(form->partedrelid == relId);
+
+	/* Must get partclass, and partexprs the hard way */
+	datum = SysCacheGetAttr(PARTEDRELID, tuple,
+							Anum_pg_partitioned_table_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_table_partexprbin))
+	{
+		Datum		exprsDatum;
+		bool		isnull;
+		char	   *exprsString;
+
+		exprsDatum = SysCacheGetAttr(PARTEDRELID, tuple,
+									 Anum_pg_partitioned_table_partexprbin, &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);
+
+	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,
+											0, 0);
+
+			appendStringInfoString(&buf, 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/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index a5c2d09..c418ba7 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -1253,9 +1253,10 @@ expand_table_name_patterns(Archive *fout,
 						  "SELECT c.oid"
 						  "\nFROM pg_catalog.pg_class c"
 		"\n     LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace"
-					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c')\n",
+					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c')\n",
 						  RELKIND_RELATION, RELKIND_SEQUENCE, RELKIND_VIEW,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 		processSQLNamePattern(GetConnection(fout), query, cell->val, true,
 							  false, "n.nspname", "c.relname", NULL,
 							  "pg_catalog.pg_table_is_visible(c.oid)");
@@ -2117,6 +2118,9 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo, bool oids)
 	/* Skip FOREIGN TABLEs (no data to dump) */
 	if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 		return;
+	/* Skip partitioned tables (data in partitions) */
+	if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+		return;
 
 	/* Don't dump data in unlogged tables, if so requested */
 	if (tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED &&
@@ -5195,6 +5199,7 @@ getTables(Archive *fout, int *numTables)
 	int			i_reloftype;
 	int			i_relpages;
 	int			i_changed_acl;
+	int			i_partkeydef;
 
 	/* Make sure we are in proper schema */
 	selectSourceSchema(fout, "pg_catalog");
@@ -5280,7 +5285,8 @@ getTables(Archive *fout, int *numTables)
 						  "OR %s IS NOT NULL "
 						  "OR %s IS NOT NULL"
 						  "))"
-						  "AS changed_acl "
+						  "AS changed_acl, "
+						  "CASE WHEN c.relkind = 'P' THEN pg_catalog.pg_get_partkeydef(c.oid) ELSE NULL END AS partkeydef "
 						  "FROM pg_class c "
 						  "LEFT JOIN pg_depend d ON "
 						  "(c.relkind = '%c' AND "
@@ -5292,7 +5298,7 @@ getTables(Archive *fout, int *numTables)
 						  "(c.oid = pip.objoid "
 						  "AND pip.classoid = 'pg_class'::regclass "
 						  "AND pip.objsubid = 0) "
-				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c') "
+				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c', '%c') "
 						  "ORDER BY c.oid",
 						  acl_subquery->data,
 						  racl_subquery->data,
@@ -5306,7 +5312,8 @@ getTables(Archive *fout, int *numTables)
 						  RELKIND_SEQUENCE,
 						  RELKIND_RELATION, RELKIND_SEQUENCE,
 						  RELKIND_VIEW, RELKIND_COMPOSITE_TYPE,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 
 		destroyPQExpBuffer(acl_subquery);
 		destroyPQExpBuffer(racl_subquery);
@@ -5868,6 +5875,7 @@ getTables(Archive *fout, int *numTables)
 	i_toastreloptions = PQfnumber(res, "toast_reloptions");
 	i_reloftype = PQfnumber(res, "reloftype");
 	i_changed_acl = PQfnumber(res, "changed_acl");
+	i_partkeydef = PQfnumber(res, "partkeydef");
 
 	if (dopt->lockWaitTimeout && fout->remoteVersion >= 70300)
 	{
@@ -5938,6 +5946,7 @@ getTables(Archive *fout, int *numTables)
 		else
 			tblinfo[i].checkoption = pg_strdup(PQgetvalue(res, i, i_checkoption));
 		tblinfo[i].toast_reloptions = pg_strdup(PQgetvalue(res, i, i_toastreloptions));
+		tblinfo[i].partkeydef = pg_strdup(PQgetvalue(res, i, i_partkeydef));
 
 		/* other fields were zeroed above */
 
@@ -5982,7 +5991,9 @@ getTables(Archive *fout, int *numTables)
 		 * We only need to lock the table for certain components; see
 		 * pg_dump.h
 		 */
-		if (tblinfo[i].dobj.dump && tblinfo[i].relkind == RELKIND_RELATION &&
+		if (tblinfo[i].dobj.dump &&
+			(tblinfo[i].relkind == RELKIND_RELATION ||
+			 tblinfo->relkind == RELKIND_PARTITIONED_TABLE) &&
 			(tblinfo[i].dobj.dump & DUMP_COMPONENTS_REQUIRING_LOCK))
 		{
 			resetPQExpBuffer(query);
@@ -6084,7 +6095,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);
 
@@ -15437,6 +15451,9 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 				appendPQExpBufferChar(q, ')');
 			}
 
+			if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+				appendPQExpBuffer(q, "\nPARTITION BY %s", tbinfo->partkeydef);
+
 			if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 				appendPQExpBuffer(q, "\nSERVER %s", fmtId(srvname));
 		}
@@ -15497,6 +15514,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 		 */
 		if (dopt->binary_upgrade &&
 			(tbinfo->relkind == RELKIND_RELATION ||
+			 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 			 tbinfo->relkind == RELKIND_FOREIGN_TABLE))
 		{
 			for (j = 0; j < tbinfo->numatts; j++)
@@ -15515,7 +15533,8 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 					appendStringLiteralAH(q, fmtId(tbinfo->dobj.name), fout);
 					appendPQExpBufferStr(q, "::pg_catalog.regclass;\n");
 
-					if (tbinfo->relkind == RELKIND_RELATION)
+					if (tbinfo->relkind == RELKIND_RELATION ||
+						tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
 						appendPQExpBuffer(q, "ALTER TABLE ONLY %s ",
 										  fmtId(tbinfo->dobj.name));
 					else
@@ -15732,6 +15751,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 	 * dump properties we only have ALTER TABLE syntax for
 	 */
 	if ((tbinfo->relkind == RELKIND_RELATION ||
+		 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 		 tbinfo->relkind == RELKIND_MATVIEW) &&
 		tbinfo->relreplident != REPLICA_IDENTITY_DEFAULT)
 	{
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 2bfa2d9..0292859 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -310,6 +310,7 @@ typedef struct _tableInfo
 	bool	   *inhNotNull;		/* true if NOT NULL is inherited */
 	struct _attrDefInfo **attrdefs;		/* DEFAULT expressions */
 	struct _constraintInfo *checkexprs; /* CHECK constraints */
+	char	   *partkeydef;		/* partition key definition */
 
 	/*
 	 * Stuff computed only for dumpable tables.
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 6275a68..10d924a 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -861,6 +861,7 @@ permissionsList(const char *pattern)
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'S' THEN '%s'"
@@ -870,6 +871,7 @@ permissionsList(const char *pattern)
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("sequence"),
@@ -920,7 +922,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
@@ -1567,8 +1569,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)");
 	}
 
@@ -1633,6 +1635,14 @@ describeOneTableDetails(const char *schemaname,
 			printfPQExpBuffer(&title, _("Foreign table \"%s.%s\""),
 							  schemaname, relationname);
 			break;
+		case 'P':
+			if (tableinfo.relpersistence == 'u')
+				printfPQExpBuffer(&title, _("Unlogged table \"%s.%s\""),
+								  schemaname, relationname);
+			else
+				printfPQExpBuffer(&title, _("Table \"%s.%s\""),
+								  schemaname, relationname);
+			break;
 		default:
 			/* untranslated unknown relkind */
 			printfPQExpBuffer(&title, "?%c? \"%s.%s\"",
@@ -1646,8 +1656,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");
@@ -1667,12 +1677,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");
 	}
 
@@ -1772,7 +1782,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);
@@ -1780,14 +1790,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 (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 */
@@ -1926,7 +1955,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;
@@ -2485,7 +2514,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;
@@ -2696,7 +2725,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
@@ -3024,6 +3053,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'i' THEN '%s'"
@@ -3035,6 +3065,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("index"),
@@ -3079,7 +3110,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 1345e4e..4686cd1 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/pg_proc.h b/src/include/catalog/pg_proc.h
index e2d08ba..b45688b 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -1980,6 +1980,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/utils/builtins.h b/src/include/utils/builtins.h
index 2ae212a..e800647 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -729,6 +729,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/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 708232d..2fec847 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -408,3 +408,29 @@ CREATE TABLE no_inh_con_parted (
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
 ERROR:  cannot add NO INHERIT constraint to partitioned table "no_inh_con_parted"
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+Table "public.describe_range_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition Key: RANGE ((a + b))
+
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+Table "public.describe_list_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition Key: LIST (a)
+
+DROP TABLE describe_range_key, describe_list_key;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 6e4a8be..4dd6a0a 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -402,3 +402,16 @@ CREATE TABLE no_inh_con_parted (
 	a int,
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
+
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+DROP TABLE describe_range_key, describe_list_key;
-- 
1.7.1


--------------954458C17BBEB1BF6FC758E7
Content-Type: text/x-diff;
 name="0003-Catalog-and-DDL-for-partitions-2.patch"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
 filename="0003-Catalog-and-DDL-for-partitions-2.patch"



^ permalink  raw  reply  [nested|flat] 110+ messages in thread

* [PATCH 2/8] psql and pg_dump support for partitioned tables.
@ 2016-07-12 08:20  amit <[email protected]>
  0 siblings, 0 replies; 110+ messages in thread

From: amit @ 2016-07-12 08:20 UTC (permalink / raw)

Takes care of both the partition key deparse stuff and the new relkind.
---
 src/backend/utils/adt/ruleutils.c          |  147 ++++++++++++++++++++++++++++
 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/pg_proc.h              |    2 +
 src/include/utils/builtins.h               |    1 +
 src/test/regress/expected/create_table.out |   26 +++++
 src/test/regress/sql/create_table.sql      |   13 +++
 9 files changed, 267 insertions(+), 26 deletions(-)

diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index ec966c7..4691b5b 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, bool missing_ok);
+static char *pg_get_partkeydef_worker(Oid relId, int prettyFlags);
 static char *pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 							int prettyFlags, bool missing_ok);
 static text *pg_get_expr_worker(text *expr, Oid relid, const char *relname,
@@ -1389,6 +1391,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 [ 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/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 4ee10fc..a127672 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -1253,9 +1253,10 @@ expand_table_name_patterns(Archive *fout,
 						  "SELECT c.oid"
 						  "\nFROM pg_catalog.pg_class c"
 		"\n     LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace"
-					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c')\n",
+					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c')\n",
 						  RELKIND_RELATION, RELKIND_SEQUENCE, RELKIND_VIEW,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_REL);
 		processSQLNamePattern(GetConnection(fout), query, cell->val, true,
 							  false, "n.nspname", "c.relname", NULL,
 							  "pg_catalog.pg_table_is_visible(c.oid)");
@@ -2117,6 +2118,9 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo, bool oids)
 	/* Skip FOREIGN TABLEs (no data to dump) */
 	if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 		return;
+	/* Skip partitioned tables (data in partitions) */
+	if (tbinfo->relkind == RELKIND_PARTITIONED_REL)
+		return;
 
 	/* Don't dump data in unlogged tables, if so requested */
 	if (tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED &&
@@ -5195,6 +5199,7 @@ getTables(Archive *fout, int *numTables)
 	int			i_reloftype;
 	int			i_relpages;
 	int			i_changed_acl;
+	int			i_partkeydef;
 
 	/* Make sure we are in proper schema */
 	selectSourceSchema(fout, "pg_catalog");
@@ -5280,7 +5285,8 @@ getTables(Archive *fout, int *numTables)
 						  "OR %s IS NOT NULL "
 						  "OR %s IS NOT NULL"
 						  "))"
-						  "AS changed_acl "
+						  "AS changed_acl, "
+						  "CASE WHEN c.relkind = 'P' THEN pg_catalog.pg_get_partkeydef(c.oid) ELSE NULL END AS partkeydef "
 						  "FROM pg_class c "
 						  "LEFT JOIN pg_depend d ON "
 						  "(c.relkind = '%c' AND "
@@ -5292,7 +5298,7 @@ getTables(Archive *fout, int *numTables)
 						  "(c.oid = pip.objoid "
 						  "AND pip.classoid = 'pg_class'::regclass "
 						  "AND pip.objsubid = 0) "
-				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c') "
+				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c', '%c') "
 						  "ORDER BY c.oid",
 						  acl_subquery->data,
 						  racl_subquery->data,
@@ -5306,7 +5312,8 @@ getTables(Archive *fout, int *numTables)
 						  RELKIND_SEQUENCE,
 						  RELKIND_RELATION, RELKIND_SEQUENCE,
 						  RELKIND_VIEW, RELKIND_COMPOSITE_TYPE,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_REL);
 
 		destroyPQExpBuffer(acl_subquery);
 		destroyPQExpBuffer(racl_subquery);
@@ -5868,6 +5875,7 @@ getTables(Archive *fout, int *numTables)
 	i_toastreloptions = PQfnumber(res, "toast_reloptions");
 	i_reloftype = PQfnumber(res, "reloftype");
 	i_changed_acl = PQfnumber(res, "changed_acl");
+	i_partkeydef = PQfnumber(res, "partkeydef");
 
 	if (dopt->lockWaitTimeout && fout->remoteVersion >= 70300)
 	{
@@ -5938,6 +5946,7 @@ getTables(Archive *fout, int *numTables)
 		else
 			tblinfo[i].checkoption = pg_strdup(PQgetvalue(res, i, i_checkoption));
 		tblinfo[i].toast_reloptions = pg_strdup(PQgetvalue(res, i, i_toastreloptions));
+		tblinfo[i].partkeydef = pg_strdup(PQgetvalue(res, i, i_partkeydef));
 
 		/* other fields were zeroed above */
 
@@ -5982,7 +5991,9 @@ getTables(Archive *fout, int *numTables)
 		 * We only need to lock the table for certain components; see
 		 * pg_dump.h
 		 */
-		if (tblinfo[i].dobj.dump && tblinfo[i].relkind == RELKIND_RELATION &&
+		if (tblinfo[i].dobj.dump &&
+			(tblinfo[i].relkind == RELKIND_RELATION ||
+			 tblinfo->relkind == RELKIND_PARTITIONED_REL) &&
 			(tblinfo[i].dobj.dump & DUMP_COMPONENTS_REQUIRING_LOCK))
 		{
 			resetPQExpBuffer(query);
@@ -6083,7 +6094,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);
 
@@ -15436,6 +15450,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));
 		}
@@ -15496,6 +15513,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++)
@@ -15514,7 +15532,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
@@ -15731,6 +15750,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 	 * dump properties we only have ALTER TABLE syntax for
 	 */
 	if ((tbinfo->relkind == RELKIND_RELATION ||
+		 tbinfo->relkind == RELKIND_PARTITIONED_REL ||
 		 tbinfo->relkind == RELKIND_MATVIEW) &&
 		tbinfo->relreplident != REPLICA_IDENTITY_DEFAULT)
 	{
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 2bfa2d9..0292859 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -310,6 +310,7 @@ typedef struct _tableInfo
 	bool	   *inhNotNull;		/* true if NOT NULL is inherited */
 	struct _attrDefInfo **attrdefs;		/* DEFAULT expressions */
 	struct _constraintInfo *checkexprs; /* CHECK constraints */
+	char	   *partkeydef;		/* partition key definition */
 
 	/*
 	 * Stuff computed only for dumpable tables.
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 27be102..3ba8a90 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -856,6 +856,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"),
@@ -865,6 +866,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");
@@ -911,7 +913,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
@@ -1555,8 +1557,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)");
 	}
 
@@ -1621,6 +1623,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\"",
@@ -1634,8 +1644,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");
@@ -1655,12 +1665,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");
 	}
 
@@ -1760,7 +1770,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);
@@ -1768,14 +1778,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 */
@@ -1914,7 +1943,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;
@@ -2473,7 +2502,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;
@@ -2684,7 +2713,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
@@ -3018,6 +3047,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"),
@@ -3029,6 +3059,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"));
 
@@ -3067,7 +3098,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 
 	appendPQExpBufferStr(&buf, "\nWHERE c.relkind IN (");
 	if (showTables)
-		appendPQExpBufferStr(&buf, "'r',");
+		appendPQExpBufferStr(&buf, "'r', 'P',");
 	if (showViews)
 		appendPQExpBufferStr(&buf, "'v',");
 	if (showMatViews)
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 8469d9f..cd4eb7c 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -427,7 +427,7 @@ static const SchemaQuery Query_for_list_of_tables = {
 	/* catname */
 	"pg_catalog.pg_class c",
 	/* selcondition */
-	"c.relkind IN ('r')",
+	"c.relkind IN ('r', 'P')",
 	/* viscondition */
 	"pg_catalog.pg_table_is_visible(c.oid)",
 	/* namespace */
@@ -458,7 +458,7 @@ static const SchemaQuery Query_for_list_of_updatables = {
 	/* catname */
 	"pg_catalog.pg_class c",
 	/* selcondition */
-	"c.relkind IN ('r', 'f', 'v')",
+	"c.relkind IN ('r', 'f', 'v', 'P')",
 	/* viscondition */
 	"pg_catalog.pg_table_is_visible(c.oid)",
 	/* namespace */
@@ -488,7 +488,7 @@ static const SchemaQuery Query_for_list_of_tsvmf = {
 	/* catname */
 	"pg_catalog.pg_class c",
 	/* selcondition */
-	"c.relkind IN ('r', 'S', 'v', 'm', 'f')",
+	"c.relkind IN ('r', 'S', 'v', 'm', 'f', 'P')",
 	/* viscondition */
 	"pg_catalog.pg_table_is_visible(c.oid)",
 	/* namespace */
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index 270dd21..5a444d9 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/utils/builtins.h b/src/include/utils/builtins.h
index 8cebc86..351bfd3 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -722,6 +722,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/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index fb7a1b3..4fc8c06 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -413,3 +413,29 @@ SELECT connoinherit FROM pg_constraint WHERE conrelid = 'no_inh_con_parted'::reg
 (1 row)
 
 DROP TABLE no_inh_con_parted;
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+Partitioned table "public.describe_range_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition Key: PARTITION BY RANGE (((a + b)))
+
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+Partitioned table "public.describe_list_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition Key: PARTITION BY LIST (a)
+
+DROP TABLE describe_range_key, describe_list_key;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index cb17517..7c7a18a 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -404,3 +404,16 @@ CREATE TABLE no_inh_con_parted (
 ) PARTITION BY RANGE (a);
 SELECT connoinherit FROM pg_constraint WHERE conrelid = 'no_inh_con_parted'::regclass;
 DROP TABLE no_inh_con_parted;
+
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+DROP TABLE describe_range_key, describe_list_key;
-- 
1.7.1


--------------8C4C48E640631A327D3D2826
Content-Type: text/x-diff;
 name="0003-Catalog-and-DDL-for-partitions.patch"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
 filename="0003-Catalog-and-DDL-for-partitions.patch"



^ permalink  raw  reply  [nested|flat] 110+ messages in thread

* [PATCH 2/8] psql and pg_dump support for partitioned tables.
@ 2016-07-12 08:20  amit <[email protected]>
  0 siblings, 0 replies; 110+ messages in thread

From: amit @ 2016-07-12 08:20 UTC (permalink / raw)

Takes care of both the partition key deparse stuff and the new relkind.
---
 src/backend/utils/adt/ruleutils.c          |  147 ++++++++++++++++++++++++++++
 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/pg_proc.h              |    2 +
 src/include/utils/builtins.h               |    1 +
 src/test/regress/expected/create_table.out |   26 +++++
 src/test/regress/sql/create_table.sql      |   13 +++
 9 files changed, 267 insertions(+), 26 deletions(-)

diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index ec966c7..4691b5b 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, bool missing_ok);
+static char *pg_get_partkeydef_worker(Oid relId, int prettyFlags);
 static char *pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 							int prettyFlags, bool missing_ok);
 static text *pg_get_expr_worker(text *expr, Oid relid, const char *relname,
@@ -1389,6 +1391,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 [ 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/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 4ee10fc..a127672 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -1253,9 +1253,10 @@ expand_table_name_patterns(Archive *fout,
 						  "SELECT c.oid"
 						  "\nFROM pg_catalog.pg_class c"
 		"\n     LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace"
-					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c')\n",
+					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c')\n",
 						  RELKIND_RELATION, RELKIND_SEQUENCE, RELKIND_VIEW,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_REL);
 		processSQLNamePattern(GetConnection(fout), query, cell->val, true,
 							  false, "n.nspname", "c.relname", NULL,
 							  "pg_catalog.pg_table_is_visible(c.oid)");
@@ -2117,6 +2118,9 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo, bool oids)
 	/* Skip FOREIGN TABLEs (no data to dump) */
 	if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 		return;
+	/* Skip partitioned tables (data in partitions) */
+	if (tbinfo->relkind == RELKIND_PARTITIONED_REL)
+		return;
 
 	/* Don't dump data in unlogged tables, if so requested */
 	if (tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED &&
@@ -5195,6 +5199,7 @@ getTables(Archive *fout, int *numTables)
 	int			i_reloftype;
 	int			i_relpages;
 	int			i_changed_acl;
+	int			i_partkeydef;
 
 	/* Make sure we are in proper schema */
 	selectSourceSchema(fout, "pg_catalog");
@@ -5280,7 +5285,8 @@ getTables(Archive *fout, int *numTables)
 						  "OR %s IS NOT NULL "
 						  "OR %s IS NOT NULL"
 						  "))"
-						  "AS changed_acl "
+						  "AS changed_acl, "
+						  "CASE WHEN c.relkind = 'P' THEN pg_catalog.pg_get_partkeydef(c.oid) ELSE NULL END AS partkeydef "
 						  "FROM pg_class c "
 						  "LEFT JOIN pg_depend d ON "
 						  "(c.relkind = '%c' AND "
@@ -5292,7 +5298,7 @@ getTables(Archive *fout, int *numTables)
 						  "(c.oid = pip.objoid "
 						  "AND pip.classoid = 'pg_class'::regclass "
 						  "AND pip.objsubid = 0) "
-				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c') "
+				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c', '%c') "
 						  "ORDER BY c.oid",
 						  acl_subquery->data,
 						  racl_subquery->data,
@@ -5306,7 +5312,8 @@ getTables(Archive *fout, int *numTables)
 						  RELKIND_SEQUENCE,
 						  RELKIND_RELATION, RELKIND_SEQUENCE,
 						  RELKIND_VIEW, RELKIND_COMPOSITE_TYPE,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_REL);
 
 		destroyPQExpBuffer(acl_subquery);
 		destroyPQExpBuffer(racl_subquery);
@@ -5868,6 +5875,7 @@ getTables(Archive *fout, int *numTables)
 	i_toastreloptions = PQfnumber(res, "toast_reloptions");
 	i_reloftype = PQfnumber(res, "reloftype");
 	i_changed_acl = PQfnumber(res, "changed_acl");
+	i_partkeydef = PQfnumber(res, "partkeydef");
 
 	if (dopt->lockWaitTimeout && fout->remoteVersion >= 70300)
 	{
@@ -5938,6 +5946,7 @@ getTables(Archive *fout, int *numTables)
 		else
 			tblinfo[i].checkoption = pg_strdup(PQgetvalue(res, i, i_checkoption));
 		tblinfo[i].toast_reloptions = pg_strdup(PQgetvalue(res, i, i_toastreloptions));
+		tblinfo[i].partkeydef = pg_strdup(PQgetvalue(res, i, i_partkeydef));
 
 		/* other fields were zeroed above */
 
@@ -5982,7 +5991,9 @@ getTables(Archive *fout, int *numTables)
 		 * We only need to lock the table for certain components; see
 		 * pg_dump.h
 		 */
-		if (tblinfo[i].dobj.dump && tblinfo[i].relkind == RELKIND_RELATION &&
+		if (tblinfo[i].dobj.dump &&
+			(tblinfo[i].relkind == RELKIND_RELATION ||
+			 tblinfo->relkind == RELKIND_PARTITIONED_REL) &&
 			(tblinfo[i].dobj.dump & DUMP_COMPONENTS_REQUIRING_LOCK))
 		{
 			resetPQExpBuffer(query);
@@ -6083,7 +6094,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);
 
@@ -15436,6 +15450,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));
 		}
@@ -15496,6 +15513,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++)
@@ -15514,7 +15532,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
@@ -15731,6 +15750,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 	 * dump properties we only have ALTER TABLE syntax for
 	 */
 	if ((tbinfo->relkind == RELKIND_RELATION ||
+		 tbinfo->relkind == RELKIND_PARTITIONED_REL ||
 		 tbinfo->relkind == RELKIND_MATVIEW) &&
 		tbinfo->relreplident != REPLICA_IDENTITY_DEFAULT)
 	{
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 2bfa2d9..0292859 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -310,6 +310,7 @@ typedef struct _tableInfo
 	bool	   *inhNotNull;		/* true if NOT NULL is inherited */
 	struct _attrDefInfo **attrdefs;		/* DEFAULT expressions */
 	struct _constraintInfo *checkexprs; /* CHECK constraints */
+	char	   *partkeydef;		/* partition key definition */
 
 	/*
 	 * Stuff computed only for dumpable tables.
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 27be102..3ba8a90 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -856,6 +856,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"),
@@ -865,6 +866,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");
@@ -911,7 +913,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
@@ -1555,8 +1557,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)");
 	}
 
@@ -1621,6 +1623,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\"",
@@ -1634,8 +1644,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");
@@ -1655,12 +1665,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");
 	}
 
@@ -1760,7 +1770,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);
@@ -1768,14 +1778,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 */
@@ -1914,7 +1943,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;
@@ -2473,7 +2502,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;
@@ -2684,7 +2713,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
@@ -3018,6 +3047,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"),
@@ -3029,6 +3059,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"));
 
@@ -3067,7 +3098,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 
 	appendPQExpBufferStr(&buf, "\nWHERE c.relkind IN (");
 	if (showTables)
-		appendPQExpBufferStr(&buf, "'r',");
+		appendPQExpBufferStr(&buf, "'r', 'P',");
 	if (showViews)
 		appendPQExpBufferStr(&buf, "'v',");
 	if (showMatViews)
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 8469d9f..cd4eb7c 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -427,7 +427,7 @@ static const SchemaQuery Query_for_list_of_tables = {
 	/* catname */
 	"pg_catalog.pg_class c",
 	/* selcondition */
-	"c.relkind IN ('r')",
+	"c.relkind IN ('r', 'P')",
 	/* viscondition */
 	"pg_catalog.pg_table_is_visible(c.oid)",
 	/* namespace */
@@ -458,7 +458,7 @@ static const SchemaQuery Query_for_list_of_updatables = {
 	/* catname */
 	"pg_catalog.pg_class c",
 	/* selcondition */
-	"c.relkind IN ('r', 'f', 'v')",
+	"c.relkind IN ('r', 'f', 'v', 'P')",
 	/* viscondition */
 	"pg_catalog.pg_table_is_visible(c.oid)",
 	/* namespace */
@@ -488,7 +488,7 @@ static const SchemaQuery Query_for_list_of_tsvmf = {
 	/* catname */
 	"pg_catalog.pg_class c",
 	/* selcondition */
-	"c.relkind IN ('r', 'S', 'v', 'm', 'f')",
+	"c.relkind IN ('r', 'S', 'v', 'm', 'f', 'P')",
 	/* viscondition */
 	"pg_catalog.pg_table_is_visible(c.oid)",
 	/* namespace */
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index 270dd21..5a444d9 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/utils/builtins.h b/src/include/utils/builtins.h
index 8cebc86..351bfd3 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -722,6 +722,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/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index fb7a1b3..4fc8c06 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -413,3 +413,29 @@ SELECT connoinherit FROM pg_constraint WHERE conrelid = 'no_inh_con_parted'::reg
 (1 row)
 
 DROP TABLE no_inh_con_parted;
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+Partitioned table "public.describe_range_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition Key: PARTITION BY RANGE (((a + b)))
+
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+Partitioned table "public.describe_list_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition Key: PARTITION BY LIST (a)
+
+DROP TABLE describe_range_key, describe_list_key;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index cb17517..7c7a18a 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -404,3 +404,16 @@ CREATE TABLE no_inh_con_parted (
 ) PARTITION BY RANGE (a);
 SELECT connoinherit FROM pg_constraint WHERE conrelid = 'no_inh_con_parted'::regclass;
 DROP TABLE no_inh_con_parted;
+
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+DROP TABLE describe_range_key, describe_list_key;
-- 
1.7.1


--------------8C4C48E640631A327D3D2826
Content-Type: text/x-diff;
 name="0003-Catalog-and-DDL-for-partitions.patch"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
 filename="0003-Catalog-and-DDL-for-partitions.patch"



^ permalink  raw  reply  [nested|flat] 110+ messages in thread

* [PATCH 2/9] psql and pg_dump support for partitioned tables.
@ 2016-07-12 08:20  amit <[email protected]>
  0 siblings, 0 replies; 110+ messages in thread

From: amit @ 2016-07-12 08:20 UTC (permalink / raw)

Takes care of both the partition key deparse stuff and the new relkind.
---
 src/backend/utils/adt/ruleutils.c          |  140 ++++++++++++++++++++++++++++
 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/pg_proc.h              |    2 +
 src/include/utils/builtins.h               |    1 +
 src/test/regress/expected/create_table.out |   26 +++++
 src/test/regress/sql/create_table.sql      |   13 +++
 9 files changed, 260 insertions(+), 26 deletions(-)

diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 8a81d7a..77ce807 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_table.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
@@ -315,6 +316,7 @@ static char *pg_get_indexdef_worker(Oid indexrelid, int colno,
 					   const Oid *excludeOps,
 					   bool attrsOnly, bool showTblSpc,
 					   int prettyFlags, bool missing_ok);
+static char *pg_get_partkeydef_worker(Oid relId, int prettyFlags);
 static char *pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 							int prettyFlags, bool missing_ok);
 static text *pg_get_expr_worker(text *expr, Oid relid, const char *relname,
@@ -1389,6 +1391,144 @@ 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 [ 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_table	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_table) GETSTRUCT(tuple);
+
+	Assert(form->partedrelid == relId);
+
+	/* Must get partclass, and partexprs the hard way */
+	datum = SysCacheGetAttr(PARTEDRELID, tuple,
+							Anum_pg_partitioned_table_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_table_partexprbin))
+	{
+		Datum		exprsDatum;
+		bool		isnull;
+		char	   *exprsString;
+
+		exprsDatum = SysCacheGetAttr(PARTEDRELID, tuple,
+									 Anum_pg_partitioned_table_partexprbin, &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);
+
+	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,
+											0, 0);
+
+			appendStringInfoString(&buf, 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/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index a5c2d09..c418ba7 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -1253,9 +1253,10 @@ expand_table_name_patterns(Archive *fout,
 						  "SELECT c.oid"
 						  "\nFROM pg_catalog.pg_class c"
 		"\n     LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace"
-					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c')\n",
+					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c')\n",
 						  RELKIND_RELATION, RELKIND_SEQUENCE, RELKIND_VIEW,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 		processSQLNamePattern(GetConnection(fout), query, cell->val, true,
 							  false, "n.nspname", "c.relname", NULL,
 							  "pg_catalog.pg_table_is_visible(c.oid)");
@@ -2117,6 +2118,9 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo, bool oids)
 	/* Skip FOREIGN TABLEs (no data to dump) */
 	if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 		return;
+	/* Skip partitioned tables (data in partitions) */
+	if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+		return;
 
 	/* Don't dump data in unlogged tables, if so requested */
 	if (tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED &&
@@ -5195,6 +5199,7 @@ getTables(Archive *fout, int *numTables)
 	int			i_reloftype;
 	int			i_relpages;
 	int			i_changed_acl;
+	int			i_partkeydef;
 
 	/* Make sure we are in proper schema */
 	selectSourceSchema(fout, "pg_catalog");
@@ -5280,7 +5285,8 @@ getTables(Archive *fout, int *numTables)
 						  "OR %s IS NOT NULL "
 						  "OR %s IS NOT NULL"
 						  "))"
-						  "AS changed_acl "
+						  "AS changed_acl, "
+						  "CASE WHEN c.relkind = 'P' THEN pg_catalog.pg_get_partkeydef(c.oid) ELSE NULL END AS partkeydef "
 						  "FROM pg_class c "
 						  "LEFT JOIN pg_depend d ON "
 						  "(c.relkind = '%c' AND "
@@ -5292,7 +5298,7 @@ getTables(Archive *fout, int *numTables)
 						  "(c.oid = pip.objoid "
 						  "AND pip.classoid = 'pg_class'::regclass "
 						  "AND pip.objsubid = 0) "
-				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c') "
+				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c', '%c') "
 						  "ORDER BY c.oid",
 						  acl_subquery->data,
 						  racl_subquery->data,
@@ -5306,7 +5312,8 @@ getTables(Archive *fout, int *numTables)
 						  RELKIND_SEQUENCE,
 						  RELKIND_RELATION, RELKIND_SEQUENCE,
 						  RELKIND_VIEW, RELKIND_COMPOSITE_TYPE,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 
 		destroyPQExpBuffer(acl_subquery);
 		destroyPQExpBuffer(racl_subquery);
@@ -5868,6 +5875,7 @@ getTables(Archive *fout, int *numTables)
 	i_toastreloptions = PQfnumber(res, "toast_reloptions");
 	i_reloftype = PQfnumber(res, "reloftype");
 	i_changed_acl = PQfnumber(res, "changed_acl");
+	i_partkeydef = PQfnumber(res, "partkeydef");
 
 	if (dopt->lockWaitTimeout && fout->remoteVersion >= 70300)
 	{
@@ -5938,6 +5946,7 @@ getTables(Archive *fout, int *numTables)
 		else
 			tblinfo[i].checkoption = pg_strdup(PQgetvalue(res, i, i_checkoption));
 		tblinfo[i].toast_reloptions = pg_strdup(PQgetvalue(res, i, i_toastreloptions));
+		tblinfo[i].partkeydef = pg_strdup(PQgetvalue(res, i, i_partkeydef));
 
 		/* other fields were zeroed above */
 
@@ -5982,7 +5991,9 @@ getTables(Archive *fout, int *numTables)
 		 * We only need to lock the table for certain components; see
 		 * pg_dump.h
 		 */
-		if (tblinfo[i].dobj.dump && tblinfo[i].relkind == RELKIND_RELATION &&
+		if (tblinfo[i].dobj.dump &&
+			(tblinfo[i].relkind == RELKIND_RELATION ||
+			 tblinfo->relkind == RELKIND_PARTITIONED_TABLE) &&
 			(tblinfo[i].dobj.dump & DUMP_COMPONENTS_REQUIRING_LOCK))
 		{
 			resetPQExpBuffer(query);
@@ -6084,7 +6095,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);
 
@@ -15437,6 +15451,9 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 				appendPQExpBufferChar(q, ')');
 			}
 
+			if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+				appendPQExpBuffer(q, "\nPARTITION BY %s", tbinfo->partkeydef);
+
 			if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 				appendPQExpBuffer(q, "\nSERVER %s", fmtId(srvname));
 		}
@@ -15497,6 +15514,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 		 */
 		if (dopt->binary_upgrade &&
 			(tbinfo->relkind == RELKIND_RELATION ||
+			 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 			 tbinfo->relkind == RELKIND_FOREIGN_TABLE))
 		{
 			for (j = 0; j < tbinfo->numatts; j++)
@@ -15515,7 +15533,8 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 					appendStringLiteralAH(q, fmtId(tbinfo->dobj.name), fout);
 					appendPQExpBufferStr(q, "::pg_catalog.regclass;\n");
 
-					if (tbinfo->relkind == RELKIND_RELATION)
+					if (tbinfo->relkind == RELKIND_RELATION ||
+						tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
 						appendPQExpBuffer(q, "ALTER TABLE ONLY %s ",
 										  fmtId(tbinfo->dobj.name));
 					else
@@ -15732,6 +15751,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 	 * dump properties we only have ALTER TABLE syntax for
 	 */
 	if ((tbinfo->relkind == RELKIND_RELATION ||
+		 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 		 tbinfo->relkind == RELKIND_MATVIEW) &&
 		tbinfo->relreplident != REPLICA_IDENTITY_DEFAULT)
 	{
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 2bfa2d9..0292859 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -310,6 +310,7 @@ typedef struct _tableInfo
 	bool	   *inhNotNull;		/* true if NOT NULL is inherited */
 	struct _attrDefInfo **attrdefs;		/* DEFAULT expressions */
 	struct _constraintInfo *checkexprs; /* CHECK constraints */
+	char	   *partkeydef;		/* partition key definition */
 
 	/*
 	 * Stuff computed only for dumpable tables.
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 6275a68..10d924a 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -861,6 +861,7 @@ permissionsList(const char *pattern)
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'S' THEN '%s'"
@@ -870,6 +871,7 @@ permissionsList(const char *pattern)
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("sequence"),
@@ -920,7 +922,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
@@ -1567,8 +1569,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)");
 	}
 
@@ -1633,6 +1635,14 @@ describeOneTableDetails(const char *schemaname,
 			printfPQExpBuffer(&title, _("Foreign table \"%s.%s\""),
 							  schemaname, relationname);
 			break;
+		case 'P':
+			if (tableinfo.relpersistence == 'u')
+				printfPQExpBuffer(&title, _("Unlogged table \"%s.%s\""),
+								  schemaname, relationname);
+			else
+				printfPQExpBuffer(&title, _("Table \"%s.%s\""),
+								  schemaname, relationname);
+			break;
 		default:
 			/* untranslated unknown relkind */
 			printfPQExpBuffer(&title, "?%c? \"%s.%s\"",
@@ -1646,8 +1656,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");
@@ -1667,12 +1677,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");
 	}
 
@@ -1772,7 +1782,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);
@@ -1780,14 +1790,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 (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 */
@@ -1926,7 +1955,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;
@@ -2485,7 +2514,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;
@@ -2696,7 +2725,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
@@ -3024,6 +3053,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'i' THEN '%s'"
@@ -3035,6 +3065,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("index"),
@@ -3079,7 +3110,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 1345e4e..4686cd1 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/pg_proc.h b/src/include/catalog/pg_proc.h
index e2d08ba..b45688b 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -1980,6 +1980,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/utils/builtins.h b/src/include/utils/builtins.h
index 2ae212a..e800647 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -729,6 +729,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/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 708232d..2fec847 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -408,3 +408,29 @@ CREATE TABLE no_inh_con_parted (
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
 ERROR:  cannot add NO INHERIT constraint to partitioned table "no_inh_con_parted"
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+Table "public.describe_range_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition Key: RANGE ((a + b))
+
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+Table "public.describe_list_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition Key: LIST (a)
+
+DROP TABLE describe_range_key, describe_list_key;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 6e4a8be..4dd6a0a 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -402,3 +402,16 @@ CREATE TABLE no_inh_con_parted (
 	a int,
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
+
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+DROP TABLE describe_range_key, describe_list_key;
-- 
1.7.1


--------------954458C17BBEB1BF6FC758E7
Content-Type: text/x-diff;
 name="0003-Catalog-and-DDL-for-partitions-2.patch"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
 filename="0003-Catalog-and-DDL-for-partitions-2.patch"



^ permalink  raw  reply  [nested|flat] 110+ messages in thread

* [PATCH 2/9] psql and pg_dump support for partitioned tables.
@ 2016-07-12 08:20  amit <[email protected]>
  0 siblings, 0 replies; 110+ messages in thread

From: amit @ 2016-07-12 08:20 UTC (permalink / raw)

Takes care of both the partition key deparse stuff and the new relkind.
---
 src/backend/utils/adt/ruleutils.c          |  140 ++++++++++++++++++++++++++++
 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/pg_proc.h              |    2 +
 src/include/utils/builtins.h               |    1 +
 src/test/regress/expected/create_table.out |   26 +++++
 src/test/regress/sql/create_table.sql      |   13 +++
 9 files changed, 260 insertions(+), 26 deletions(-)

diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 8a81d7a..77ce807 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_table.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
@@ -315,6 +316,7 @@ static char *pg_get_indexdef_worker(Oid indexrelid, int colno,
 					   const Oid *excludeOps,
 					   bool attrsOnly, bool showTblSpc,
 					   int prettyFlags, bool missing_ok);
+static char *pg_get_partkeydef_worker(Oid relId, int prettyFlags);
 static char *pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 							int prettyFlags, bool missing_ok);
 static text *pg_get_expr_worker(text *expr, Oid relid, const char *relname,
@@ -1389,6 +1391,144 @@ 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 [ 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_table	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_table) GETSTRUCT(tuple);
+
+	Assert(form->partedrelid == relId);
+
+	/* Must get partclass, and partexprs the hard way */
+	datum = SysCacheGetAttr(PARTEDRELID, tuple,
+							Anum_pg_partitioned_table_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_table_partexprbin))
+	{
+		Datum		exprsDatum;
+		bool		isnull;
+		char	   *exprsString;
+
+		exprsDatum = SysCacheGetAttr(PARTEDRELID, tuple,
+									 Anum_pg_partitioned_table_partexprbin, &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);
+
+	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,
+											0, 0);
+
+			appendStringInfoString(&buf, 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/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index a5c2d09..c418ba7 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -1253,9 +1253,10 @@ expand_table_name_patterns(Archive *fout,
 						  "SELECT c.oid"
 						  "\nFROM pg_catalog.pg_class c"
 		"\n     LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace"
-					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c')\n",
+					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c')\n",
 						  RELKIND_RELATION, RELKIND_SEQUENCE, RELKIND_VIEW,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 		processSQLNamePattern(GetConnection(fout), query, cell->val, true,
 							  false, "n.nspname", "c.relname", NULL,
 							  "pg_catalog.pg_table_is_visible(c.oid)");
@@ -2117,6 +2118,9 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo, bool oids)
 	/* Skip FOREIGN TABLEs (no data to dump) */
 	if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 		return;
+	/* Skip partitioned tables (data in partitions) */
+	if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+		return;
 
 	/* Don't dump data in unlogged tables, if so requested */
 	if (tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED &&
@@ -5195,6 +5199,7 @@ getTables(Archive *fout, int *numTables)
 	int			i_reloftype;
 	int			i_relpages;
 	int			i_changed_acl;
+	int			i_partkeydef;
 
 	/* Make sure we are in proper schema */
 	selectSourceSchema(fout, "pg_catalog");
@@ -5280,7 +5285,8 @@ getTables(Archive *fout, int *numTables)
 						  "OR %s IS NOT NULL "
 						  "OR %s IS NOT NULL"
 						  "))"
-						  "AS changed_acl "
+						  "AS changed_acl, "
+						  "CASE WHEN c.relkind = 'P' THEN pg_catalog.pg_get_partkeydef(c.oid) ELSE NULL END AS partkeydef "
 						  "FROM pg_class c "
 						  "LEFT JOIN pg_depend d ON "
 						  "(c.relkind = '%c' AND "
@@ -5292,7 +5298,7 @@ getTables(Archive *fout, int *numTables)
 						  "(c.oid = pip.objoid "
 						  "AND pip.classoid = 'pg_class'::regclass "
 						  "AND pip.objsubid = 0) "
-				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c') "
+				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c', '%c') "
 						  "ORDER BY c.oid",
 						  acl_subquery->data,
 						  racl_subquery->data,
@@ -5306,7 +5312,8 @@ getTables(Archive *fout, int *numTables)
 						  RELKIND_SEQUENCE,
 						  RELKIND_RELATION, RELKIND_SEQUENCE,
 						  RELKIND_VIEW, RELKIND_COMPOSITE_TYPE,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 
 		destroyPQExpBuffer(acl_subquery);
 		destroyPQExpBuffer(racl_subquery);
@@ -5868,6 +5875,7 @@ getTables(Archive *fout, int *numTables)
 	i_toastreloptions = PQfnumber(res, "toast_reloptions");
 	i_reloftype = PQfnumber(res, "reloftype");
 	i_changed_acl = PQfnumber(res, "changed_acl");
+	i_partkeydef = PQfnumber(res, "partkeydef");
 
 	if (dopt->lockWaitTimeout && fout->remoteVersion >= 70300)
 	{
@@ -5938,6 +5946,7 @@ getTables(Archive *fout, int *numTables)
 		else
 			tblinfo[i].checkoption = pg_strdup(PQgetvalue(res, i, i_checkoption));
 		tblinfo[i].toast_reloptions = pg_strdup(PQgetvalue(res, i, i_toastreloptions));
+		tblinfo[i].partkeydef = pg_strdup(PQgetvalue(res, i, i_partkeydef));
 
 		/* other fields were zeroed above */
 
@@ -5982,7 +5991,9 @@ getTables(Archive *fout, int *numTables)
 		 * We only need to lock the table for certain components; see
 		 * pg_dump.h
 		 */
-		if (tblinfo[i].dobj.dump && tblinfo[i].relkind == RELKIND_RELATION &&
+		if (tblinfo[i].dobj.dump &&
+			(tblinfo[i].relkind == RELKIND_RELATION ||
+			 tblinfo->relkind == RELKIND_PARTITIONED_TABLE) &&
 			(tblinfo[i].dobj.dump & DUMP_COMPONENTS_REQUIRING_LOCK))
 		{
 			resetPQExpBuffer(query);
@@ -6084,7 +6095,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);
 
@@ -15437,6 +15451,9 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 				appendPQExpBufferChar(q, ')');
 			}
 
+			if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+				appendPQExpBuffer(q, "\nPARTITION BY %s", tbinfo->partkeydef);
+
 			if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 				appendPQExpBuffer(q, "\nSERVER %s", fmtId(srvname));
 		}
@@ -15497,6 +15514,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 		 */
 		if (dopt->binary_upgrade &&
 			(tbinfo->relkind == RELKIND_RELATION ||
+			 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 			 tbinfo->relkind == RELKIND_FOREIGN_TABLE))
 		{
 			for (j = 0; j < tbinfo->numatts; j++)
@@ -15515,7 +15533,8 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 					appendStringLiteralAH(q, fmtId(tbinfo->dobj.name), fout);
 					appendPQExpBufferStr(q, "::pg_catalog.regclass;\n");
 
-					if (tbinfo->relkind == RELKIND_RELATION)
+					if (tbinfo->relkind == RELKIND_RELATION ||
+						tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
 						appendPQExpBuffer(q, "ALTER TABLE ONLY %s ",
 										  fmtId(tbinfo->dobj.name));
 					else
@@ -15732,6 +15751,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 	 * dump properties we only have ALTER TABLE syntax for
 	 */
 	if ((tbinfo->relkind == RELKIND_RELATION ||
+		 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 		 tbinfo->relkind == RELKIND_MATVIEW) &&
 		tbinfo->relreplident != REPLICA_IDENTITY_DEFAULT)
 	{
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 2bfa2d9..0292859 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -310,6 +310,7 @@ typedef struct _tableInfo
 	bool	   *inhNotNull;		/* true if NOT NULL is inherited */
 	struct _attrDefInfo **attrdefs;		/* DEFAULT expressions */
 	struct _constraintInfo *checkexprs; /* CHECK constraints */
+	char	   *partkeydef;		/* partition key definition */
 
 	/*
 	 * Stuff computed only for dumpable tables.
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 6275a68..10d924a 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -861,6 +861,7 @@ permissionsList(const char *pattern)
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'S' THEN '%s'"
@@ -870,6 +871,7 @@ permissionsList(const char *pattern)
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("sequence"),
@@ -920,7 +922,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
@@ -1567,8 +1569,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)");
 	}
 
@@ -1633,6 +1635,14 @@ describeOneTableDetails(const char *schemaname,
 			printfPQExpBuffer(&title, _("Foreign table \"%s.%s\""),
 							  schemaname, relationname);
 			break;
+		case 'P':
+			if (tableinfo.relpersistence == 'u')
+				printfPQExpBuffer(&title, _("Unlogged table \"%s.%s\""),
+								  schemaname, relationname);
+			else
+				printfPQExpBuffer(&title, _("Table \"%s.%s\""),
+								  schemaname, relationname);
+			break;
 		default:
 			/* untranslated unknown relkind */
 			printfPQExpBuffer(&title, "?%c? \"%s.%s\"",
@@ -1646,8 +1656,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");
@@ -1667,12 +1677,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");
 	}
 
@@ -1772,7 +1782,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);
@@ -1780,14 +1790,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 (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 */
@@ -1926,7 +1955,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;
@@ -2485,7 +2514,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;
@@ -2696,7 +2725,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
@@ -3024,6 +3053,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'i' THEN '%s'"
@@ -3035,6 +3065,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("index"),
@@ -3079,7 +3110,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 019f75a..5e5c370 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/pg_proc.h b/src/include/catalog/pg_proc.h
index e2d08ba..b45688b 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -1980,6 +1980,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/utils/builtins.h b/src/include/utils/builtins.h
index 2ae212a..e800647 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -729,6 +729,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/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 708232d..2fec847 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -408,3 +408,29 @@ CREATE TABLE no_inh_con_parted (
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
 ERROR:  cannot add NO INHERIT constraint to partitioned table "no_inh_con_parted"
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+Table "public.describe_range_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition Key: RANGE ((a + b))
+
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+Table "public.describe_list_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition Key: LIST (a)
+
+DROP TABLE describe_range_key, describe_list_key;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 6e4a8be..4dd6a0a 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -402,3 +402,16 @@ CREATE TABLE no_inh_con_parted (
 	a int,
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
+
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+DROP TABLE describe_range_key, describe_list_key;
-- 
1.7.1


--------------6EB5B318125CB7DB1A4FD229
Content-Type: text/x-diff;
 name="0003-Catalog-and-DDL-for-partitions-3.patch"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
 filename="0003-Catalog-and-DDL-for-partitions-3.patch"



^ permalink  raw  reply  [nested|flat] 110+ messages in thread

* [PATCH 2/8] psql and pg_dump support for partitioned tables.
@ 2016-07-12 08:20  amit <[email protected]>
  0 siblings, 0 replies; 110+ messages in thread

From: amit @ 2016-07-12 08:20 UTC (permalink / raw)

Takes care of both the partition key deparse stuff and the new relkind.
---
 src/backend/utils/adt/ruleutils.c          |  147 ++++++++++++++++++++++++++++
 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/pg_proc.h              |    2 +
 src/include/utils/builtins.h               |    1 +
 src/test/regress/expected/create_table.out |   26 +++++
 src/test/regress/sql/create_table.sql      |   13 +++
 9 files changed, 267 insertions(+), 26 deletions(-)

diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index ec966c7..4691b5b 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, bool missing_ok);
+static char *pg_get_partkeydef_worker(Oid relId, int prettyFlags);
 static char *pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 							int prettyFlags, bool missing_ok);
 static text *pg_get_expr_worker(text *expr, Oid relid, const char *relname,
@@ -1389,6 +1391,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 [ 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/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 4ee10fc..a127672 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -1253,9 +1253,10 @@ expand_table_name_patterns(Archive *fout,
 						  "SELECT c.oid"
 						  "\nFROM pg_catalog.pg_class c"
 		"\n     LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace"
-					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c')\n",
+					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c')\n",
 						  RELKIND_RELATION, RELKIND_SEQUENCE, RELKIND_VIEW,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_REL);
 		processSQLNamePattern(GetConnection(fout), query, cell->val, true,
 							  false, "n.nspname", "c.relname", NULL,
 							  "pg_catalog.pg_table_is_visible(c.oid)");
@@ -2117,6 +2118,9 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo, bool oids)
 	/* Skip FOREIGN TABLEs (no data to dump) */
 	if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 		return;
+	/* Skip partitioned tables (data in partitions) */
+	if (tbinfo->relkind == RELKIND_PARTITIONED_REL)
+		return;
 
 	/* Don't dump data in unlogged tables, if so requested */
 	if (tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED &&
@@ -5195,6 +5199,7 @@ getTables(Archive *fout, int *numTables)
 	int			i_reloftype;
 	int			i_relpages;
 	int			i_changed_acl;
+	int			i_partkeydef;
 
 	/* Make sure we are in proper schema */
 	selectSourceSchema(fout, "pg_catalog");
@@ -5280,7 +5285,8 @@ getTables(Archive *fout, int *numTables)
 						  "OR %s IS NOT NULL "
 						  "OR %s IS NOT NULL"
 						  "))"
-						  "AS changed_acl "
+						  "AS changed_acl, "
+						  "CASE WHEN c.relkind = 'P' THEN pg_catalog.pg_get_partkeydef(c.oid) ELSE NULL END AS partkeydef "
 						  "FROM pg_class c "
 						  "LEFT JOIN pg_depend d ON "
 						  "(c.relkind = '%c' AND "
@@ -5292,7 +5298,7 @@ getTables(Archive *fout, int *numTables)
 						  "(c.oid = pip.objoid "
 						  "AND pip.classoid = 'pg_class'::regclass "
 						  "AND pip.objsubid = 0) "
-				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c') "
+				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c', '%c') "
 						  "ORDER BY c.oid",
 						  acl_subquery->data,
 						  racl_subquery->data,
@@ -5306,7 +5312,8 @@ getTables(Archive *fout, int *numTables)
 						  RELKIND_SEQUENCE,
 						  RELKIND_RELATION, RELKIND_SEQUENCE,
 						  RELKIND_VIEW, RELKIND_COMPOSITE_TYPE,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_REL);
 
 		destroyPQExpBuffer(acl_subquery);
 		destroyPQExpBuffer(racl_subquery);
@@ -5868,6 +5875,7 @@ getTables(Archive *fout, int *numTables)
 	i_toastreloptions = PQfnumber(res, "toast_reloptions");
 	i_reloftype = PQfnumber(res, "reloftype");
 	i_changed_acl = PQfnumber(res, "changed_acl");
+	i_partkeydef = PQfnumber(res, "partkeydef");
 
 	if (dopt->lockWaitTimeout && fout->remoteVersion >= 70300)
 	{
@@ -5938,6 +5946,7 @@ getTables(Archive *fout, int *numTables)
 		else
 			tblinfo[i].checkoption = pg_strdup(PQgetvalue(res, i, i_checkoption));
 		tblinfo[i].toast_reloptions = pg_strdup(PQgetvalue(res, i, i_toastreloptions));
+		tblinfo[i].partkeydef = pg_strdup(PQgetvalue(res, i, i_partkeydef));
 
 		/* other fields were zeroed above */
 
@@ -5982,7 +5991,9 @@ getTables(Archive *fout, int *numTables)
 		 * We only need to lock the table for certain components; see
 		 * pg_dump.h
 		 */
-		if (tblinfo[i].dobj.dump && tblinfo[i].relkind == RELKIND_RELATION &&
+		if (tblinfo[i].dobj.dump &&
+			(tblinfo[i].relkind == RELKIND_RELATION ||
+			 tblinfo->relkind == RELKIND_PARTITIONED_REL) &&
 			(tblinfo[i].dobj.dump & DUMP_COMPONENTS_REQUIRING_LOCK))
 		{
 			resetPQExpBuffer(query);
@@ -6083,7 +6094,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);
 
@@ -15436,6 +15450,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));
 		}
@@ -15496,6 +15513,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++)
@@ -15514,7 +15532,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
@@ -15731,6 +15750,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 	 * dump properties we only have ALTER TABLE syntax for
 	 */
 	if ((tbinfo->relkind == RELKIND_RELATION ||
+		 tbinfo->relkind == RELKIND_PARTITIONED_REL ||
 		 tbinfo->relkind == RELKIND_MATVIEW) &&
 		tbinfo->relreplident != REPLICA_IDENTITY_DEFAULT)
 	{
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 2bfa2d9..0292859 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -310,6 +310,7 @@ typedef struct _tableInfo
 	bool	   *inhNotNull;		/* true if NOT NULL is inherited */
 	struct _attrDefInfo **attrdefs;		/* DEFAULT expressions */
 	struct _constraintInfo *checkexprs; /* CHECK constraints */
+	char	   *partkeydef;		/* partition key definition */
 
 	/*
 	 * Stuff computed only for dumpable tables.
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 27be102..3ba8a90 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -856,6 +856,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"),
@@ -865,6 +866,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");
@@ -911,7 +913,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
@@ -1555,8 +1557,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)");
 	}
 
@@ -1621,6 +1623,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\"",
@@ -1634,8 +1644,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");
@@ -1655,12 +1665,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");
 	}
 
@@ -1760,7 +1770,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);
@@ -1768,14 +1778,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 */
@@ -1914,7 +1943,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;
@@ -2473,7 +2502,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;
@@ -2684,7 +2713,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
@@ -3018,6 +3047,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"),
@@ -3029,6 +3059,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"));
 
@@ -3067,7 +3098,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 
 	appendPQExpBufferStr(&buf, "\nWHERE c.relkind IN (");
 	if (showTables)
-		appendPQExpBufferStr(&buf, "'r',");
+		appendPQExpBufferStr(&buf, "'r', 'P',");
 	if (showViews)
 		appendPQExpBufferStr(&buf, "'v',");
 	if (showMatViews)
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 8469d9f..cd4eb7c 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -427,7 +427,7 @@ static const SchemaQuery Query_for_list_of_tables = {
 	/* catname */
 	"pg_catalog.pg_class c",
 	/* selcondition */
-	"c.relkind IN ('r')",
+	"c.relkind IN ('r', 'P')",
 	/* viscondition */
 	"pg_catalog.pg_table_is_visible(c.oid)",
 	/* namespace */
@@ -458,7 +458,7 @@ static const SchemaQuery Query_for_list_of_updatables = {
 	/* catname */
 	"pg_catalog.pg_class c",
 	/* selcondition */
-	"c.relkind IN ('r', 'f', 'v')",
+	"c.relkind IN ('r', 'f', 'v', 'P')",
 	/* viscondition */
 	"pg_catalog.pg_table_is_visible(c.oid)",
 	/* namespace */
@@ -488,7 +488,7 @@ static const SchemaQuery Query_for_list_of_tsvmf = {
 	/* catname */
 	"pg_catalog.pg_class c",
 	/* selcondition */
-	"c.relkind IN ('r', 'S', 'v', 'm', 'f')",
+	"c.relkind IN ('r', 'S', 'v', 'm', 'f', 'P')",
 	/* viscondition */
 	"pg_catalog.pg_table_is_visible(c.oid)",
 	/* namespace */
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index 270dd21..5a444d9 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/utils/builtins.h b/src/include/utils/builtins.h
index 8cebc86..351bfd3 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -722,6 +722,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/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index fb7a1b3..4fc8c06 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -413,3 +413,29 @@ SELECT connoinherit FROM pg_constraint WHERE conrelid = 'no_inh_con_parted'::reg
 (1 row)
 
 DROP TABLE no_inh_con_parted;
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+Partitioned table "public.describe_range_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition Key: PARTITION BY RANGE (((a + b)))
+
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+Partitioned table "public.describe_list_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition Key: PARTITION BY LIST (a)
+
+DROP TABLE describe_range_key, describe_list_key;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index cb17517..7c7a18a 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -404,3 +404,16 @@ CREATE TABLE no_inh_con_parted (
 ) PARTITION BY RANGE (a);
 SELECT connoinherit FROM pg_constraint WHERE conrelid = 'no_inh_con_parted'::regclass;
 DROP TABLE no_inh_con_parted;
+
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+DROP TABLE describe_range_key, describe_list_key;
-- 
1.7.1


--------------8C4C48E640631A327D3D2826
Content-Type: text/x-diff;
 name="0003-Catalog-and-DDL-for-partitions.patch"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
 filename="0003-Catalog-and-DDL-for-partitions.patch"



^ permalink  raw  reply  [nested|flat] 110+ messages in thread

* [PATCH 2/9] psql and pg_dump support for partitioned tables.
@ 2016-07-12 08:20  amit <[email protected]>
  0 siblings, 0 replies; 110+ messages in thread

From: amit @ 2016-07-12 08:20 UTC (permalink / raw)

Takes care of both the partition key deparse stuff and the new relkind.
---
 src/backend/utils/adt/ruleutils.c          |  140 ++++++++++++++++++++++++++++
 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/pg_proc.h              |    2 +
 src/include/utils/builtins.h               |    1 +
 src/test/regress/expected/create_table.out |   26 +++++
 src/test/regress/sql/create_table.sql      |   13 +++
 9 files changed, 260 insertions(+), 26 deletions(-)

diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 8a81d7a..03be202 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_table.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
@@ -315,6 +316,7 @@ static char *pg_get_indexdef_worker(Oid indexrelid, int colno,
 					   const Oid *excludeOps,
 					   bool attrsOnly, bool showTblSpc,
 					   int prettyFlags, bool missing_ok);
+static char *pg_get_partkeydef_worker(Oid relid, int prettyFlags);
 static char *pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 							int prettyFlags, bool missing_ok);
 static text *pg_get_expr_worker(text *expr, Oid relid, const char *relname,
@@ -1389,6 +1391,144 @@ 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 [ opclass_name ] [, ...])
+ */
+Datum
+pg_get_partkeydef(PG_FUNCTION_ARGS)
+{
+	Oid			relid = PG_GETARG_OID(0);
+	int			prettyFlags;
+
+	prettyFlags = PRETTYFLAG_INDENT;
+	PG_RETURN_TEXT_P(string_to_text(pg_get_partkeydef_worker(relid,
+									prettyFlags)));
+}
+
+/*
+ * Internal workhorse to decompile a partition key definition.
+ */
+static char *
+pg_get_partkeydef_worker(Oid relid, int prettyFlags)
+{
+	Form_pg_partitioned_table	form;
+	HeapTuple	tuple;
+	oidvector  *partclass;
+	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_table) GETSTRUCT(tuple);
+
+	Assert(form->partrelid == relid);
+
+	/* Must get partclass, and partexprs the hard way */
+	datum = SysCacheGetAttr(PARTEDRELID, tuple,
+							Anum_pg_partitioned_table_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_table_partexprbin))
+	{
+		Datum		exprsDatum;
+		bool		isnull;
+		char	   *exprsString;
+
+		exprsDatum = SysCacheGetAttr(PARTEDRELID, tuple,
+									 Anum_pg_partitioned_table_partexprbin, &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);
+
+	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,
+											0, 0);
+
+			appendStringInfoString(&buf, 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/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index ba9c276..c805a84 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -1253,9 +1253,10 @@ expand_table_name_patterns(Archive *fout,
 						  "SELECT c.oid"
 						  "\nFROM pg_catalog.pg_class c"
 		"\n     LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace"
-					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c')\n",
+					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c')\n",
 						  RELKIND_RELATION, RELKIND_SEQUENCE, RELKIND_VIEW,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 		processSQLNamePattern(GetConnection(fout), query, cell->val, true,
 							  false, "n.nspname", "c.relname", NULL,
 							  "pg_catalog.pg_table_is_visible(c.oid)");
@@ -2125,6 +2126,9 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo, bool oids)
 	/* Skip FOREIGN TABLEs (no data to dump) */
 	if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 		return;
+	/* Skip partitioned tables (data in partitions) */
+	if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+		return;
 
 	/* Don't dump data in unlogged tables, if so requested */
 	if (tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED &&
@@ -5204,6 +5208,7 @@ getTables(Archive *fout, int *numTables)
 	int			i_reloftype;
 	int			i_relpages;
 	int			i_changed_acl;
+	int			i_partkeydef;
 
 	/* Make sure we are in proper schema */
 	selectSourceSchema(fout, "pg_catalog");
@@ -5289,7 +5294,8 @@ getTables(Archive *fout, int *numTables)
 						  "OR %s IS NOT NULL "
 						  "OR %s IS NOT NULL"
 						  "))"
-						  "AS changed_acl "
+						  "AS changed_acl, "
+						  "CASE WHEN c.relkind = 'P' THEN pg_catalog.pg_get_partkeydef(c.oid) ELSE NULL END AS partkeydef "
 						  "FROM pg_class c "
 						  "LEFT JOIN pg_depend d ON "
 						  "(c.relkind = '%c' AND "
@@ -5301,7 +5307,7 @@ getTables(Archive *fout, int *numTables)
 						  "(c.oid = pip.objoid "
 						  "AND pip.classoid = 'pg_class'::regclass "
 						  "AND pip.objsubid = 0) "
-				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c') "
+				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c', '%c') "
 						  "ORDER BY c.oid",
 						  acl_subquery->data,
 						  racl_subquery->data,
@@ -5315,7 +5321,8 @@ getTables(Archive *fout, int *numTables)
 						  RELKIND_SEQUENCE,
 						  RELKIND_RELATION, RELKIND_SEQUENCE,
 						  RELKIND_VIEW, RELKIND_COMPOSITE_TYPE,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 
 		destroyPQExpBuffer(acl_subquery);
 		destroyPQExpBuffer(racl_subquery);
@@ -5877,6 +5884,7 @@ getTables(Archive *fout, int *numTables)
 	i_toastreloptions = PQfnumber(res, "toast_reloptions");
 	i_reloftype = PQfnumber(res, "reloftype");
 	i_changed_acl = PQfnumber(res, "changed_acl");
+	i_partkeydef = PQfnumber(res, "partkeydef");
 
 	if (dopt->lockWaitTimeout && fout->remoteVersion >= 70300)
 	{
@@ -5947,6 +5955,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 */
 
@@ -5991,7 +6000,9 @@ getTables(Archive *fout, int *numTables)
 		 * We only need to lock the table for certain components; see
 		 * pg_dump.h
 		 */
-		if (tblinfo[i].dobj.dump && tblinfo[i].relkind == RELKIND_RELATION &&
+		if (tblinfo[i].dobj.dump &&
+			(tblinfo[i].relkind == RELKIND_RELATION ||
+			 tblinfo->relkind == RELKIND_PARTITIONED_TABLE) &&
 			(tblinfo[i].dobj.dump & DUMP_COMPONENTS_REQUIRING_LOCK))
 		{
 			resetPQExpBuffer(query);
@@ -6093,7 +6104,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);
 
@@ -15446,6 +15460,9 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 				appendPQExpBufferChar(q, ')');
 			}
 
+			if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+				appendPQExpBuffer(q, "\nPARTITION BY %s", tbinfo->partkeydef);
+
 			if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 				appendPQExpBuffer(q, "\nSERVER %s", fmtId(srvname));
 		}
@@ -15506,6 +15523,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 		 */
 		if (dopt->binary_upgrade &&
 			(tbinfo->relkind == RELKIND_RELATION ||
+			 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 			 tbinfo->relkind == RELKIND_FOREIGN_TABLE))
 		{
 			for (j = 0; j < tbinfo->numatts; j++)
@@ -15524,7 +15542,8 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 					appendStringLiteralAH(q, fmtId(tbinfo->dobj.name), fout);
 					appendPQExpBufferStr(q, "::pg_catalog.regclass;\n");
 
-					if (tbinfo->relkind == RELKIND_RELATION)
+					if (tbinfo->relkind == RELKIND_RELATION ||
+						tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
 						appendPQExpBuffer(q, "ALTER TABLE ONLY %s ",
 										  fmtId(tbinfo->dobj.name));
 					else
@@ -15741,6 +15760,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 	 * dump properties we only have ALTER TABLE syntax for
 	 */
 	if ((tbinfo->relkind == RELKIND_RELATION ||
+		 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 		 tbinfo->relkind == RELKIND_MATVIEW) &&
 		tbinfo->relreplident != REPLICA_IDENTITY_DEFAULT)
 	{
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 2bfa2d9..0292859 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -310,6 +310,7 @@ typedef struct _tableInfo
 	bool	   *inhNotNull;		/* true if NOT NULL is inherited */
 	struct _attrDefInfo **attrdefs;		/* DEFAULT expressions */
 	struct _constraintInfo *checkexprs; /* CHECK constraints */
+	char	   *partkeydef;		/* partition key definition */
 
 	/*
 	 * Stuff computed only for dumpable tables.
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 6275a68..10d924a 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -861,6 +861,7 @@ permissionsList(const char *pattern)
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'S' THEN '%s'"
@@ -870,6 +871,7 @@ permissionsList(const char *pattern)
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("sequence"),
@@ -920,7 +922,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
@@ -1567,8 +1569,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)");
 	}
 
@@ -1633,6 +1635,14 @@ describeOneTableDetails(const char *schemaname,
 			printfPQExpBuffer(&title, _("Foreign table \"%s.%s\""),
 							  schemaname, relationname);
 			break;
+		case 'P':
+			if (tableinfo.relpersistence == 'u')
+				printfPQExpBuffer(&title, _("Unlogged table \"%s.%s\""),
+								  schemaname, relationname);
+			else
+				printfPQExpBuffer(&title, _("Table \"%s.%s\""),
+								  schemaname, relationname);
+			break;
 		default:
 			/* untranslated unknown relkind */
 			printfPQExpBuffer(&title, "?%c? \"%s.%s\"",
@@ -1646,8 +1656,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");
@@ -1667,12 +1677,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");
 	}
 
@@ -1772,7 +1782,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);
@@ -1780,14 +1790,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 (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 */
@@ -1926,7 +1955,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;
@@ -2485,7 +2514,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;
@@ -2696,7 +2725,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
@@ -3024,6 +3053,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'i' THEN '%s'"
@@ -3035,6 +3065,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("index"),
@@ -3079,7 +3110,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 50a45eb..8284a9c 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/pg_proc.h b/src/include/catalog/pg_proc.h
index e2d08ba..b45688b 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -1980,6 +1980,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/utils/builtins.h b/src/include/utils/builtins.h
index 2ae212a..e800647 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -729,6 +729,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/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 0af1422..91ba1be 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -412,3 +412,29 @@ CREATE TABLE no_inh_con_parted (
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
 ERROR:  cannot add NO INHERIT constraint to partitioned table "no_inh_con_parted"
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+Table "public.describe_range_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition Key: RANGE ((a + b))
+
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+Table "public.describe_list_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition Key: LIST (a)
+
+DROP TABLE describe_range_key, describe_list_key;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 418ac55..b75542d 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -406,3 +406,16 @@ CREATE TABLE no_inh_con_parted (
 	a int,
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
+
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+DROP TABLE describe_range_key, describe_list_key;
-- 
1.7.1


--------------D008BA677674C4836D7B65AD
Content-Type: text/x-diff;
 name="0003-Catalog-and-DDL-for-partitions-5.patch"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
 filename="0003-Catalog-and-DDL-for-partitions-5.patch"



^ permalink  raw  reply  [nested|flat] 110+ messages in thread

* [PATCH 2/9] psql and pg_dump support for partitioned tables.
@ 2016-07-12 08:20  amit <[email protected]>
  0 siblings, 0 replies; 110+ messages in thread

From: amit @ 2016-07-12 08:20 UTC (permalink / raw)

Takes care of both the partition key deparse stuff and the new relkind.
---
 src/backend/utils/adt/ruleutils.c          |  140 ++++++++++++++++++++++++++++
 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/pg_proc.h              |    2 +
 src/include/utils/builtins.h               |    1 +
 src/test/regress/expected/create_table.out |   26 +++++
 src/test/regress/sql/create_table.sql      |   13 +++
 9 files changed, 260 insertions(+), 26 deletions(-)

diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 8a81d7a..03be202 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_table.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
@@ -315,6 +316,7 @@ static char *pg_get_indexdef_worker(Oid indexrelid, int colno,
 					   const Oid *excludeOps,
 					   bool attrsOnly, bool showTblSpc,
 					   int prettyFlags, bool missing_ok);
+static char *pg_get_partkeydef_worker(Oid relid, int prettyFlags);
 static char *pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 							int prettyFlags, bool missing_ok);
 static text *pg_get_expr_worker(text *expr, Oid relid, const char *relname,
@@ -1389,6 +1391,144 @@ 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 [ opclass_name ] [, ...])
+ */
+Datum
+pg_get_partkeydef(PG_FUNCTION_ARGS)
+{
+	Oid			relid = PG_GETARG_OID(0);
+	int			prettyFlags;
+
+	prettyFlags = PRETTYFLAG_INDENT;
+	PG_RETURN_TEXT_P(string_to_text(pg_get_partkeydef_worker(relid,
+									prettyFlags)));
+}
+
+/*
+ * Internal workhorse to decompile a partition key definition.
+ */
+static char *
+pg_get_partkeydef_worker(Oid relid, int prettyFlags)
+{
+	Form_pg_partitioned_table	form;
+	HeapTuple	tuple;
+	oidvector  *partclass;
+	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_table) GETSTRUCT(tuple);
+
+	Assert(form->partrelid == relid);
+
+	/* Must get partclass, and partexprs the hard way */
+	datum = SysCacheGetAttr(PARTEDRELID, tuple,
+							Anum_pg_partitioned_table_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_table_partexprbin))
+	{
+		Datum		exprsDatum;
+		bool		isnull;
+		char	   *exprsString;
+
+		exprsDatum = SysCacheGetAttr(PARTEDRELID, tuple,
+									 Anum_pg_partitioned_table_partexprbin, &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);
+
+	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,
+											0, 0);
+
+			appendStringInfoString(&buf, 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/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 51b8a1a..854dddc 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -1253,9 +1253,10 @@ expand_table_name_patterns(Archive *fout,
 						  "SELECT c.oid"
 						  "\nFROM pg_catalog.pg_class c"
 		"\n     LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace"
-					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c')\n",
+					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c')\n",
 						  RELKIND_RELATION, RELKIND_SEQUENCE, RELKIND_VIEW,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 		processSQLNamePattern(GetConnection(fout), query, cell->val, true,
 							  false, "n.nspname", "c.relname", NULL,
 							  "pg_catalog.pg_table_is_visible(c.oid)");
@@ -2125,6 +2126,9 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo, bool oids)
 	/* Skip FOREIGN TABLEs (no data to dump) */
 	if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 		return;
+	/* Skip partitioned tables (data in partitions) */
+	if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+		return;
 
 	/* Don't dump data in unlogged tables, if so requested */
 	if (tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED &&
@@ -5211,6 +5215,7 @@ getTables(Archive *fout, int *numTables)
 	int			i_reloftype;
 	int			i_relpages;
 	int			i_changed_acl;
+	int			i_partkeydef;
 
 	/* Make sure we are in proper schema */
 	selectSourceSchema(fout, "pg_catalog");
@@ -5296,7 +5301,8 @@ getTables(Archive *fout, int *numTables)
 						  "OR %s IS NOT NULL "
 						  "OR %s IS NOT NULL"
 						  "))"
-						  "AS changed_acl "
+						  "AS changed_acl, "
+						  "CASE WHEN c.relkind = 'P' THEN pg_catalog.pg_get_partkeydef(c.oid) ELSE NULL END AS partkeydef "
 						  "FROM pg_class c "
 						  "LEFT JOIN pg_depend d ON "
 						  "(c.relkind = '%c' AND "
@@ -5308,7 +5314,7 @@ getTables(Archive *fout, int *numTables)
 						  "(c.oid = pip.objoid "
 						  "AND pip.classoid = 'pg_class'::regclass "
 						  "AND pip.objsubid = 0) "
-				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c') "
+				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c', '%c') "
 						  "ORDER BY c.oid",
 						  acl_subquery->data,
 						  racl_subquery->data,
@@ -5322,7 +5328,8 @@ getTables(Archive *fout, int *numTables)
 						  RELKIND_SEQUENCE,
 						  RELKIND_RELATION, RELKIND_SEQUENCE,
 						  RELKIND_VIEW, RELKIND_COMPOSITE_TYPE,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 
 		destroyPQExpBuffer(acl_subquery);
 		destroyPQExpBuffer(racl_subquery);
@@ -5884,6 +5891,7 @@ getTables(Archive *fout, int *numTables)
 	i_toastreloptions = PQfnumber(res, "toast_reloptions");
 	i_reloftype = PQfnumber(res, "reloftype");
 	i_changed_acl = PQfnumber(res, "changed_acl");
+	i_partkeydef = PQfnumber(res, "partkeydef");
 
 	if (dopt->lockWaitTimeout && fout->remoteVersion >= 70300)
 	{
@@ -5954,6 +5962,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 */
 
@@ -5998,7 +6007,9 @@ getTables(Archive *fout, int *numTables)
 		 * We only need to lock the table for certain components; see
 		 * pg_dump.h
 		 */
-		if (tblinfo[i].dobj.dump && tblinfo[i].relkind == RELKIND_RELATION &&
+		if (tblinfo[i].dobj.dump &&
+			(tblinfo[i].relkind == RELKIND_RELATION ||
+			 tblinfo->relkind == RELKIND_PARTITIONED_TABLE) &&
 			(tblinfo[i].dobj.dump & DUMP_COMPONENTS_REQUIRING_LOCK))
 		{
 			resetPQExpBuffer(query);
@@ -6100,7 +6111,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);
 
@@ -15453,6 +15467,9 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 				appendPQExpBufferChar(q, ')');
 			}
 
+			if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+				appendPQExpBuffer(q, "\nPARTITION BY %s", tbinfo->partkeydef);
+
 			if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 				appendPQExpBuffer(q, "\nSERVER %s", fmtId(srvname));
 		}
@@ -15513,6 +15530,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 		 */
 		if (dopt->binary_upgrade &&
 			(tbinfo->relkind == RELKIND_RELATION ||
+			 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 			 tbinfo->relkind == RELKIND_FOREIGN_TABLE))
 		{
 			for (j = 0; j < tbinfo->numatts; j++)
@@ -15531,7 +15549,8 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 					appendStringLiteralAH(q, fmtId(tbinfo->dobj.name), fout);
 					appendPQExpBufferStr(q, "::pg_catalog.regclass;\n");
 
-					if (tbinfo->relkind == RELKIND_RELATION)
+					if (tbinfo->relkind == RELKIND_RELATION ||
+						tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
 						appendPQExpBuffer(q, "ALTER TABLE ONLY %s ",
 										  fmtId(tbinfo->dobj.name));
 					else
@@ -15748,6 +15767,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 	 * dump properties we only have ALTER TABLE syntax for
 	 */
 	if ((tbinfo->relkind == RELKIND_RELATION ||
+		 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 		 tbinfo->relkind == RELKIND_MATVIEW) &&
 		tbinfo->relreplident != REPLICA_IDENTITY_DEFAULT)
 	{
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 2bfa2d9..0292859 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -310,6 +310,7 @@ typedef struct _tableInfo
 	bool	   *inhNotNull;		/* true if NOT NULL is inherited */
 	struct _attrDefInfo **attrdefs;		/* DEFAULT expressions */
 	struct _constraintInfo *checkexprs; /* CHECK constraints */
+	char	   *partkeydef;		/* partition key definition */
 
 	/*
 	 * Stuff computed only for dumpable tables.
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 6275a68..db6dc5c 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -861,6 +861,7 @@ permissionsList(const char *pattern)
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'S' THEN '%s'"
@@ -870,6 +871,7 @@ permissionsList(const char *pattern)
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("sequence"),
@@ -920,7 +922,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
@@ -1567,8 +1569,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)");
 	}
 
@@ -1633,6 +1635,14 @@ describeOneTableDetails(const char *schemaname,
 			printfPQExpBuffer(&title, _("Foreign table \"%s.%s\""),
 							  schemaname, relationname);
 			break;
+		case 'P':
+			if (tableinfo.relpersistence == 'u')
+				printfPQExpBuffer(&title, _("Unlogged table \"%s.%s\""),
+								  schemaname, relationname);
+			else
+				printfPQExpBuffer(&title, _("Table \"%s.%s\""),
+								  schemaname, relationname);
+			break;
 		default:
 			/* untranslated unknown relkind */
 			printfPQExpBuffer(&title, "?%c? \"%s.%s\"",
@@ -1646,8 +1656,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");
@@ -1667,12 +1677,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");
 	}
 
@@ -1772,7 +1782,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);
@@ -1780,14 +1790,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 (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 */
@@ -1926,7 +1955,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;
@@ -2485,7 +2514,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;
@@ -2696,7 +2725,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
@@ -3024,6 +3053,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'i' THEN '%s'"
@@ -3035,6 +3065,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("index"),
@@ -3079,7 +3110,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 50a45eb..8284a9c 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/pg_proc.h b/src/include/catalog/pg_proc.h
index e2d08ba..b45688b 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -1980,6 +1980,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/utils/builtins.h b/src/include/utils/builtins.h
index 2ae212a..e800647 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -729,6 +729,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/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 1394362..260ec34 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -407,3 +407,29 @@ CREATE TABLE no_inh_con_parted (
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
 ERROR:  cannot add NO INHERIT constraint to partitioned table "no_inh_con_parted"
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+Table "public.describe_range_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition key: RANGE ((a + b))
+
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+Table "public.describe_list_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition key: LIST (a)
+
+DROP TABLE describe_range_key, describe_list_key;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index d383f58..7a1f8de 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -401,3 +401,16 @@ CREATE TABLE no_inh_con_parted (
 	a int,
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
+
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+DROP TABLE describe_range_key, describe_list_key;
-- 
1.7.1


--------------C3235EE96C69D8DAE4DB543E
Content-Type: text/x-diff;
 name="0003-Catalog-and-DDL-for-partitions-6.patch"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
 filename="0003-Catalog-and-DDL-for-partitions-6.patch"



^ permalink  raw  reply  [nested|flat] 110+ messages in thread

* [PATCH 2/9] psql and pg_dump support for partitioned tables.
@ 2016-07-12 08:20  amit <[email protected]>
  0 siblings, 0 replies; 110+ messages in thread

From: amit @ 2016-07-12 08:20 UTC (permalink / raw)

Takes care of both the partition key deparse stuff and the new relkind.
---
 src/backend/utils/adt/ruleutils.c          |  140 ++++++++++++++++++++++++++++
 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/pg_proc.h              |    2 +
 src/include/utils/builtins.h               |    1 +
 src/test/regress/expected/create_table.out |   26 +++++
 src/test/regress/sql/create_table.sql      |   13 +++
 9 files changed, 260 insertions(+), 26 deletions(-)

diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 8a81d7a..03be202 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_table.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
@@ -315,6 +316,7 @@ static char *pg_get_indexdef_worker(Oid indexrelid, int colno,
 					   const Oid *excludeOps,
 					   bool attrsOnly, bool showTblSpc,
 					   int prettyFlags, bool missing_ok);
+static char *pg_get_partkeydef_worker(Oid relid, int prettyFlags);
 static char *pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 							int prettyFlags, bool missing_ok);
 static text *pg_get_expr_worker(text *expr, Oid relid, const char *relname,
@@ -1389,6 +1391,144 @@ 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 [ opclass_name ] [, ...])
+ */
+Datum
+pg_get_partkeydef(PG_FUNCTION_ARGS)
+{
+	Oid			relid = PG_GETARG_OID(0);
+	int			prettyFlags;
+
+	prettyFlags = PRETTYFLAG_INDENT;
+	PG_RETURN_TEXT_P(string_to_text(pg_get_partkeydef_worker(relid,
+									prettyFlags)));
+}
+
+/*
+ * Internal workhorse to decompile a partition key definition.
+ */
+static char *
+pg_get_partkeydef_worker(Oid relid, int prettyFlags)
+{
+	Form_pg_partitioned_table	form;
+	HeapTuple	tuple;
+	oidvector  *partclass;
+	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_table) GETSTRUCT(tuple);
+
+	Assert(form->partrelid == relid);
+
+	/* Must get partclass, and partexprs the hard way */
+	datum = SysCacheGetAttr(PARTEDRELID, tuple,
+							Anum_pg_partitioned_table_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_table_partexprbin))
+	{
+		Datum		exprsDatum;
+		bool		isnull;
+		char	   *exprsString;
+
+		exprsDatum = SysCacheGetAttr(PARTEDRELID, tuple,
+									 Anum_pg_partitioned_table_partexprbin, &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);
+
+	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,
+											0, 0);
+
+			appendStringInfoString(&buf, 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/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index ba9c276..c805a84 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -1253,9 +1253,10 @@ expand_table_name_patterns(Archive *fout,
 						  "SELECT c.oid"
 						  "\nFROM pg_catalog.pg_class c"
 		"\n     LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace"
-					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c')\n",
+					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c')\n",
 						  RELKIND_RELATION, RELKIND_SEQUENCE, RELKIND_VIEW,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 		processSQLNamePattern(GetConnection(fout), query, cell->val, true,
 							  false, "n.nspname", "c.relname", NULL,
 							  "pg_catalog.pg_table_is_visible(c.oid)");
@@ -2125,6 +2126,9 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo, bool oids)
 	/* Skip FOREIGN TABLEs (no data to dump) */
 	if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 		return;
+	/* Skip partitioned tables (data in partitions) */
+	if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+		return;
 
 	/* Don't dump data in unlogged tables, if so requested */
 	if (tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED &&
@@ -5204,6 +5208,7 @@ getTables(Archive *fout, int *numTables)
 	int			i_reloftype;
 	int			i_relpages;
 	int			i_changed_acl;
+	int			i_partkeydef;
 
 	/* Make sure we are in proper schema */
 	selectSourceSchema(fout, "pg_catalog");
@@ -5289,7 +5294,8 @@ getTables(Archive *fout, int *numTables)
 						  "OR %s IS NOT NULL "
 						  "OR %s IS NOT NULL"
 						  "))"
-						  "AS changed_acl "
+						  "AS changed_acl, "
+						  "CASE WHEN c.relkind = 'P' THEN pg_catalog.pg_get_partkeydef(c.oid) ELSE NULL END AS partkeydef "
 						  "FROM pg_class c "
 						  "LEFT JOIN pg_depend d ON "
 						  "(c.relkind = '%c' AND "
@@ -5301,7 +5307,7 @@ getTables(Archive *fout, int *numTables)
 						  "(c.oid = pip.objoid "
 						  "AND pip.classoid = 'pg_class'::regclass "
 						  "AND pip.objsubid = 0) "
-				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c') "
+				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c', '%c') "
 						  "ORDER BY c.oid",
 						  acl_subquery->data,
 						  racl_subquery->data,
@@ -5315,7 +5321,8 @@ getTables(Archive *fout, int *numTables)
 						  RELKIND_SEQUENCE,
 						  RELKIND_RELATION, RELKIND_SEQUENCE,
 						  RELKIND_VIEW, RELKIND_COMPOSITE_TYPE,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 
 		destroyPQExpBuffer(acl_subquery);
 		destroyPQExpBuffer(racl_subquery);
@@ -5877,6 +5884,7 @@ getTables(Archive *fout, int *numTables)
 	i_toastreloptions = PQfnumber(res, "toast_reloptions");
 	i_reloftype = PQfnumber(res, "reloftype");
 	i_changed_acl = PQfnumber(res, "changed_acl");
+	i_partkeydef = PQfnumber(res, "partkeydef");
 
 	if (dopt->lockWaitTimeout && fout->remoteVersion >= 70300)
 	{
@@ -5947,6 +5955,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 */
 
@@ -5991,7 +6000,9 @@ getTables(Archive *fout, int *numTables)
 		 * We only need to lock the table for certain components; see
 		 * pg_dump.h
 		 */
-		if (tblinfo[i].dobj.dump && tblinfo[i].relkind == RELKIND_RELATION &&
+		if (tblinfo[i].dobj.dump &&
+			(tblinfo[i].relkind == RELKIND_RELATION ||
+			 tblinfo->relkind == RELKIND_PARTITIONED_TABLE) &&
 			(tblinfo[i].dobj.dump & DUMP_COMPONENTS_REQUIRING_LOCK))
 		{
 			resetPQExpBuffer(query);
@@ -6093,7 +6104,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);
 
@@ -15446,6 +15460,9 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 				appendPQExpBufferChar(q, ')');
 			}
 
+			if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+				appendPQExpBuffer(q, "\nPARTITION BY %s", tbinfo->partkeydef);
+
 			if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 				appendPQExpBuffer(q, "\nSERVER %s", fmtId(srvname));
 		}
@@ -15506,6 +15523,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 		 */
 		if (dopt->binary_upgrade &&
 			(tbinfo->relkind == RELKIND_RELATION ||
+			 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 			 tbinfo->relkind == RELKIND_FOREIGN_TABLE))
 		{
 			for (j = 0; j < tbinfo->numatts; j++)
@@ -15524,7 +15542,8 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 					appendStringLiteralAH(q, fmtId(tbinfo->dobj.name), fout);
 					appendPQExpBufferStr(q, "::pg_catalog.regclass;\n");
 
-					if (tbinfo->relkind == RELKIND_RELATION)
+					if (tbinfo->relkind == RELKIND_RELATION ||
+						tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
 						appendPQExpBuffer(q, "ALTER TABLE ONLY %s ",
 										  fmtId(tbinfo->dobj.name));
 					else
@@ -15741,6 +15760,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 	 * dump properties we only have ALTER TABLE syntax for
 	 */
 	if ((tbinfo->relkind == RELKIND_RELATION ||
+		 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 		 tbinfo->relkind == RELKIND_MATVIEW) &&
 		tbinfo->relreplident != REPLICA_IDENTITY_DEFAULT)
 	{
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 2bfa2d9..0292859 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -310,6 +310,7 @@ typedef struct _tableInfo
 	bool	   *inhNotNull;		/* true if NOT NULL is inherited */
 	struct _attrDefInfo **attrdefs;		/* DEFAULT expressions */
 	struct _constraintInfo *checkexprs; /* CHECK constraints */
+	char	   *partkeydef;		/* partition key definition */
 
 	/*
 	 * Stuff computed only for dumpable tables.
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 6275a68..10d924a 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -861,6 +861,7 @@ permissionsList(const char *pattern)
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'S' THEN '%s'"
@@ -870,6 +871,7 @@ permissionsList(const char *pattern)
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("sequence"),
@@ -920,7 +922,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
@@ -1567,8 +1569,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)");
 	}
 
@@ -1633,6 +1635,14 @@ describeOneTableDetails(const char *schemaname,
 			printfPQExpBuffer(&title, _("Foreign table \"%s.%s\""),
 							  schemaname, relationname);
 			break;
+		case 'P':
+			if (tableinfo.relpersistence == 'u')
+				printfPQExpBuffer(&title, _("Unlogged table \"%s.%s\""),
+								  schemaname, relationname);
+			else
+				printfPQExpBuffer(&title, _("Table \"%s.%s\""),
+								  schemaname, relationname);
+			break;
 		default:
 			/* untranslated unknown relkind */
 			printfPQExpBuffer(&title, "?%c? \"%s.%s\"",
@@ -1646,8 +1656,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");
@@ -1667,12 +1677,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");
 	}
 
@@ -1772,7 +1782,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);
@@ -1780,14 +1790,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 (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 */
@@ -1926,7 +1955,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;
@@ -2485,7 +2514,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;
@@ -2696,7 +2725,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
@@ -3024,6 +3053,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'i' THEN '%s'"
@@ -3035,6 +3065,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("index"),
@@ -3079,7 +3110,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 50a45eb..8284a9c 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/pg_proc.h b/src/include/catalog/pg_proc.h
index e2d08ba..b45688b 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -1980,6 +1980,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/utils/builtins.h b/src/include/utils/builtins.h
index 2ae212a..e800647 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -729,6 +729,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/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 0af1422..91ba1be 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -412,3 +412,29 @@ CREATE TABLE no_inh_con_parted (
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
 ERROR:  cannot add NO INHERIT constraint to partitioned table "no_inh_con_parted"
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+Table "public.describe_range_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition Key: RANGE ((a + b))
+
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+Table "public.describe_list_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition Key: LIST (a)
+
+DROP TABLE describe_range_key, describe_list_key;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 418ac55..b75542d 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -406,3 +406,16 @@ CREATE TABLE no_inh_con_parted (
 	a int,
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
+
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+DROP TABLE describe_range_key, describe_list_key;
-- 
1.7.1


--------------D008BA677674C4836D7B65AD
Content-Type: text/x-diff;
 name="0003-Catalog-and-DDL-for-partitions-5.patch"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
 filename="0003-Catalog-and-DDL-for-partitions-5.patch"



^ permalink  raw  reply  [nested|flat] 110+ messages in thread

* [PATCH 2/9] psql and pg_dump support for partitioned tables.
@ 2016-07-12 08:20  amit <[email protected]>
  0 siblings, 0 replies; 110+ messages in thread

From: amit @ 2016-07-12 08:20 UTC (permalink / raw)

Takes care of both the partition key deparse stuff and the new relkind.
---
 src/backend/utils/adt/ruleutils.c          |  140 ++++++++++++++++++++++++++++
 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/pg_proc.h              |    2 +
 src/include/utils/builtins.h               |    1 +
 src/test/regress/expected/create_table.out |   26 +++++
 src/test/regress/sql/create_table.sql      |   13 +++
 9 files changed, 260 insertions(+), 26 deletions(-)

diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 8a81d7a..03be202 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_table.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
@@ -315,6 +316,7 @@ static char *pg_get_indexdef_worker(Oid indexrelid, int colno,
 					   const Oid *excludeOps,
 					   bool attrsOnly, bool showTblSpc,
 					   int prettyFlags, bool missing_ok);
+static char *pg_get_partkeydef_worker(Oid relid, int prettyFlags);
 static char *pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 							int prettyFlags, bool missing_ok);
 static text *pg_get_expr_worker(text *expr, Oid relid, const char *relname,
@@ -1389,6 +1391,144 @@ 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 [ opclass_name ] [, ...])
+ */
+Datum
+pg_get_partkeydef(PG_FUNCTION_ARGS)
+{
+	Oid			relid = PG_GETARG_OID(0);
+	int			prettyFlags;
+
+	prettyFlags = PRETTYFLAG_INDENT;
+	PG_RETURN_TEXT_P(string_to_text(pg_get_partkeydef_worker(relid,
+									prettyFlags)));
+}
+
+/*
+ * Internal workhorse to decompile a partition key definition.
+ */
+static char *
+pg_get_partkeydef_worker(Oid relid, int prettyFlags)
+{
+	Form_pg_partitioned_table	form;
+	HeapTuple	tuple;
+	oidvector  *partclass;
+	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_table) GETSTRUCT(tuple);
+
+	Assert(form->partrelid == relid);
+
+	/* Must get partclass, and partexprs the hard way */
+	datum = SysCacheGetAttr(PARTEDRELID, tuple,
+							Anum_pg_partitioned_table_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_table_partexprbin))
+	{
+		Datum		exprsDatum;
+		bool		isnull;
+		char	   *exprsString;
+
+		exprsDatum = SysCacheGetAttr(PARTEDRELID, tuple,
+									 Anum_pg_partitioned_table_partexprbin, &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);
+
+	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,
+											0, 0);
+
+			appendStringInfoString(&buf, 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/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 51b8a1a..854dddc 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -1253,9 +1253,10 @@ expand_table_name_patterns(Archive *fout,
 						  "SELECT c.oid"
 						  "\nFROM pg_catalog.pg_class c"
 		"\n     LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace"
-					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c')\n",
+					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c')\n",
 						  RELKIND_RELATION, RELKIND_SEQUENCE, RELKIND_VIEW,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 		processSQLNamePattern(GetConnection(fout), query, cell->val, true,
 							  false, "n.nspname", "c.relname", NULL,
 							  "pg_catalog.pg_table_is_visible(c.oid)");
@@ -2125,6 +2126,9 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo, bool oids)
 	/* Skip FOREIGN TABLEs (no data to dump) */
 	if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 		return;
+	/* Skip partitioned tables (data in partitions) */
+	if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+		return;
 
 	/* Don't dump data in unlogged tables, if so requested */
 	if (tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED &&
@@ -5211,6 +5215,7 @@ getTables(Archive *fout, int *numTables)
 	int			i_reloftype;
 	int			i_relpages;
 	int			i_changed_acl;
+	int			i_partkeydef;
 
 	/* Make sure we are in proper schema */
 	selectSourceSchema(fout, "pg_catalog");
@@ -5296,7 +5301,8 @@ getTables(Archive *fout, int *numTables)
 						  "OR %s IS NOT NULL "
 						  "OR %s IS NOT NULL"
 						  "))"
-						  "AS changed_acl "
+						  "AS changed_acl, "
+						  "CASE WHEN c.relkind = 'P' THEN pg_catalog.pg_get_partkeydef(c.oid) ELSE NULL END AS partkeydef "
 						  "FROM pg_class c "
 						  "LEFT JOIN pg_depend d ON "
 						  "(c.relkind = '%c' AND "
@@ -5308,7 +5314,7 @@ getTables(Archive *fout, int *numTables)
 						  "(c.oid = pip.objoid "
 						  "AND pip.classoid = 'pg_class'::regclass "
 						  "AND pip.objsubid = 0) "
-				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c') "
+				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c', '%c') "
 						  "ORDER BY c.oid",
 						  acl_subquery->data,
 						  racl_subquery->data,
@@ -5322,7 +5328,8 @@ getTables(Archive *fout, int *numTables)
 						  RELKIND_SEQUENCE,
 						  RELKIND_RELATION, RELKIND_SEQUENCE,
 						  RELKIND_VIEW, RELKIND_COMPOSITE_TYPE,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 
 		destroyPQExpBuffer(acl_subquery);
 		destroyPQExpBuffer(racl_subquery);
@@ -5884,6 +5891,7 @@ getTables(Archive *fout, int *numTables)
 	i_toastreloptions = PQfnumber(res, "toast_reloptions");
 	i_reloftype = PQfnumber(res, "reloftype");
 	i_changed_acl = PQfnumber(res, "changed_acl");
+	i_partkeydef = PQfnumber(res, "partkeydef");
 
 	if (dopt->lockWaitTimeout && fout->remoteVersion >= 70300)
 	{
@@ -5954,6 +5962,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 */
 
@@ -5998,7 +6007,9 @@ getTables(Archive *fout, int *numTables)
 		 * We only need to lock the table for certain components; see
 		 * pg_dump.h
 		 */
-		if (tblinfo[i].dobj.dump && tblinfo[i].relkind == RELKIND_RELATION &&
+		if (tblinfo[i].dobj.dump &&
+			(tblinfo[i].relkind == RELKIND_RELATION ||
+			 tblinfo->relkind == RELKIND_PARTITIONED_TABLE) &&
 			(tblinfo[i].dobj.dump & DUMP_COMPONENTS_REQUIRING_LOCK))
 		{
 			resetPQExpBuffer(query);
@@ -6100,7 +6111,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);
 
@@ -15453,6 +15467,9 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 				appendPQExpBufferChar(q, ')');
 			}
 
+			if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+				appendPQExpBuffer(q, "\nPARTITION BY %s", tbinfo->partkeydef);
+
 			if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 				appendPQExpBuffer(q, "\nSERVER %s", fmtId(srvname));
 		}
@@ -15513,6 +15530,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 		 */
 		if (dopt->binary_upgrade &&
 			(tbinfo->relkind == RELKIND_RELATION ||
+			 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 			 tbinfo->relkind == RELKIND_FOREIGN_TABLE))
 		{
 			for (j = 0; j < tbinfo->numatts; j++)
@@ -15531,7 +15549,8 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 					appendStringLiteralAH(q, fmtId(tbinfo->dobj.name), fout);
 					appendPQExpBufferStr(q, "::pg_catalog.regclass;\n");
 
-					if (tbinfo->relkind == RELKIND_RELATION)
+					if (tbinfo->relkind == RELKIND_RELATION ||
+						tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
 						appendPQExpBuffer(q, "ALTER TABLE ONLY %s ",
 										  fmtId(tbinfo->dobj.name));
 					else
@@ -15748,6 +15767,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 	 * dump properties we only have ALTER TABLE syntax for
 	 */
 	if ((tbinfo->relkind == RELKIND_RELATION ||
+		 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 		 tbinfo->relkind == RELKIND_MATVIEW) &&
 		tbinfo->relreplident != REPLICA_IDENTITY_DEFAULT)
 	{
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 2bfa2d9..0292859 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -310,6 +310,7 @@ typedef struct _tableInfo
 	bool	   *inhNotNull;		/* true if NOT NULL is inherited */
 	struct _attrDefInfo **attrdefs;		/* DEFAULT expressions */
 	struct _constraintInfo *checkexprs; /* CHECK constraints */
+	char	   *partkeydef;		/* partition key definition */
 
 	/*
 	 * Stuff computed only for dumpable tables.
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 6275a68..db6dc5c 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -861,6 +861,7 @@ permissionsList(const char *pattern)
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'S' THEN '%s'"
@@ -870,6 +871,7 @@ permissionsList(const char *pattern)
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("sequence"),
@@ -920,7 +922,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
@@ -1567,8 +1569,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)");
 	}
 
@@ -1633,6 +1635,14 @@ describeOneTableDetails(const char *schemaname,
 			printfPQExpBuffer(&title, _("Foreign table \"%s.%s\""),
 							  schemaname, relationname);
 			break;
+		case 'P':
+			if (tableinfo.relpersistence == 'u')
+				printfPQExpBuffer(&title, _("Unlogged table \"%s.%s\""),
+								  schemaname, relationname);
+			else
+				printfPQExpBuffer(&title, _("Table \"%s.%s\""),
+								  schemaname, relationname);
+			break;
 		default:
 			/* untranslated unknown relkind */
 			printfPQExpBuffer(&title, "?%c? \"%s.%s\"",
@@ -1646,8 +1656,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");
@@ -1667,12 +1677,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");
 	}
 
@@ -1772,7 +1782,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);
@@ -1780,14 +1790,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 (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 */
@@ -1926,7 +1955,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;
@@ -2485,7 +2514,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;
@@ -2696,7 +2725,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
@@ -3024,6 +3053,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'i' THEN '%s'"
@@ -3035,6 +3065,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("index"),
@@ -3079,7 +3110,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 50a45eb..8284a9c 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/pg_proc.h b/src/include/catalog/pg_proc.h
index e2d08ba..b45688b 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -1980,6 +1980,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/utils/builtins.h b/src/include/utils/builtins.h
index 2ae212a..e800647 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -729,6 +729,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/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 1394362..260ec34 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -407,3 +407,29 @@ CREATE TABLE no_inh_con_parted (
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
 ERROR:  cannot add NO INHERIT constraint to partitioned table "no_inh_con_parted"
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+Table "public.describe_range_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition key: RANGE ((a + b))
+
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+Table "public.describe_list_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition key: LIST (a)
+
+DROP TABLE describe_range_key, describe_list_key;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index d383f58..7a1f8de 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -401,3 +401,16 @@ CREATE TABLE no_inh_con_parted (
 	a int,
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
+
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+DROP TABLE describe_range_key, describe_list_key;
-- 
1.7.1


--------------C3235EE96C69D8DAE4DB543E
Content-Type: text/x-diff;
 name="0003-Catalog-and-DDL-for-partitions-6.patch"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
 filename="0003-Catalog-and-DDL-for-partitions-6.patch"



^ permalink  raw  reply  [nested|flat] 110+ messages in thread

* [PATCH 2/8] psql and pg_dump support for partitioned tables.
@ 2016-07-12 08:20  amit <[email protected]>
  0 siblings, 0 replies; 110+ messages in thread

From: amit @ 2016-07-12 08:20 UTC (permalink / raw)

Takes care of both the partition key deparse stuff and the new relkind.
---
 src/backend/utils/adt/ruleutils.c          |  147 ++++++++++++++++++++++++++++
 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/pg_proc.h              |    2 +
 src/include/utils/builtins.h               |    1 +
 src/test/regress/expected/create_table.out |   26 +++++
 src/test/regress/sql/create_table.sql      |   13 +++
 9 files changed, 267 insertions(+), 26 deletions(-)

diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index ec966c7..4691b5b 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, bool missing_ok);
+static char *pg_get_partkeydef_worker(Oid relId, int prettyFlags);
 static char *pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 							int prettyFlags, bool missing_ok);
 static text *pg_get_expr_worker(text *expr, Oid relid, const char *relname,
@@ -1389,6 +1391,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 [ 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/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 4ee10fc..a127672 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -1253,9 +1253,10 @@ expand_table_name_patterns(Archive *fout,
 						  "SELECT c.oid"
 						  "\nFROM pg_catalog.pg_class c"
 		"\n     LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace"
-					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c')\n",
+					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c')\n",
 						  RELKIND_RELATION, RELKIND_SEQUENCE, RELKIND_VIEW,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_REL);
 		processSQLNamePattern(GetConnection(fout), query, cell->val, true,
 							  false, "n.nspname", "c.relname", NULL,
 							  "pg_catalog.pg_table_is_visible(c.oid)");
@@ -2117,6 +2118,9 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo, bool oids)
 	/* Skip FOREIGN TABLEs (no data to dump) */
 	if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 		return;
+	/* Skip partitioned tables (data in partitions) */
+	if (tbinfo->relkind == RELKIND_PARTITIONED_REL)
+		return;
 
 	/* Don't dump data in unlogged tables, if so requested */
 	if (tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED &&
@@ -5195,6 +5199,7 @@ getTables(Archive *fout, int *numTables)
 	int			i_reloftype;
 	int			i_relpages;
 	int			i_changed_acl;
+	int			i_partkeydef;
 
 	/* Make sure we are in proper schema */
 	selectSourceSchema(fout, "pg_catalog");
@@ -5280,7 +5285,8 @@ getTables(Archive *fout, int *numTables)
 						  "OR %s IS NOT NULL "
 						  "OR %s IS NOT NULL"
 						  "))"
-						  "AS changed_acl "
+						  "AS changed_acl, "
+						  "CASE WHEN c.relkind = 'P' THEN pg_catalog.pg_get_partkeydef(c.oid) ELSE NULL END AS partkeydef "
 						  "FROM pg_class c "
 						  "LEFT JOIN pg_depend d ON "
 						  "(c.relkind = '%c' AND "
@@ -5292,7 +5298,7 @@ getTables(Archive *fout, int *numTables)
 						  "(c.oid = pip.objoid "
 						  "AND pip.classoid = 'pg_class'::regclass "
 						  "AND pip.objsubid = 0) "
-				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c') "
+				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c', '%c') "
 						  "ORDER BY c.oid",
 						  acl_subquery->data,
 						  racl_subquery->data,
@@ -5306,7 +5312,8 @@ getTables(Archive *fout, int *numTables)
 						  RELKIND_SEQUENCE,
 						  RELKIND_RELATION, RELKIND_SEQUENCE,
 						  RELKIND_VIEW, RELKIND_COMPOSITE_TYPE,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_REL);
 
 		destroyPQExpBuffer(acl_subquery);
 		destroyPQExpBuffer(racl_subquery);
@@ -5868,6 +5875,7 @@ getTables(Archive *fout, int *numTables)
 	i_toastreloptions = PQfnumber(res, "toast_reloptions");
 	i_reloftype = PQfnumber(res, "reloftype");
 	i_changed_acl = PQfnumber(res, "changed_acl");
+	i_partkeydef = PQfnumber(res, "partkeydef");
 
 	if (dopt->lockWaitTimeout && fout->remoteVersion >= 70300)
 	{
@@ -5938,6 +5946,7 @@ getTables(Archive *fout, int *numTables)
 		else
 			tblinfo[i].checkoption = pg_strdup(PQgetvalue(res, i, i_checkoption));
 		tblinfo[i].toast_reloptions = pg_strdup(PQgetvalue(res, i, i_toastreloptions));
+		tblinfo[i].partkeydef = pg_strdup(PQgetvalue(res, i, i_partkeydef));
 
 		/* other fields were zeroed above */
 
@@ -5982,7 +5991,9 @@ getTables(Archive *fout, int *numTables)
 		 * We only need to lock the table for certain components; see
 		 * pg_dump.h
 		 */
-		if (tblinfo[i].dobj.dump && tblinfo[i].relkind == RELKIND_RELATION &&
+		if (tblinfo[i].dobj.dump &&
+			(tblinfo[i].relkind == RELKIND_RELATION ||
+			 tblinfo->relkind == RELKIND_PARTITIONED_REL) &&
 			(tblinfo[i].dobj.dump & DUMP_COMPONENTS_REQUIRING_LOCK))
 		{
 			resetPQExpBuffer(query);
@@ -6083,7 +6094,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);
 
@@ -15436,6 +15450,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));
 		}
@@ -15496,6 +15513,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++)
@@ -15514,7 +15532,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
@@ -15731,6 +15750,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 	 * dump properties we only have ALTER TABLE syntax for
 	 */
 	if ((tbinfo->relkind == RELKIND_RELATION ||
+		 tbinfo->relkind == RELKIND_PARTITIONED_REL ||
 		 tbinfo->relkind == RELKIND_MATVIEW) &&
 		tbinfo->relreplident != REPLICA_IDENTITY_DEFAULT)
 	{
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 2bfa2d9..0292859 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -310,6 +310,7 @@ typedef struct _tableInfo
 	bool	   *inhNotNull;		/* true if NOT NULL is inherited */
 	struct _attrDefInfo **attrdefs;		/* DEFAULT expressions */
 	struct _constraintInfo *checkexprs; /* CHECK constraints */
+	char	   *partkeydef;		/* partition key definition */
 
 	/*
 	 * Stuff computed only for dumpable tables.
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 27be102..3ba8a90 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -856,6 +856,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"),
@@ -865,6 +866,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");
@@ -911,7 +913,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
@@ -1555,8 +1557,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)");
 	}
 
@@ -1621,6 +1623,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\"",
@@ -1634,8 +1644,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");
@@ -1655,12 +1665,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");
 	}
 
@@ -1760,7 +1770,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);
@@ -1768,14 +1778,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 */
@@ -1914,7 +1943,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;
@@ -2473,7 +2502,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;
@@ -2684,7 +2713,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
@@ -3018,6 +3047,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"),
@@ -3029,6 +3059,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"));
 
@@ -3067,7 +3098,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 
 	appendPQExpBufferStr(&buf, "\nWHERE c.relkind IN (");
 	if (showTables)
-		appendPQExpBufferStr(&buf, "'r',");
+		appendPQExpBufferStr(&buf, "'r', 'P',");
 	if (showViews)
 		appendPQExpBufferStr(&buf, "'v',");
 	if (showMatViews)
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 8469d9f..cd4eb7c 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -427,7 +427,7 @@ static const SchemaQuery Query_for_list_of_tables = {
 	/* catname */
 	"pg_catalog.pg_class c",
 	/* selcondition */
-	"c.relkind IN ('r')",
+	"c.relkind IN ('r', 'P')",
 	/* viscondition */
 	"pg_catalog.pg_table_is_visible(c.oid)",
 	/* namespace */
@@ -458,7 +458,7 @@ static const SchemaQuery Query_for_list_of_updatables = {
 	/* catname */
 	"pg_catalog.pg_class c",
 	/* selcondition */
-	"c.relkind IN ('r', 'f', 'v')",
+	"c.relkind IN ('r', 'f', 'v', 'P')",
 	/* viscondition */
 	"pg_catalog.pg_table_is_visible(c.oid)",
 	/* namespace */
@@ -488,7 +488,7 @@ static const SchemaQuery Query_for_list_of_tsvmf = {
 	/* catname */
 	"pg_catalog.pg_class c",
 	/* selcondition */
-	"c.relkind IN ('r', 'S', 'v', 'm', 'f')",
+	"c.relkind IN ('r', 'S', 'v', 'm', 'f', 'P')",
 	/* viscondition */
 	"pg_catalog.pg_table_is_visible(c.oid)",
 	/* namespace */
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index 270dd21..5a444d9 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/utils/builtins.h b/src/include/utils/builtins.h
index 8cebc86..351bfd3 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -722,6 +722,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/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index fb7a1b3..4fc8c06 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -413,3 +413,29 @@ SELECT connoinherit FROM pg_constraint WHERE conrelid = 'no_inh_con_parted'::reg
 (1 row)
 
 DROP TABLE no_inh_con_parted;
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+Partitioned table "public.describe_range_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition Key: PARTITION BY RANGE (((a + b)))
+
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+Partitioned table "public.describe_list_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition Key: PARTITION BY LIST (a)
+
+DROP TABLE describe_range_key, describe_list_key;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index cb17517..7c7a18a 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -404,3 +404,16 @@ CREATE TABLE no_inh_con_parted (
 ) PARTITION BY RANGE (a);
 SELECT connoinherit FROM pg_constraint WHERE conrelid = 'no_inh_con_parted'::regclass;
 DROP TABLE no_inh_con_parted;
+
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+DROP TABLE describe_range_key, describe_list_key;
-- 
1.7.1


--------------8C4C48E640631A327D3D2826
Content-Type: text/x-diff;
 name="0003-Catalog-and-DDL-for-partitions.patch"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
 filename="0003-Catalog-and-DDL-for-partitions.patch"



^ permalink  raw  reply  [nested|flat] 110+ messages in thread

* [PATCH 2/9] psql and pg_dump support for partitioned tables.
@ 2016-07-12 08:20  amit <[email protected]>
  0 siblings, 0 replies; 110+ messages in thread

From: amit @ 2016-07-12 08:20 UTC (permalink / raw)

Takes care of both the partition key deparse stuff and the new relkind.
---
 src/backend/utils/adt/ruleutils.c          |  140 ++++++++++++++++++++++++++++
 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/pg_proc.h              |    2 +
 src/include/utils/builtins.h               |    1 +
 src/test/regress/expected/create_table.out |   26 +++++
 src/test/regress/sql/create_table.sql      |   13 +++
 9 files changed, 260 insertions(+), 26 deletions(-)

diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 8a81d7a..77ce807 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_table.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
@@ -315,6 +316,7 @@ static char *pg_get_indexdef_worker(Oid indexrelid, int colno,
 					   const Oid *excludeOps,
 					   bool attrsOnly, bool showTblSpc,
 					   int prettyFlags, bool missing_ok);
+static char *pg_get_partkeydef_worker(Oid relId, int prettyFlags);
 static char *pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 							int prettyFlags, bool missing_ok);
 static text *pg_get_expr_worker(text *expr, Oid relid, const char *relname,
@@ -1389,6 +1391,144 @@ 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 [ 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_table	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_table) GETSTRUCT(tuple);
+
+	Assert(form->partedrelid == relId);
+
+	/* Must get partclass, and partexprs the hard way */
+	datum = SysCacheGetAttr(PARTEDRELID, tuple,
+							Anum_pg_partitioned_table_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_table_partexprbin))
+	{
+		Datum		exprsDatum;
+		bool		isnull;
+		char	   *exprsString;
+
+		exprsDatum = SysCacheGetAttr(PARTEDRELID, tuple,
+									 Anum_pg_partitioned_table_partexprbin, &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);
+
+	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,
+											0, 0);
+
+			appendStringInfoString(&buf, 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/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index a5c2d09..c418ba7 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -1253,9 +1253,10 @@ expand_table_name_patterns(Archive *fout,
 						  "SELECT c.oid"
 						  "\nFROM pg_catalog.pg_class c"
 		"\n     LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace"
-					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c')\n",
+					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c')\n",
 						  RELKIND_RELATION, RELKIND_SEQUENCE, RELKIND_VIEW,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 		processSQLNamePattern(GetConnection(fout), query, cell->val, true,
 							  false, "n.nspname", "c.relname", NULL,
 							  "pg_catalog.pg_table_is_visible(c.oid)");
@@ -2117,6 +2118,9 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo, bool oids)
 	/* Skip FOREIGN TABLEs (no data to dump) */
 	if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 		return;
+	/* Skip partitioned tables (data in partitions) */
+	if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+		return;
 
 	/* Don't dump data in unlogged tables, if so requested */
 	if (tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED &&
@@ -5195,6 +5199,7 @@ getTables(Archive *fout, int *numTables)
 	int			i_reloftype;
 	int			i_relpages;
 	int			i_changed_acl;
+	int			i_partkeydef;
 
 	/* Make sure we are in proper schema */
 	selectSourceSchema(fout, "pg_catalog");
@@ -5280,7 +5285,8 @@ getTables(Archive *fout, int *numTables)
 						  "OR %s IS NOT NULL "
 						  "OR %s IS NOT NULL"
 						  "))"
-						  "AS changed_acl "
+						  "AS changed_acl, "
+						  "CASE WHEN c.relkind = 'P' THEN pg_catalog.pg_get_partkeydef(c.oid) ELSE NULL END AS partkeydef "
 						  "FROM pg_class c "
 						  "LEFT JOIN pg_depend d ON "
 						  "(c.relkind = '%c' AND "
@@ -5292,7 +5298,7 @@ getTables(Archive *fout, int *numTables)
 						  "(c.oid = pip.objoid "
 						  "AND pip.classoid = 'pg_class'::regclass "
 						  "AND pip.objsubid = 0) "
-				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c') "
+				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c', '%c') "
 						  "ORDER BY c.oid",
 						  acl_subquery->data,
 						  racl_subquery->data,
@@ -5306,7 +5312,8 @@ getTables(Archive *fout, int *numTables)
 						  RELKIND_SEQUENCE,
 						  RELKIND_RELATION, RELKIND_SEQUENCE,
 						  RELKIND_VIEW, RELKIND_COMPOSITE_TYPE,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 
 		destroyPQExpBuffer(acl_subquery);
 		destroyPQExpBuffer(racl_subquery);
@@ -5868,6 +5875,7 @@ getTables(Archive *fout, int *numTables)
 	i_toastreloptions = PQfnumber(res, "toast_reloptions");
 	i_reloftype = PQfnumber(res, "reloftype");
 	i_changed_acl = PQfnumber(res, "changed_acl");
+	i_partkeydef = PQfnumber(res, "partkeydef");
 
 	if (dopt->lockWaitTimeout && fout->remoteVersion >= 70300)
 	{
@@ -5938,6 +5946,7 @@ getTables(Archive *fout, int *numTables)
 		else
 			tblinfo[i].checkoption = pg_strdup(PQgetvalue(res, i, i_checkoption));
 		tblinfo[i].toast_reloptions = pg_strdup(PQgetvalue(res, i, i_toastreloptions));
+		tblinfo[i].partkeydef = pg_strdup(PQgetvalue(res, i, i_partkeydef));
 
 		/* other fields were zeroed above */
 
@@ -5982,7 +5991,9 @@ getTables(Archive *fout, int *numTables)
 		 * We only need to lock the table for certain components; see
 		 * pg_dump.h
 		 */
-		if (tblinfo[i].dobj.dump && tblinfo[i].relkind == RELKIND_RELATION &&
+		if (tblinfo[i].dobj.dump &&
+			(tblinfo[i].relkind == RELKIND_RELATION ||
+			 tblinfo->relkind == RELKIND_PARTITIONED_TABLE) &&
 			(tblinfo[i].dobj.dump & DUMP_COMPONENTS_REQUIRING_LOCK))
 		{
 			resetPQExpBuffer(query);
@@ -6084,7 +6095,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);
 
@@ -15437,6 +15451,9 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 				appendPQExpBufferChar(q, ')');
 			}
 
+			if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+				appendPQExpBuffer(q, "\nPARTITION BY %s", tbinfo->partkeydef);
+
 			if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 				appendPQExpBuffer(q, "\nSERVER %s", fmtId(srvname));
 		}
@@ -15497,6 +15514,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 		 */
 		if (dopt->binary_upgrade &&
 			(tbinfo->relkind == RELKIND_RELATION ||
+			 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 			 tbinfo->relkind == RELKIND_FOREIGN_TABLE))
 		{
 			for (j = 0; j < tbinfo->numatts; j++)
@@ -15515,7 +15533,8 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 					appendStringLiteralAH(q, fmtId(tbinfo->dobj.name), fout);
 					appendPQExpBufferStr(q, "::pg_catalog.regclass;\n");
 
-					if (tbinfo->relkind == RELKIND_RELATION)
+					if (tbinfo->relkind == RELKIND_RELATION ||
+						tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
 						appendPQExpBuffer(q, "ALTER TABLE ONLY %s ",
 										  fmtId(tbinfo->dobj.name));
 					else
@@ -15732,6 +15751,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 	 * dump properties we only have ALTER TABLE syntax for
 	 */
 	if ((tbinfo->relkind == RELKIND_RELATION ||
+		 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 		 tbinfo->relkind == RELKIND_MATVIEW) &&
 		tbinfo->relreplident != REPLICA_IDENTITY_DEFAULT)
 	{
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 2bfa2d9..0292859 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -310,6 +310,7 @@ typedef struct _tableInfo
 	bool	   *inhNotNull;		/* true if NOT NULL is inherited */
 	struct _attrDefInfo **attrdefs;		/* DEFAULT expressions */
 	struct _constraintInfo *checkexprs; /* CHECK constraints */
+	char	   *partkeydef;		/* partition key definition */
 
 	/*
 	 * Stuff computed only for dumpable tables.
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 6275a68..10d924a 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -861,6 +861,7 @@ permissionsList(const char *pattern)
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'S' THEN '%s'"
@@ -870,6 +871,7 @@ permissionsList(const char *pattern)
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("sequence"),
@@ -920,7 +922,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
@@ -1567,8 +1569,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)");
 	}
 
@@ -1633,6 +1635,14 @@ describeOneTableDetails(const char *schemaname,
 			printfPQExpBuffer(&title, _("Foreign table \"%s.%s\""),
 							  schemaname, relationname);
 			break;
+		case 'P':
+			if (tableinfo.relpersistence == 'u')
+				printfPQExpBuffer(&title, _("Unlogged table \"%s.%s\""),
+								  schemaname, relationname);
+			else
+				printfPQExpBuffer(&title, _("Table \"%s.%s\""),
+								  schemaname, relationname);
+			break;
 		default:
 			/* untranslated unknown relkind */
 			printfPQExpBuffer(&title, "?%c? \"%s.%s\"",
@@ -1646,8 +1656,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");
@@ -1667,12 +1677,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");
 	}
 
@@ -1772,7 +1782,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);
@@ -1780,14 +1790,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 (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 */
@@ -1926,7 +1955,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;
@@ -2485,7 +2514,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;
@@ -2696,7 +2725,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
@@ -3024,6 +3053,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'i' THEN '%s'"
@@ -3035,6 +3065,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("index"),
@@ -3079,7 +3110,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 1345e4e..4686cd1 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/pg_proc.h b/src/include/catalog/pg_proc.h
index e2d08ba..b45688b 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -1980,6 +1980,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/utils/builtins.h b/src/include/utils/builtins.h
index 2ae212a..e800647 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -729,6 +729,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/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 708232d..2fec847 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -408,3 +408,29 @@ CREATE TABLE no_inh_con_parted (
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
 ERROR:  cannot add NO INHERIT constraint to partitioned table "no_inh_con_parted"
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+Table "public.describe_range_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition Key: RANGE ((a + b))
+
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+Table "public.describe_list_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition Key: LIST (a)
+
+DROP TABLE describe_range_key, describe_list_key;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 6e4a8be..4dd6a0a 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -402,3 +402,16 @@ CREATE TABLE no_inh_con_parted (
 	a int,
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
+
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+DROP TABLE describe_range_key, describe_list_key;
-- 
1.7.1


--------------954458C17BBEB1BF6FC758E7
Content-Type: text/x-diff;
 name="0003-Catalog-and-DDL-for-partitions-2.patch"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
 filename="0003-Catalog-and-DDL-for-partitions-2.patch"



^ permalink  raw  reply  [nested|flat] 110+ messages in thread

* [PATCH 2/9] psql and pg_dump support for partitioned tables.
@ 2016-07-12 08:20  amit <[email protected]>
  0 siblings, 0 replies; 110+ messages in thread

From: amit @ 2016-07-12 08:20 UTC (permalink / raw)

Takes care of both the partition key deparse stuff and the new relkind.
---
 src/backend/utils/adt/ruleutils.c          |  140 ++++++++++++++++++++++++++++
 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/pg_proc.h              |    2 +
 src/include/utils/builtins.h               |    1 +
 src/test/regress/expected/create_table.out |   26 +++++
 src/test/regress/sql/create_table.sql      |   13 +++
 9 files changed, 260 insertions(+), 26 deletions(-)

diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 8a81d7a..03be202 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_table.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
@@ -315,6 +316,7 @@ static char *pg_get_indexdef_worker(Oid indexrelid, int colno,
 					   const Oid *excludeOps,
 					   bool attrsOnly, bool showTblSpc,
 					   int prettyFlags, bool missing_ok);
+static char *pg_get_partkeydef_worker(Oid relid, int prettyFlags);
 static char *pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 							int prettyFlags, bool missing_ok);
 static text *pg_get_expr_worker(text *expr, Oid relid, const char *relname,
@@ -1389,6 +1391,144 @@ 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 [ opclass_name ] [, ...])
+ */
+Datum
+pg_get_partkeydef(PG_FUNCTION_ARGS)
+{
+	Oid			relid = PG_GETARG_OID(0);
+	int			prettyFlags;
+
+	prettyFlags = PRETTYFLAG_INDENT;
+	PG_RETURN_TEXT_P(string_to_text(pg_get_partkeydef_worker(relid,
+									prettyFlags)));
+}
+
+/*
+ * Internal workhorse to decompile a partition key definition.
+ */
+static char *
+pg_get_partkeydef_worker(Oid relid, int prettyFlags)
+{
+	Form_pg_partitioned_table	form;
+	HeapTuple	tuple;
+	oidvector  *partclass;
+	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_table) GETSTRUCT(tuple);
+
+	Assert(form->partrelid == relid);
+
+	/* Must get partclass, and partexprs the hard way */
+	datum = SysCacheGetAttr(PARTEDRELID, tuple,
+							Anum_pg_partitioned_table_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_table_partexprbin))
+	{
+		Datum		exprsDatum;
+		bool		isnull;
+		char	   *exprsString;
+
+		exprsDatum = SysCacheGetAttr(PARTEDRELID, tuple,
+									 Anum_pg_partitioned_table_partexprbin, &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);
+
+	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,
+											0, 0);
+
+			appendStringInfoString(&buf, 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/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index ba9c276..c805a84 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -1253,9 +1253,10 @@ expand_table_name_patterns(Archive *fout,
 						  "SELECT c.oid"
 						  "\nFROM pg_catalog.pg_class c"
 		"\n     LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace"
-					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c')\n",
+					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c')\n",
 						  RELKIND_RELATION, RELKIND_SEQUENCE, RELKIND_VIEW,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 		processSQLNamePattern(GetConnection(fout), query, cell->val, true,
 							  false, "n.nspname", "c.relname", NULL,
 							  "pg_catalog.pg_table_is_visible(c.oid)");
@@ -2125,6 +2126,9 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo, bool oids)
 	/* Skip FOREIGN TABLEs (no data to dump) */
 	if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 		return;
+	/* Skip partitioned tables (data in partitions) */
+	if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+		return;
 
 	/* Don't dump data in unlogged tables, if so requested */
 	if (tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED &&
@@ -5204,6 +5208,7 @@ getTables(Archive *fout, int *numTables)
 	int			i_reloftype;
 	int			i_relpages;
 	int			i_changed_acl;
+	int			i_partkeydef;
 
 	/* Make sure we are in proper schema */
 	selectSourceSchema(fout, "pg_catalog");
@@ -5289,7 +5294,8 @@ getTables(Archive *fout, int *numTables)
 						  "OR %s IS NOT NULL "
 						  "OR %s IS NOT NULL"
 						  "))"
-						  "AS changed_acl "
+						  "AS changed_acl, "
+						  "CASE WHEN c.relkind = 'P' THEN pg_catalog.pg_get_partkeydef(c.oid) ELSE NULL END AS partkeydef "
 						  "FROM pg_class c "
 						  "LEFT JOIN pg_depend d ON "
 						  "(c.relkind = '%c' AND "
@@ -5301,7 +5307,7 @@ getTables(Archive *fout, int *numTables)
 						  "(c.oid = pip.objoid "
 						  "AND pip.classoid = 'pg_class'::regclass "
 						  "AND pip.objsubid = 0) "
-				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c') "
+				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c', '%c') "
 						  "ORDER BY c.oid",
 						  acl_subquery->data,
 						  racl_subquery->data,
@@ -5315,7 +5321,8 @@ getTables(Archive *fout, int *numTables)
 						  RELKIND_SEQUENCE,
 						  RELKIND_RELATION, RELKIND_SEQUENCE,
 						  RELKIND_VIEW, RELKIND_COMPOSITE_TYPE,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 
 		destroyPQExpBuffer(acl_subquery);
 		destroyPQExpBuffer(racl_subquery);
@@ -5877,6 +5884,7 @@ getTables(Archive *fout, int *numTables)
 	i_toastreloptions = PQfnumber(res, "toast_reloptions");
 	i_reloftype = PQfnumber(res, "reloftype");
 	i_changed_acl = PQfnumber(res, "changed_acl");
+	i_partkeydef = PQfnumber(res, "partkeydef");
 
 	if (dopt->lockWaitTimeout && fout->remoteVersion >= 70300)
 	{
@@ -5947,6 +5955,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 */
 
@@ -5991,7 +6000,9 @@ getTables(Archive *fout, int *numTables)
 		 * We only need to lock the table for certain components; see
 		 * pg_dump.h
 		 */
-		if (tblinfo[i].dobj.dump && tblinfo[i].relkind == RELKIND_RELATION &&
+		if (tblinfo[i].dobj.dump &&
+			(tblinfo[i].relkind == RELKIND_RELATION ||
+			 tblinfo->relkind == RELKIND_PARTITIONED_TABLE) &&
 			(tblinfo[i].dobj.dump & DUMP_COMPONENTS_REQUIRING_LOCK))
 		{
 			resetPQExpBuffer(query);
@@ -6093,7 +6104,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);
 
@@ -15446,6 +15460,9 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 				appendPQExpBufferChar(q, ')');
 			}
 
+			if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+				appendPQExpBuffer(q, "\nPARTITION BY %s", tbinfo->partkeydef);
+
 			if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 				appendPQExpBuffer(q, "\nSERVER %s", fmtId(srvname));
 		}
@@ -15506,6 +15523,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 		 */
 		if (dopt->binary_upgrade &&
 			(tbinfo->relkind == RELKIND_RELATION ||
+			 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 			 tbinfo->relkind == RELKIND_FOREIGN_TABLE))
 		{
 			for (j = 0; j < tbinfo->numatts; j++)
@@ -15524,7 +15542,8 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 					appendStringLiteralAH(q, fmtId(tbinfo->dobj.name), fout);
 					appendPQExpBufferStr(q, "::pg_catalog.regclass;\n");
 
-					if (tbinfo->relkind == RELKIND_RELATION)
+					if (tbinfo->relkind == RELKIND_RELATION ||
+						tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
 						appendPQExpBuffer(q, "ALTER TABLE ONLY %s ",
 										  fmtId(tbinfo->dobj.name));
 					else
@@ -15741,6 +15760,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 	 * dump properties we only have ALTER TABLE syntax for
 	 */
 	if ((tbinfo->relkind == RELKIND_RELATION ||
+		 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 		 tbinfo->relkind == RELKIND_MATVIEW) &&
 		tbinfo->relreplident != REPLICA_IDENTITY_DEFAULT)
 	{
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 2bfa2d9..0292859 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -310,6 +310,7 @@ typedef struct _tableInfo
 	bool	   *inhNotNull;		/* true if NOT NULL is inherited */
 	struct _attrDefInfo **attrdefs;		/* DEFAULT expressions */
 	struct _constraintInfo *checkexprs; /* CHECK constraints */
+	char	   *partkeydef;		/* partition key definition */
 
 	/*
 	 * Stuff computed only for dumpable tables.
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 6275a68..10d924a 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -861,6 +861,7 @@ permissionsList(const char *pattern)
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'S' THEN '%s'"
@@ -870,6 +871,7 @@ permissionsList(const char *pattern)
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("sequence"),
@@ -920,7 +922,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
@@ -1567,8 +1569,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)");
 	}
 
@@ -1633,6 +1635,14 @@ describeOneTableDetails(const char *schemaname,
 			printfPQExpBuffer(&title, _("Foreign table \"%s.%s\""),
 							  schemaname, relationname);
 			break;
+		case 'P':
+			if (tableinfo.relpersistence == 'u')
+				printfPQExpBuffer(&title, _("Unlogged table \"%s.%s\""),
+								  schemaname, relationname);
+			else
+				printfPQExpBuffer(&title, _("Table \"%s.%s\""),
+								  schemaname, relationname);
+			break;
 		default:
 			/* untranslated unknown relkind */
 			printfPQExpBuffer(&title, "?%c? \"%s.%s\"",
@@ -1646,8 +1656,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");
@@ -1667,12 +1677,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");
 	}
 
@@ -1772,7 +1782,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);
@@ -1780,14 +1790,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 (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 */
@@ -1926,7 +1955,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;
@@ -2485,7 +2514,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;
@@ -2696,7 +2725,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
@@ -3024,6 +3053,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'i' THEN '%s'"
@@ -3035,6 +3065,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("index"),
@@ -3079,7 +3110,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 019f75a..5e5c370 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/pg_proc.h b/src/include/catalog/pg_proc.h
index e2d08ba..b45688b 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -1980,6 +1980,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/utils/builtins.h b/src/include/utils/builtins.h
index 2ae212a..e800647 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -729,6 +729,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/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 708232d..2fec847 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -408,3 +408,29 @@ CREATE TABLE no_inh_con_parted (
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
 ERROR:  cannot add NO INHERIT constraint to partitioned table "no_inh_con_parted"
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+Table "public.describe_range_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition Key: RANGE ((a + b))
+
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+Table "public.describe_list_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition Key: LIST (a)
+
+DROP TABLE describe_range_key, describe_list_key;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 6e4a8be..4dd6a0a 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -402,3 +402,16 @@ CREATE TABLE no_inh_con_parted (
 	a int,
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
+
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+DROP TABLE describe_range_key, describe_list_key;
-- 
1.7.1


--------------4012470FC48539D6F6714CEA
Content-Type: text/x-diff;
 name="0003-Catalog-and-DDL-for-partitions-4.patch"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
 filename="0003-Catalog-and-DDL-for-partitions-4.patch"



^ permalink  raw  reply  [nested|flat] 110+ messages in thread

* [PATCH 2/9] psql and pg_dump support for partitioned tables.
@ 2016-07-12 08:20  amit <[email protected]>
  0 siblings, 0 replies; 110+ messages in thread

From: amit @ 2016-07-12 08:20 UTC (permalink / raw)

Takes care of both the partition key deparse stuff and the new relkind.
---
 src/backend/utils/adt/ruleutils.c          |  140 ++++++++++++++++++++++++++++
 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/pg_proc.h              |    2 +
 src/include/utils/builtins.h               |    1 +
 src/test/regress/expected/create_table.out |   26 +++++
 src/test/regress/sql/create_table.sql      |   13 +++
 9 files changed, 260 insertions(+), 26 deletions(-)

diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 8a81d7a..03be202 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_table.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
@@ -315,6 +316,7 @@ static char *pg_get_indexdef_worker(Oid indexrelid, int colno,
 					   const Oid *excludeOps,
 					   bool attrsOnly, bool showTblSpc,
 					   int prettyFlags, bool missing_ok);
+static char *pg_get_partkeydef_worker(Oid relid, int prettyFlags);
 static char *pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 							int prettyFlags, bool missing_ok);
 static text *pg_get_expr_worker(text *expr, Oid relid, const char *relname,
@@ -1389,6 +1391,144 @@ 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 [ opclass_name ] [, ...])
+ */
+Datum
+pg_get_partkeydef(PG_FUNCTION_ARGS)
+{
+	Oid			relid = PG_GETARG_OID(0);
+	int			prettyFlags;
+
+	prettyFlags = PRETTYFLAG_INDENT;
+	PG_RETURN_TEXT_P(string_to_text(pg_get_partkeydef_worker(relid,
+									prettyFlags)));
+}
+
+/*
+ * Internal workhorse to decompile a partition key definition.
+ */
+static char *
+pg_get_partkeydef_worker(Oid relid, int prettyFlags)
+{
+	Form_pg_partitioned_table	form;
+	HeapTuple	tuple;
+	oidvector  *partclass;
+	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_table) GETSTRUCT(tuple);
+
+	Assert(form->partrelid == relid);
+
+	/* Must get partclass, and partexprs the hard way */
+	datum = SysCacheGetAttr(PARTEDRELID, tuple,
+							Anum_pg_partitioned_table_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_table_partexprbin))
+	{
+		Datum		exprsDatum;
+		bool		isnull;
+		char	   *exprsString;
+
+		exprsDatum = SysCacheGetAttr(PARTEDRELID, tuple,
+									 Anum_pg_partitioned_table_partexprbin, &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);
+
+	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,
+											0, 0);
+
+			appendStringInfoString(&buf, 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/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 51b8a1a..854dddc 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -1253,9 +1253,10 @@ expand_table_name_patterns(Archive *fout,
 						  "SELECT c.oid"
 						  "\nFROM pg_catalog.pg_class c"
 		"\n     LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace"
-					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c')\n",
+					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c')\n",
 						  RELKIND_RELATION, RELKIND_SEQUENCE, RELKIND_VIEW,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 		processSQLNamePattern(GetConnection(fout), query, cell->val, true,
 							  false, "n.nspname", "c.relname", NULL,
 							  "pg_catalog.pg_table_is_visible(c.oid)");
@@ -2125,6 +2126,9 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo, bool oids)
 	/* Skip FOREIGN TABLEs (no data to dump) */
 	if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 		return;
+	/* Skip partitioned tables (data in partitions) */
+	if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+		return;
 
 	/* Don't dump data in unlogged tables, if so requested */
 	if (tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED &&
@@ -5211,6 +5215,7 @@ getTables(Archive *fout, int *numTables)
 	int			i_reloftype;
 	int			i_relpages;
 	int			i_changed_acl;
+	int			i_partkeydef;
 
 	/* Make sure we are in proper schema */
 	selectSourceSchema(fout, "pg_catalog");
@@ -5296,7 +5301,8 @@ getTables(Archive *fout, int *numTables)
 						  "OR %s IS NOT NULL "
 						  "OR %s IS NOT NULL"
 						  "))"
-						  "AS changed_acl "
+						  "AS changed_acl, "
+						  "CASE WHEN c.relkind = 'P' THEN pg_catalog.pg_get_partkeydef(c.oid) ELSE NULL END AS partkeydef "
 						  "FROM pg_class c "
 						  "LEFT JOIN pg_depend d ON "
 						  "(c.relkind = '%c' AND "
@@ -5308,7 +5314,7 @@ getTables(Archive *fout, int *numTables)
 						  "(c.oid = pip.objoid "
 						  "AND pip.classoid = 'pg_class'::regclass "
 						  "AND pip.objsubid = 0) "
-				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c') "
+				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c', '%c') "
 						  "ORDER BY c.oid",
 						  acl_subquery->data,
 						  racl_subquery->data,
@@ -5322,7 +5328,8 @@ getTables(Archive *fout, int *numTables)
 						  RELKIND_SEQUENCE,
 						  RELKIND_RELATION, RELKIND_SEQUENCE,
 						  RELKIND_VIEW, RELKIND_COMPOSITE_TYPE,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 
 		destroyPQExpBuffer(acl_subquery);
 		destroyPQExpBuffer(racl_subquery);
@@ -5884,6 +5891,7 @@ getTables(Archive *fout, int *numTables)
 	i_toastreloptions = PQfnumber(res, "toast_reloptions");
 	i_reloftype = PQfnumber(res, "reloftype");
 	i_changed_acl = PQfnumber(res, "changed_acl");
+	i_partkeydef = PQfnumber(res, "partkeydef");
 
 	if (dopt->lockWaitTimeout && fout->remoteVersion >= 70300)
 	{
@@ -5954,6 +5962,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 */
 
@@ -5998,7 +6007,9 @@ getTables(Archive *fout, int *numTables)
 		 * We only need to lock the table for certain components; see
 		 * pg_dump.h
 		 */
-		if (tblinfo[i].dobj.dump && tblinfo[i].relkind == RELKIND_RELATION &&
+		if (tblinfo[i].dobj.dump &&
+			(tblinfo[i].relkind == RELKIND_RELATION ||
+			 tblinfo->relkind == RELKIND_PARTITIONED_TABLE) &&
 			(tblinfo[i].dobj.dump & DUMP_COMPONENTS_REQUIRING_LOCK))
 		{
 			resetPQExpBuffer(query);
@@ -6100,7 +6111,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);
 
@@ -15453,6 +15467,9 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 				appendPQExpBufferChar(q, ')');
 			}
 
+			if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+				appendPQExpBuffer(q, "\nPARTITION BY %s", tbinfo->partkeydef);
+
 			if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 				appendPQExpBuffer(q, "\nSERVER %s", fmtId(srvname));
 		}
@@ -15513,6 +15530,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 		 */
 		if (dopt->binary_upgrade &&
 			(tbinfo->relkind == RELKIND_RELATION ||
+			 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 			 tbinfo->relkind == RELKIND_FOREIGN_TABLE))
 		{
 			for (j = 0; j < tbinfo->numatts; j++)
@@ -15531,7 +15549,8 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 					appendStringLiteralAH(q, fmtId(tbinfo->dobj.name), fout);
 					appendPQExpBufferStr(q, "::pg_catalog.regclass;\n");
 
-					if (tbinfo->relkind == RELKIND_RELATION)
+					if (tbinfo->relkind == RELKIND_RELATION ||
+						tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
 						appendPQExpBuffer(q, "ALTER TABLE ONLY %s ",
 										  fmtId(tbinfo->dobj.name));
 					else
@@ -15748,6 +15767,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 	 * dump properties we only have ALTER TABLE syntax for
 	 */
 	if ((tbinfo->relkind == RELKIND_RELATION ||
+		 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 		 tbinfo->relkind == RELKIND_MATVIEW) &&
 		tbinfo->relreplident != REPLICA_IDENTITY_DEFAULT)
 	{
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 2bfa2d9..0292859 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -310,6 +310,7 @@ typedef struct _tableInfo
 	bool	   *inhNotNull;		/* true if NOT NULL is inherited */
 	struct _attrDefInfo **attrdefs;		/* DEFAULT expressions */
 	struct _constraintInfo *checkexprs; /* CHECK constraints */
+	char	   *partkeydef;		/* partition key definition */
 
 	/*
 	 * Stuff computed only for dumpable tables.
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 6275a68..db6dc5c 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -861,6 +861,7 @@ permissionsList(const char *pattern)
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'S' THEN '%s'"
@@ -870,6 +871,7 @@ permissionsList(const char *pattern)
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("sequence"),
@@ -920,7 +922,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
@@ -1567,8 +1569,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)");
 	}
 
@@ -1633,6 +1635,14 @@ describeOneTableDetails(const char *schemaname,
 			printfPQExpBuffer(&title, _("Foreign table \"%s.%s\""),
 							  schemaname, relationname);
 			break;
+		case 'P':
+			if (tableinfo.relpersistence == 'u')
+				printfPQExpBuffer(&title, _("Unlogged table \"%s.%s\""),
+								  schemaname, relationname);
+			else
+				printfPQExpBuffer(&title, _("Table \"%s.%s\""),
+								  schemaname, relationname);
+			break;
 		default:
 			/* untranslated unknown relkind */
 			printfPQExpBuffer(&title, "?%c? \"%s.%s\"",
@@ -1646,8 +1656,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");
@@ -1667,12 +1677,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");
 	}
 
@@ -1772,7 +1782,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);
@@ -1780,14 +1790,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 (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 */
@@ -1926,7 +1955,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;
@@ -2485,7 +2514,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;
@@ -2696,7 +2725,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
@@ -3024,6 +3053,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'i' THEN '%s'"
@@ -3035,6 +3065,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("index"),
@@ -3079,7 +3110,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 50a45eb..8284a9c 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/pg_proc.h b/src/include/catalog/pg_proc.h
index e2d08ba..b45688b 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -1980,6 +1980,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/utils/builtins.h b/src/include/utils/builtins.h
index 2ae212a..e800647 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -729,6 +729,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/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 1394362..260ec34 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -407,3 +407,29 @@ CREATE TABLE no_inh_con_parted (
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
 ERROR:  cannot add NO INHERIT constraint to partitioned table "no_inh_con_parted"
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+Table "public.describe_range_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition key: RANGE ((a + b))
+
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+Table "public.describe_list_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition key: LIST (a)
+
+DROP TABLE describe_range_key, describe_list_key;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index d383f58..7a1f8de 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -401,3 +401,16 @@ CREATE TABLE no_inh_con_parted (
 	a int,
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
+
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+DROP TABLE describe_range_key, describe_list_key;
-- 
1.7.1


--------------C3235EE96C69D8DAE4DB543E
Content-Type: text/x-diff;
 name="0003-Catalog-and-DDL-for-partitions-6.patch"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
 filename="0003-Catalog-and-DDL-for-partitions-6.patch"



^ permalink  raw  reply  [nested|flat] 110+ messages in thread

* [PATCH 2/9] psql and pg_dump support for partitioned tables.
@ 2016-07-12 08:20  amit <[email protected]>
  0 siblings, 0 replies; 110+ messages in thread

From: amit @ 2016-07-12 08:20 UTC (permalink / raw)

Takes care of both the partition key deparse stuff and the new relkind.
---
 src/backend/utils/adt/ruleutils.c          |  140 ++++++++++++++++++++++++++++
 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/pg_proc.h              |    2 +
 src/include/utils/builtins.h               |    1 +
 src/test/regress/expected/create_table.out |   26 +++++
 src/test/regress/sql/create_table.sql      |   13 +++
 9 files changed, 260 insertions(+), 26 deletions(-)

diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 8a81d7a..77ce807 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_table.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
@@ -315,6 +316,7 @@ static char *pg_get_indexdef_worker(Oid indexrelid, int colno,
 					   const Oid *excludeOps,
 					   bool attrsOnly, bool showTblSpc,
 					   int prettyFlags, bool missing_ok);
+static char *pg_get_partkeydef_worker(Oid relId, int prettyFlags);
 static char *pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 							int prettyFlags, bool missing_ok);
 static text *pg_get_expr_worker(text *expr, Oid relid, const char *relname,
@@ -1389,6 +1391,144 @@ 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 [ 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_table	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_table) GETSTRUCT(tuple);
+
+	Assert(form->partedrelid == relId);
+
+	/* Must get partclass, and partexprs the hard way */
+	datum = SysCacheGetAttr(PARTEDRELID, tuple,
+							Anum_pg_partitioned_table_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_table_partexprbin))
+	{
+		Datum		exprsDatum;
+		bool		isnull;
+		char	   *exprsString;
+
+		exprsDatum = SysCacheGetAttr(PARTEDRELID, tuple,
+									 Anum_pg_partitioned_table_partexprbin, &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);
+
+	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,
+											0, 0);
+
+			appendStringInfoString(&buf, 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/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index a5c2d09..c418ba7 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -1253,9 +1253,10 @@ expand_table_name_patterns(Archive *fout,
 						  "SELECT c.oid"
 						  "\nFROM pg_catalog.pg_class c"
 		"\n     LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace"
-					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c')\n",
+					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c')\n",
 						  RELKIND_RELATION, RELKIND_SEQUENCE, RELKIND_VIEW,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 		processSQLNamePattern(GetConnection(fout), query, cell->val, true,
 							  false, "n.nspname", "c.relname", NULL,
 							  "pg_catalog.pg_table_is_visible(c.oid)");
@@ -2117,6 +2118,9 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo, bool oids)
 	/* Skip FOREIGN TABLEs (no data to dump) */
 	if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 		return;
+	/* Skip partitioned tables (data in partitions) */
+	if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+		return;
 
 	/* Don't dump data in unlogged tables, if so requested */
 	if (tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED &&
@@ -5195,6 +5199,7 @@ getTables(Archive *fout, int *numTables)
 	int			i_reloftype;
 	int			i_relpages;
 	int			i_changed_acl;
+	int			i_partkeydef;
 
 	/* Make sure we are in proper schema */
 	selectSourceSchema(fout, "pg_catalog");
@@ -5280,7 +5285,8 @@ getTables(Archive *fout, int *numTables)
 						  "OR %s IS NOT NULL "
 						  "OR %s IS NOT NULL"
 						  "))"
-						  "AS changed_acl "
+						  "AS changed_acl, "
+						  "CASE WHEN c.relkind = 'P' THEN pg_catalog.pg_get_partkeydef(c.oid) ELSE NULL END AS partkeydef "
 						  "FROM pg_class c "
 						  "LEFT JOIN pg_depend d ON "
 						  "(c.relkind = '%c' AND "
@@ -5292,7 +5298,7 @@ getTables(Archive *fout, int *numTables)
 						  "(c.oid = pip.objoid "
 						  "AND pip.classoid = 'pg_class'::regclass "
 						  "AND pip.objsubid = 0) "
-				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c') "
+				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c', '%c') "
 						  "ORDER BY c.oid",
 						  acl_subquery->data,
 						  racl_subquery->data,
@@ -5306,7 +5312,8 @@ getTables(Archive *fout, int *numTables)
 						  RELKIND_SEQUENCE,
 						  RELKIND_RELATION, RELKIND_SEQUENCE,
 						  RELKIND_VIEW, RELKIND_COMPOSITE_TYPE,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 
 		destroyPQExpBuffer(acl_subquery);
 		destroyPQExpBuffer(racl_subquery);
@@ -5868,6 +5875,7 @@ getTables(Archive *fout, int *numTables)
 	i_toastreloptions = PQfnumber(res, "toast_reloptions");
 	i_reloftype = PQfnumber(res, "reloftype");
 	i_changed_acl = PQfnumber(res, "changed_acl");
+	i_partkeydef = PQfnumber(res, "partkeydef");
 
 	if (dopt->lockWaitTimeout && fout->remoteVersion >= 70300)
 	{
@@ -5938,6 +5946,7 @@ getTables(Archive *fout, int *numTables)
 		else
 			tblinfo[i].checkoption = pg_strdup(PQgetvalue(res, i, i_checkoption));
 		tblinfo[i].toast_reloptions = pg_strdup(PQgetvalue(res, i, i_toastreloptions));
+		tblinfo[i].partkeydef = pg_strdup(PQgetvalue(res, i, i_partkeydef));
 
 		/* other fields were zeroed above */
 
@@ -5982,7 +5991,9 @@ getTables(Archive *fout, int *numTables)
 		 * We only need to lock the table for certain components; see
 		 * pg_dump.h
 		 */
-		if (tblinfo[i].dobj.dump && tblinfo[i].relkind == RELKIND_RELATION &&
+		if (tblinfo[i].dobj.dump &&
+			(tblinfo[i].relkind == RELKIND_RELATION ||
+			 tblinfo->relkind == RELKIND_PARTITIONED_TABLE) &&
 			(tblinfo[i].dobj.dump & DUMP_COMPONENTS_REQUIRING_LOCK))
 		{
 			resetPQExpBuffer(query);
@@ -6084,7 +6095,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);
 
@@ -15437,6 +15451,9 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 				appendPQExpBufferChar(q, ')');
 			}
 
+			if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+				appendPQExpBuffer(q, "\nPARTITION BY %s", tbinfo->partkeydef);
+
 			if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 				appendPQExpBuffer(q, "\nSERVER %s", fmtId(srvname));
 		}
@@ -15497,6 +15514,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 		 */
 		if (dopt->binary_upgrade &&
 			(tbinfo->relkind == RELKIND_RELATION ||
+			 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 			 tbinfo->relkind == RELKIND_FOREIGN_TABLE))
 		{
 			for (j = 0; j < tbinfo->numatts; j++)
@@ -15515,7 +15533,8 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 					appendStringLiteralAH(q, fmtId(tbinfo->dobj.name), fout);
 					appendPQExpBufferStr(q, "::pg_catalog.regclass;\n");
 
-					if (tbinfo->relkind == RELKIND_RELATION)
+					if (tbinfo->relkind == RELKIND_RELATION ||
+						tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
 						appendPQExpBuffer(q, "ALTER TABLE ONLY %s ",
 										  fmtId(tbinfo->dobj.name));
 					else
@@ -15732,6 +15751,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 	 * dump properties we only have ALTER TABLE syntax for
 	 */
 	if ((tbinfo->relkind == RELKIND_RELATION ||
+		 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 		 tbinfo->relkind == RELKIND_MATVIEW) &&
 		tbinfo->relreplident != REPLICA_IDENTITY_DEFAULT)
 	{
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 2bfa2d9..0292859 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -310,6 +310,7 @@ typedef struct _tableInfo
 	bool	   *inhNotNull;		/* true if NOT NULL is inherited */
 	struct _attrDefInfo **attrdefs;		/* DEFAULT expressions */
 	struct _constraintInfo *checkexprs; /* CHECK constraints */
+	char	   *partkeydef;		/* partition key definition */
 
 	/*
 	 * Stuff computed only for dumpable tables.
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 6275a68..10d924a 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -861,6 +861,7 @@ permissionsList(const char *pattern)
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'S' THEN '%s'"
@@ -870,6 +871,7 @@ permissionsList(const char *pattern)
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("sequence"),
@@ -920,7 +922,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
@@ -1567,8 +1569,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)");
 	}
 
@@ -1633,6 +1635,14 @@ describeOneTableDetails(const char *schemaname,
 			printfPQExpBuffer(&title, _("Foreign table \"%s.%s\""),
 							  schemaname, relationname);
 			break;
+		case 'P':
+			if (tableinfo.relpersistence == 'u')
+				printfPQExpBuffer(&title, _("Unlogged table \"%s.%s\""),
+								  schemaname, relationname);
+			else
+				printfPQExpBuffer(&title, _("Table \"%s.%s\""),
+								  schemaname, relationname);
+			break;
 		default:
 			/* untranslated unknown relkind */
 			printfPQExpBuffer(&title, "?%c? \"%s.%s\"",
@@ -1646,8 +1656,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");
@@ -1667,12 +1677,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");
 	}
 
@@ -1772,7 +1782,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);
@@ -1780,14 +1790,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 (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 */
@@ -1926,7 +1955,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;
@@ -2485,7 +2514,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;
@@ -2696,7 +2725,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
@@ -3024,6 +3053,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'i' THEN '%s'"
@@ -3035,6 +3065,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("index"),
@@ -3079,7 +3110,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 1345e4e..4686cd1 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/pg_proc.h b/src/include/catalog/pg_proc.h
index e2d08ba..b45688b 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -1980,6 +1980,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/utils/builtins.h b/src/include/utils/builtins.h
index 2ae212a..e800647 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -729,6 +729,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/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 708232d..2fec847 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -408,3 +408,29 @@ CREATE TABLE no_inh_con_parted (
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
 ERROR:  cannot add NO INHERIT constraint to partitioned table "no_inh_con_parted"
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+Table "public.describe_range_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition Key: RANGE ((a + b))
+
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+Table "public.describe_list_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition Key: LIST (a)
+
+DROP TABLE describe_range_key, describe_list_key;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 6e4a8be..4dd6a0a 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -402,3 +402,16 @@ CREATE TABLE no_inh_con_parted (
 	a int,
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
+
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+DROP TABLE describe_range_key, describe_list_key;
-- 
1.7.1


--------------954458C17BBEB1BF6FC758E7
Content-Type: text/x-diff;
 name="0003-Catalog-and-DDL-for-partitions-2.patch"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
 filename="0003-Catalog-and-DDL-for-partitions-2.patch"



^ permalink  raw  reply  [nested|flat] 110+ messages in thread

* [PATCH 2/9] psql and pg_dump support for partitioned tables.
@ 2016-07-12 08:20  amit <[email protected]>
  0 siblings, 0 replies; 110+ messages in thread

From: amit @ 2016-07-12 08:20 UTC (permalink / raw)

Takes care of both the partition key deparse stuff and the new relkind.
---
 src/backend/utils/adt/ruleutils.c          |  140 ++++++++++++++++++++++++++++
 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/pg_proc.h              |    2 +
 src/include/utils/builtins.h               |    1 +
 src/test/regress/expected/create_table.out |   26 +++++
 src/test/regress/sql/create_table.sql      |   13 +++
 9 files changed, 260 insertions(+), 26 deletions(-)

diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 8a81d7a..77ce807 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_table.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
@@ -315,6 +316,7 @@ static char *pg_get_indexdef_worker(Oid indexrelid, int colno,
 					   const Oid *excludeOps,
 					   bool attrsOnly, bool showTblSpc,
 					   int prettyFlags, bool missing_ok);
+static char *pg_get_partkeydef_worker(Oid relId, int prettyFlags);
 static char *pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 							int prettyFlags, bool missing_ok);
 static text *pg_get_expr_worker(text *expr, Oid relid, const char *relname,
@@ -1389,6 +1391,144 @@ 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 [ 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_table	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_table) GETSTRUCT(tuple);
+
+	Assert(form->partedrelid == relId);
+
+	/* Must get partclass, and partexprs the hard way */
+	datum = SysCacheGetAttr(PARTEDRELID, tuple,
+							Anum_pg_partitioned_table_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_table_partexprbin))
+	{
+		Datum		exprsDatum;
+		bool		isnull;
+		char	   *exprsString;
+
+		exprsDatum = SysCacheGetAttr(PARTEDRELID, tuple,
+									 Anum_pg_partitioned_table_partexprbin, &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);
+
+	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,
+											0, 0);
+
+			appendStringInfoString(&buf, 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/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index a5c2d09..c418ba7 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -1253,9 +1253,10 @@ expand_table_name_patterns(Archive *fout,
 						  "SELECT c.oid"
 						  "\nFROM pg_catalog.pg_class c"
 		"\n     LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace"
-					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c')\n",
+					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c')\n",
 						  RELKIND_RELATION, RELKIND_SEQUENCE, RELKIND_VIEW,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 		processSQLNamePattern(GetConnection(fout), query, cell->val, true,
 							  false, "n.nspname", "c.relname", NULL,
 							  "pg_catalog.pg_table_is_visible(c.oid)");
@@ -2117,6 +2118,9 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo, bool oids)
 	/* Skip FOREIGN TABLEs (no data to dump) */
 	if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 		return;
+	/* Skip partitioned tables (data in partitions) */
+	if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+		return;
 
 	/* Don't dump data in unlogged tables, if so requested */
 	if (tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED &&
@@ -5195,6 +5199,7 @@ getTables(Archive *fout, int *numTables)
 	int			i_reloftype;
 	int			i_relpages;
 	int			i_changed_acl;
+	int			i_partkeydef;
 
 	/* Make sure we are in proper schema */
 	selectSourceSchema(fout, "pg_catalog");
@@ -5280,7 +5285,8 @@ getTables(Archive *fout, int *numTables)
 						  "OR %s IS NOT NULL "
 						  "OR %s IS NOT NULL"
 						  "))"
-						  "AS changed_acl "
+						  "AS changed_acl, "
+						  "CASE WHEN c.relkind = 'P' THEN pg_catalog.pg_get_partkeydef(c.oid) ELSE NULL END AS partkeydef "
 						  "FROM pg_class c "
 						  "LEFT JOIN pg_depend d ON "
 						  "(c.relkind = '%c' AND "
@@ -5292,7 +5298,7 @@ getTables(Archive *fout, int *numTables)
 						  "(c.oid = pip.objoid "
 						  "AND pip.classoid = 'pg_class'::regclass "
 						  "AND pip.objsubid = 0) "
-				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c') "
+				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c', '%c') "
 						  "ORDER BY c.oid",
 						  acl_subquery->data,
 						  racl_subquery->data,
@@ -5306,7 +5312,8 @@ getTables(Archive *fout, int *numTables)
 						  RELKIND_SEQUENCE,
 						  RELKIND_RELATION, RELKIND_SEQUENCE,
 						  RELKIND_VIEW, RELKIND_COMPOSITE_TYPE,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 
 		destroyPQExpBuffer(acl_subquery);
 		destroyPQExpBuffer(racl_subquery);
@@ -5868,6 +5875,7 @@ getTables(Archive *fout, int *numTables)
 	i_toastreloptions = PQfnumber(res, "toast_reloptions");
 	i_reloftype = PQfnumber(res, "reloftype");
 	i_changed_acl = PQfnumber(res, "changed_acl");
+	i_partkeydef = PQfnumber(res, "partkeydef");
 
 	if (dopt->lockWaitTimeout && fout->remoteVersion >= 70300)
 	{
@@ -5938,6 +5946,7 @@ getTables(Archive *fout, int *numTables)
 		else
 			tblinfo[i].checkoption = pg_strdup(PQgetvalue(res, i, i_checkoption));
 		tblinfo[i].toast_reloptions = pg_strdup(PQgetvalue(res, i, i_toastreloptions));
+		tblinfo[i].partkeydef = pg_strdup(PQgetvalue(res, i, i_partkeydef));
 
 		/* other fields were zeroed above */
 
@@ -5982,7 +5991,9 @@ getTables(Archive *fout, int *numTables)
 		 * We only need to lock the table for certain components; see
 		 * pg_dump.h
 		 */
-		if (tblinfo[i].dobj.dump && tblinfo[i].relkind == RELKIND_RELATION &&
+		if (tblinfo[i].dobj.dump &&
+			(tblinfo[i].relkind == RELKIND_RELATION ||
+			 tblinfo->relkind == RELKIND_PARTITIONED_TABLE) &&
 			(tblinfo[i].dobj.dump & DUMP_COMPONENTS_REQUIRING_LOCK))
 		{
 			resetPQExpBuffer(query);
@@ -6084,7 +6095,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);
 
@@ -15437,6 +15451,9 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 				appendPQExpBufferChar(q, ')');
 			}
 
+			if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+				appendPQExpBuffer(q, "\nPARTITION BY %s", tbinfo->partkeydef);
+
 			if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 				appendPQExpBuffer(q, "\nSERVER %s", fmtId(srvname));
 		}
@@ -15497,6 +15514,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 		 */
 		if (dopt->binary_upgrade &&
 			(tbinfo->relkind == RELKIND_RELATION ||
+			 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 			 tbinfo->relkind == RELKIND_FOREIGN_TABLE))
 		{
 			for (j = 0; j < tbinfo->numatts; j++)
@@ -15515,7 +15533,8 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 					appendStringLiteralAH(q, fmtId(tbinfo->dobj.name), fout);
 					appendPQExpBufferStr(q, "::pg_catalog.regclass;\n");
 
-					if (tbinfo->relkind == RELKIND_RELATION)
+					if (tbinfo->relkind == RELKIND_RELATION ||
+						tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
 						appendPQExpBuffer(q, "ALTER TABLE ONLY %s ",
 										  fmtId(tbinfo->dobj.name));
 					else
@@ -15732,6 +15751,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 	 * dump properties we only have ALTER TABLE syntax for
 	 */
 	if ((tbinfo->relkind == RELKIND_RELATION ||
+		 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 		 tbinfo->relkind == RELKIND_MATVIEW) &&
 		tbinfo->relreplident != REPLICA_IDENTITY_DEFAULT)
 	{
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 2bfa2d9..0292859 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -310,6 +310,7 @@ typedef struct _tableInfo
 	bool	   *inhNotNull;		/* true if NOT NULL is inherited */
 	struct _attrDefInfo **attrdefs;		/* DEFAULT expressions */
 	struct _constraintInfo *checkexprs; /* CHECK constraints */
+	char	   *partkeydef;		/* partition key definition */
 
 	/*
 	 * Stuff computed only for dumpable tables.
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 6275a68..10d924a 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -861,6 +861,7 @@ permissionsList(const char *pattern)
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'S' THEN '%s'"
@@ -870,6 +871,7 @@ permissionsList(const char *pattern)
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("sequence"),
@@ -920,7 +922,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
@@ -1567,8 +1569,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)");
 	}
 
@@ -1633,6 +1635,14 @@ describeOneTableDetails(const char *schemaname,
 			printfPQExpBuffer(&title, _("Foreign table \"%s.%s\""),
 							  schemaname, relationname);
 			break;
+		case 'P':
+			if (tableinfo.relpersistence == 'u')
+				printfPQExpBuffer(&title, _("Unlogged table \"%s.%s\""),
+								  schemaname, relationname);
+			else
+				printfPQExpBuffer(&title, _("Table \"%s.%s\""),
+								  schemaname, relationname);
+			break;
 		default:
 			/* untranslated unknown relkind */
 			printfPQExpBuffer(&title, "?%c? \"%s.%s\"",
@@ -1646,8 +1656,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");
@@ -1667,12 +1677,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");
 	}
 
@@ -1772,7 +1782,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);
@@ -1780,14 +1790,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 (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 */
@@ -1926,7 +1955,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;
@@ -2485,7 +2514,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;
@@ -2696,7 +2725,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
@@ -3024,6 +3053,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'i' THEN '%s'"
@@ -3035,6 +3065,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("index"),
@@ -3079,7 +3110,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 019f75a..5e5c370 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/pg_proc.h b/src/include/catalog/pg_proc.h
index e2d08ba..b45688b 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -1980,6 +1980,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/utils/builtins.h b/src/include/utils/builtins.h
index 2ae212a..e800647 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -729,6 +729,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/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 708232d..2fec847 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -408,3 +408,29 @@ CREATE TABLE no_inh_con_parted (
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
 ERROR:  cannot add NO INHERIT constraint to partitioned table "no_inh_con_parted"
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+Table "public.describe_range_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition Key: RANGE ((a + b))
+
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+Table "public.describe_list_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition Key: LIST (a)
+
+DROP TABLE describe_range_key, describe_list_key;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 6e4a8be..4dd6a0a 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -402,3 +402,16 @@ CREATE TABLE no_inh_con_parted (
 	a int,
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
+
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+DROP TABLE describe_range_key, describe_list_key;
-- 
1.7.1


--------------6EB5B318125CB7DB1A4FD229
Content-Type: text/x-diff;
 name="0003-Catalog-and-DDL-for-partitions-3.patch"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
 filename="0003-Catalog-and-DDL-for-partitions-3.patch"



^ permalink  raw  reply  [nested|flat] 110+ messages in thread

* [PATCH 2/9] psql and pg_dump support for partitioned tables.
@ 2016-07-12 08:20  amit <[email protected]>
  0 siblings, 0 replies; 110+ messages in thread

From: amit @ 2016-07-12 08:20 UTC (permalink / raw)

Takes care of both the partition key deparse stuff and the new relkind.
---
 src/backend/utils/adt/ruleutils.c          |  140 ++++++++++++++++++++++++++++
 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/pg_proc.h              |    2 +
 src/include/utils/builtins.h               |    1 +
 src/test/regress/expected/create_table.out |   26 +++++
 src/test/regress/sql/create_table.sql      |   13 +++
 9 files changed, 260 insertions(+), 26 deletions(-)

diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 8a81d7a..03be202 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_table.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
@@ -315,6 +316,7 @@ static char *pg_get_indexdef_worker(Oid indexrelid, int colno,
 					   const Oid *excludeOps,
 					   bool attrsOnly, bool showTblSpc,
 					   int prettyFlags, bool missing_ok);
+static char *pg_get_partkeydef_worker(Oid relid, int prettyFlags);
 static char *pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 							int prettyFlags, bool missing_ok);
 static text *pg_get_expr_worker(text *expr, Oid relid, const char *relname,
@@ -1389,6 +1391,144 @@ 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 [ opclass_name ] [, ...])
+ */
+Datum
+pg_get_partkeydef(PG_FUNCTION_ARGS)
+{
+	Oid			relid = PG_GETARG_OID(0);
+	int			prettyFlags;
+
+	prettyFlags = PRETTYFLAG_INDENT;
+	PG_RETURN_TEXT_P(string_to_text(pg_get_partkeydef_worker(relid,
+									prettyFlags)));
+}
+
+/*
+ * Internal workhorse to decompile a partition key definition.
+ */
+static char *
+pg_get_partkeydef_worker(Oid relid, int prettyFlags)
+{
+	Form_pg_partitioned_table	form;
+	HeapTuple	tuple;
+	oidvector  *partclass;
+	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_table) GETSTRUCT(tuple);
+
+	Assert(form->partrelid == relid);
+
+	/* Must get partclass, and partexprs the hard way */
+	datum = SysCacheGetAttr(PARTEDRELID, tuple,
+							Anum_pg_partitioned_table_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_table_partexprbin))
+	{
+		Datum		exprsDatum;
+		bool		isnull;
+		char	   *exprsString;
+
+		exprsDatum = SysCacheGetAttr(PARTEDRELID, tuple,
+									 Anum_pg_partitioned_table_partexprbin, &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);
+
+	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,
+											0, 0);
+
+			appendStringInfoString(&buf, 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/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index ba9c276..c805a84 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -1253,9 +1253,10 @@ expand_table_name_patterns(Archive *fout,
 						  "SELECT c.oid"
 						  "\nFROM pg_catalog.pg_class c"
 		"\n     LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace"
-					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c')\n",
+					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c')\n",
 						  RELKIND_RELATION, RELKIND_SEQUENCE, RELKIND_VIEW,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 		processSQLNamePattern(GetConnection(fout), query, cell->val, true,
 							  false, "n.nspname", "c.relname", NULL,
 							  "pg_catalog.pg_table_is_visible(c.oid)");
@@ -2125,6 +2126,9 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo, bool oids)
 	/* Skip FOREIGN TABLEs (no data to dump) */
 	if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 		return;
+	/* Skip partitioned tables (data in partitions) */
+	if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+		return;
 
 	/* Don't dump data in unlogged tables, if so requested */
 	if (tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED &&
@@ -5204,6 +5208,7 @@ getTables(Archive *fout, int *numTables)
 	int			i_reloftype;
 	int			i_relpages;
 	int			i_changed_acl;
+	int			i_partkeydef;
 
 	/* Make sure we are in proper schema */
 	selectSourceSchema(fout, "pg_catalog");
@@ -5289,7 +5294,8 @@ getTables(Archive *fout, int *numTables)
 						  "OR %s IS NOT NULL "
 						  "OR %s IS NOT NULL"
 						  "))"
-						  "AS changed_acl "
+						  "AS changed_acl, "
+						  "CASE WHEN c.relkind = 'P' THEN pg_catalog.pg_get_partkeydef(c.oid) ELSE NULL END AS partkeydef "
 						  "FROM pg_class c "
 						  "LEFT JOIN pg_depend d ON "
 						  "(c.relkind = '%c' AND "
@@ -5301,7 +5307,7 @@ getTables(Archive *fout, int *numTables)
 						  "(c.oid = pip.objoid "
 						  "AND pip.classoid = 'pg_class'::regclass "
 						  "AND pip.objsubid = 0) "
-				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c') "
+				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c', '%c') "
 						  "ORDER BY c.oid",
 						  acl_subquery->data,
 						  racl_subquery->data,
@@ -5315,7 +5321,8 @@ getTables(Archive *fout, int *numTables)
 						  RELKIND_SEQUENCE,
 						  RELKIND_RELATION, RELKIND_SEQUENCE,
 						  RELKIND_VIEW, RELKIND_COMPOSITE_TYPE,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 
 		destroyPQExpBuffer(acl_subquery);
 		destroyPQExpBuffer(racl_subquery);
@@ -5877,6 +5884,7 @@ getTables(Archive *fout, int *numTables)
 	i_toastreloptions = PQfnumber(res, "toast_reloptions");
 	i_reloftype = PQfnumber(res, "reloftype");
 	i_changed_acl = PQfnumber(res, "changed_acl");
+	i_partkeydef = PQfnumber(res, "partkeydef");
 
 	if (dopt->lockWaitTimeout && fout->remoteVersion >= 70300)
 	{
@@ -5947,6 +5955,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 */
 
@@ -5991,7 +6000,9 @@ getTables(Archive *fout, int *numTables)
 		 * We only need to lock the table for certain components; see
 		 * pg_dump.h
 		 */
-		if (tblinfo[i].dobj.dump && tblinfo[i].relkind == RELKIND_RELATION &&
+		if (tblinfo[i].dobj.dump &&
+			(tblinfo[i].relkind == RELKIND_RELATION ||
+			 tblinfo->relkind == RELKIND_PARTITIONED_TABLE) &&
 			(tblinfo[i].dobj.dump & DUMP_COMPONENTS_REQUIRING_LOCK))
 		{
 			resetPQExpBuffer(query);
@@ -6093,7 +6104,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);
 
@@ -15446,6 +15460,9 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 				appendPQExpBufferChar(q, ')');
 			}
 
+			if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+				appendPQExpBuffer(q, "\nPARTITION BY %s", tbinfo->partkeydef);
+
 			if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 				appendPQExpBuffer(q, "\nSERVER %s", fmtId(srvname));
 		}
@@ -15506,6 +15523,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 		 */
 		if (dopt->binary_upgrade &&
 			(tbinfo->relkind == RELKIND_RELATION ||
+			 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 			 tbinfo->relkind == RELKIND_FOREIGN_TABLE))
 		{
 			for (j = 0; j < tbinfo->numatts; j++)
@@ -15524,7 +15542,8 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 					appendStringLiteralAH(q, fmtId(tbinfo->dobj.name), fout);
 					appendPQExpBufferStr(q, "::pg_catalog.regclass;\n");
 
-					if (tbinfo->relkind == RELKIND_RELATION)
+					if (tbinfo->relkind == RELKIND_RELATION ||
+						tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
 						appendPQExpBuffer(q, "ALTER TABLE ONLY %s ",
 										  fmtId(tbinfo->dobj.name));
 					else
@@ -15741,6 +15760,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 	 * dump properties we only have ALTER TABLE syntax for
 	 */
 	if ((tbinfo->relkind == RELKIND_RELATION ||
+		 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 		 tbinfo->relkind == RELKIND_MATVIEW) &&
 		tbinfo->relreplident != REPLICA_IDENTITY_DEFAULT)
 	{
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 2bfa2d9..0292859 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -310,6 +310,7 @@ typedef struct _tableInfo
 	bool	   *inhNotNull;		/* true if NOT NULL is inherited */
 	struct _attrDefInfo **attrdefs;		/* DEFAULT expressions */
 	struct _constraintInfo *checkexprs; /* CHECK constraints */
+	char	   *partkeydef;		/* partition key definition */
 
 	/*
 	 * Stuff computed only for dumpable tables.
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 6275a68..10d924a 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -861,6 +861,7 @@ permissionsList(const char *pattern)
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'S' THEN '%s'"
@@ -870,6 +871,7 @@ permissionsList(const char *pattern)
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("sequence"),
@@ -920,7 +922,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
@@ -1567,8 +1569,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)");
 	}
 
@@ -1633,6 +1635,14 @@ describeOneTableDetails(const char *schemaname,
 			printfPQExpBuffer(&title, _("Foreign table \"%s.%s\""),
 							  schemaname, relationname);
 			break;
+		case 'P':
+			if (tableinfo.relpersistence == 'u')
+				printfPQExpBuffer(&title, _("Unlogged table \"%s.%s\""),
+								  schemaname, relationname);
+			else
+				printfPQExpBuffer(&title, _("Table \"%s.%s\""),
+								  schemaname, relationname);
+			break;
 		default:
 			/* untranslated unknown relkind */
 			printfPQExpBuffer(&title, "?%c? \"%s.%s\"",
@@ -1646,8 +1656,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");
@@ -1667,12 +1677,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");
 	}
 
@@ -1772,7 +1782,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);
@@ -1780,14 +1790,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 (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 */
@@ -1926,7 +1955,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;
@@ -2485,7 +2514,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;
@@ -2696,7 +2725,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
@@ -3024,6 +3053,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'i' THEN '%s'"
@@ -3035,6 +3065,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("index"),
@@ -3079,7 +3110,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 019f75a..5e5c370 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/pg_proc.h b/src/include/catalog/pg_proc.h
index e2d08ba..b45688b 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -1980,6 +1980,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/utils/builtins.h b/src/include/utils/builtins.h
index 2ae212a..e800647 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -729,6 +729,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/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 708232d..2fec847 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -408,3 +408,29 @@ CREATE TABLE no_inh_con_parted (
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
 ERROR:  cannot add NO INHERIT constraint to partitioned table "no_inh_con_parted"
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+Table "public.describe_range_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition Key: RANGE ((a + b))
+
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+Table "public.describe_list_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition Key: LIST (a)
+
+DROP TABLE describe_range_key, describe_list_key;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 6e4a8be..4dd6a0a 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -402,3 +402,16 @@ CREATE TABLE no_inh_con_parted (
 	a int,
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
+
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+DROP TABLE describe_range_key, describe_list_key;
-- 
1.7.1


--------------4012470FC48539D6F6714CEA
Content-Type: text/x-diff;
 name="0003-Catalog-and-DDL-for-partitions-4.patch"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
 filename="0003-Catalog-and-DDL-for-partitions-4.patch"



^ permalink  raw  reply  [nested|flat] 110+ messages in thread

* [PATCH 2/9] psql and pg_dump support for partitioned tables.
@ 2016-07-12 08:20  amit <[email protected]>
  0 siblings, 0 replies; 110+ messages in thread

From: amit @ 2016-07-12 08:20 UTC (permalink / raw)

Takes care of both the partition key deparse stuff and the new relkind.
---
 src/backend/utils/adt/ruleutils.c          |  140 ++++++++++++++++++++++++++++
 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/pg_proc.h              |    2 +
 src/include/utils/builtins.h               |    1 +
 src/test/regress/expected/create_table.out |   26 +++++
 src/test/regress/sql/create_table.sql      |   13 +++
 9 files changed, 260 insertions(+), 26 deletions(-)

diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 8a81d7a..77ce807 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_table.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
@@ -315,6 +316,7 @@ static char *pg_get_indexdef_worker(Oid indexrelid, int colno,
 					   const Oid *excludeOps,
 					   bool attrsOnly, bool showTblSpc,
 					   int prettyFlags, bool missing_ok);
+static char *pg_get_partkeydef_worker(Oid relId, int prettyFlags);
 static char *pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 							int prettyFlags, bool missing_ok);
 static text *pg_get_expr_worker(text *expr, Oid relid, const char *relname,
@@ -1389,6 +1391,144 @@ 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 [ 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_table	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_table) GETSTRUCT(tuple);
+
+	Assert(form->partedrelid == relId);
+
+	/* Must get partclass, and partexprs the hard way */
+	datum = SysCacheGetAttr(PARTEDRELID, tuple,
+							Anum_pg_partitioned_table_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_table_partexprbin))
+	{
+		Datum		exprsDatum;
+		bool		isnull;
+		char	   *exprsString;
+
+		exprsDatum = SysCacheGetAttr(PARTEDRELID, tuple,
+									 Anum_pg_partitioned_table_partexprbin, &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);
+
+	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,
+											0, 0);
+
+			appendStringInfoString(&buf, 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/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index a5c2d09..c418ba7 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -1253,9 +1253,10 @@ expand_table_name_patterns(Archive *fout,
 						  "SELECT c.oid"
 						  "\nFROM pg_catalog.pg_class c"
 		"\n     LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace"
-					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c')\n",
+					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c')\n",
 						  RELKIND_RELATION, RELKIND_SEQUENCE, RELKIND_VIEW,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 		processSQLNamePattern(GetConnection(fout), query, cell->val, true,
 							  false, "n.nspname", "c.relname", NULL,
 							  "pg_catalog.pg_table_is_visible(c.oid)");
@@ -2117,6 +2118,9 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo, bool oids)
 	/* Skip FOREIGN TABLEs (no data to dump) */
 	if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 		return;
+	/* Skip partitioned tables (data in partitions) */
+	if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+		return;
 
 	/* Don't dump data in unlogged tables, if so requested */
 	if (tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED &&
@@ -5195,6 +5199,7 @@ getTables(Archive *fout, int *numTables)
 	int			i_reloftype;
 	int			i_relpages;
 	int			i_changed_acl;
+	int			i_partkeydef;
 
 	/* Make sure we are in proper schema */
 	selectSourceSchema(fout, "pg_catalog");
@@ -5280,7 +5285,8 @@ getTables(Archive *fout, int *numTables)
 						  "OR %s IS NOT NULL "
 						  "OR %s IS NOT NULL"
 						  "))"
-						  "AS changed_acl "
+						  "AS changed_acl, "
+						  "CASE WHEN c.relkind = 'P' THEN pg_catalog.pg_get_partkeydef(c.oid) ELSE NULL END AS partkeydef "
 						  "FROM pg_class c "
 						  "LEFT JOIN pg_depend d ON "
 						  "(c.relkind = '%c' AND "
@@ -5292,7 +5298,7 @@ getTables(Archive *fout, int *numTables)
 						  "(c.oid = pip.objoid "
 						  "AND pip.classoid = 'pg_class'::regclass "
 						  "AND pip.objsubid = 0) "
-				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c') "
+				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c', '%c') "
 						  "ORDER BY c.oid",
 						  acl_subquery->data,
 						  racl_subquery->data,
@@ -5306,7 +5312,8 @@ getTables(Archive *fout, int *numTables)
 						  RELKIND_SEQUENCE,
 						  RELKIND_RELATION, RELKIND_SEQUENCE,
 						  RELKIND_VIEW, RELKIND_COMPOSITE_TYPE,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 
 		destroyPQExpBuffer(acl_subquery);
 		destroyPQExpBuffer(racl_subquery);
@@ -5868,6 +5875,7 @@ getTables(Archive *fout, int *numTables)
 	i_toastreloptions = PQfnumber(res, "toast_reloptions");
 	i_reloftype = PQfnumber(res, "reloftype");
 	i_changed_acl = PQfnumber(res, "changed_acl");
+	i_partkeydef = PQfnumber(res, "partkeydef");
 
 	if (dopt->lockWaitTimeout && fout->remoteVersion >= 70300)
 	{
@@ -5938,6 +5946,7 @@ getTables(Archive *fout, int *numTables)
 		else
 			tblinfo[i].checkoption = pg_strdup(PQgetvalue(res, i, i_checkoption));
 		tblinfo[i].toast_reloptions = pg_strdup(PQgetvalue(res, i, i_toastreloptions));
+		tblinfo[i].partkeydef = pg_strdup(PQgetvalue(res, i, i_partkeydef));
 
 		/* other fields were zeroed above */
 
@@ -5982,7 +5991,9 @@ getTables(Archive *fout, int *numTables)
 		 * We only need to lock the table for certain components; see
 		 * pg_dump.h
 		 */
-		if (tblinfo[i].dobj.dump && tblinfo[i].relkind == RELKIND_RELATION &&
+		if (tblinfo[i].dobj.dump &&
+			(tblinfo[i].relkind == RELKIND_RELATION ||
+			 tblinfo->relkind == RELKIND_PARTITIONED_TABLE) &&
 			(tblinfo[i].dobj.dump & DUMP_COMPONENTS_REQUIRING_LOCK))
 		{
 			resetPQExpBuffer(query);
@@ -6084,7 +6095,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);
 
@@ -15437,6 +15451,9 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 				appendPQExpBufferChar(q, ')');
 			}
 
+			if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+				appendPQExpBuffer(q, "\nPARTITION BY %s", tbinfo->partkeydef);
+
 			if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 				appendPQExpBuffer(q, "\nSERVER %s", fmtId(srvname));
 		}
@@ -15497,6 +15514,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 		 */
 		if (dopt->binary_upgrade &&
 			(tbinfo->relkind == RELKIND_RELATION ||
+			 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 			 tbinfo->relkind == RELKIND_FOREIGN_TABLE))
 		{
 			for (j = 0; j < tbinfo->numatts; j++)
@@ -15515,7 +15533,8 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 					appendStringLiteralAH(q, fmtId(tbinfo->dobj.name), fout);
 					appendPQExpBufferStr(q, "::pg_catalog.regclass;\n");
 
-					if (tbinfo->relkind == RELKIND_RELATION)
+					if (tbinfo->relkind == RELKIND_RELATION ||
+						tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
 						appendPQExpBuffer(q, "ALTER TABLE ONLY %s ",
 										  fmtId(tbinfo->dobj.name));
 					else
@@ -15732,6 +15751,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 	 * dump properties we only have ALTER TABLE syntax for
 	 */
 	if ((tbinfo->relkind == RELKIND_RELATION ||
+		 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 		 tbinfo->relkind == RELKIND_MATVIEW) &&
 		tbinfo->relreplident != REPLICA_IDENTITY_DEFAULT)
 	{
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 2bfa2d9..0292859 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -310,6 +310,7 @@ typedef struct _tableInfo
 	bool	   *inhNotNull;		/* true if NOT NULL is inherited */
 	struct _attrDefInfo **attrdefs;		/* DEFAULT expressions */
 	struct _constraintInfo *checkexprs; /* CHECK constraints */
+	char	   *partkeydef;		/* partition key definition */
 
 	/*
 	 * Stuff computed only for dumpable tables.
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 6275a68..10d924a 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -861,6 +861,7 @@ permissionsList(const char *pattern)
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'S' THEN '%s'"
@@ -870,6 +871,7 @@ permissionsList(const char *pattern)
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("sequence"),
@@ -920,7 +922,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
@@ -1567,8 +1569,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)");
 	}
 
@@ -1633,6 +1635,14 @@ describeOneTableDetails(const char *schemaname,
 			printfPQExpBuffer(&title, _("Foreign table \"%s.%s\""),
 							  schemaname, relationname);
 			break;
+		case 'P':
+			if (tableinfo.relpersistence == 'u')
+				printfPQExpBuffer(&title, _("Unlogged table \"%s.%s\""),
+								  schemaname, relationname);
+			else
+				printfPQExpBuffer(&title, _("Table \"%s.%s\""),
+								  schemaname, relationname);
+			break;
 		default:
 			/* untranslated unknown relkind */
 			printfPQExpBuffer(&title, "?%c? \"%s.%s\"",
@@ -1646,8 +1656,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");
@@ -1667,12 +1677,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");
 	}
 
@@ -1772,7 +1782,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);
@@ -1780,14 +1790,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 (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 */
@@ -1926,7 +1955,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;
@@ -2485,7 +2514,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;
@@ -2696,7 +2725,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
@@ -3024,6 +3053,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'i' THEN '%s'"
@@ -3035,6 +3065,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("index"),
@@ -3079,7 +3110,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 1345e4e..4686cd1 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/pg_proc.h b/src/include/catalog/pg_proc.h
index e2d08ba..b45688b 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -1980,6 +1980,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/utils/builtins.h b/src/include/utils/builtins.h
index 2ae212a..e800647 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -729,6 +729,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/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 708232d..2fec847 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -408,3 +408,29 @@ CREATE TABLE no_inh_con_parted (
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
 ERROR:  cannot add NO INHERIT constraint to partitioned table "no_inh_con_parted"
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+Table "public.describe_range_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition Key: RANGE ((a + b))
+
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+Table "public.describe_list_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition Key: LIST (a)
+
+DROP TABLE describe_range_key, describe_list_key;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 6e4a8be..4dd6a0a 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -402,3 +402,16 @@ CREATE TABLE no_inh_con_parted (
 	a int,
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
+
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+DROP TABLE describe_range_key, describe_list_key;
-- 
1.7.1


--------------954458C17BBEB1BF6FC758E7
Content-Type: text/x-diff;
 name="0003-Catalog-and-DDL-for-partitions-2.patch"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
 filename="0003-Catalog-and-DDL-for-partitions-2.patch"



^ permalink  raw  reply  [nested|flat] 110+ messages in thread

* [PATCH 2/9] psql and pg_dump support for partitioned tables.
@ 2016-07-12 08:20  amit <[email protected]>
  0 siblings, 0 replies; 110+ messages in thread

From: amit @ 2016-07-12 08:20 UTC (permalink / raw)

Takes care of both the partition key deparse stuff and the new relkind.
---
 src/backend/utils/adt/ruleutils.c          |  140 ++++++++++++++++++++++++++++
 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/pg_proc.h              |    2 +
 src/include/utils/builtins.h               |    1 +
 src/test/regress/expected/create_table.out |   26 +++++
 src/test/regress/sql/create_table.sql      |   13 +++
 9 files changed, 260 insertions(+), 26 deletions(-)

diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 8a81d7a..03be202 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_table.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
@@ -315,6 +316,7 @@ static char *pg_get_indexdef_worker(Oid indexrelid, int colno,
 					   const Oid *excludeOps,
 					   bool attrsOnly, bool showTblSpc,
 					   int prettyFlags, bool missing_ok);
+static char *pg_get_partkeydef_worker(Oid relid, int prettyFlags);
 static char *pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 							int prettyFlags, bool missing_ok);
 static text *pg_get_expr_worker(text *expr, Oid relid, const char *relname,
@@ -1389,6 +1391,144 @@ 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 [ opclass_name ] [, ...])
+ */
+Datum
+pg_get_partkeydef(PG_FUNCTION_ARGS)
+{
+	Oid			relid = PG_GETARG_OID(0);
+	int			prettyFlags;
+
+	prettyFlags = PRETTYFLAG_INDENT;
+	PG_RETURN_TEXT_P(string_to_text(pg_get_partkeydef_worker(relid,
+									prettyFlags)));
+}
+
+/*
+ * Internal workhorse to decompile a partition key definition.
+ */
+static char *
+pg_get_partkeydef_worker(Oid relid, int prettyFlags)
+{
+	Form_pg_partitioned_table	form;
+	HeapTuple	tuple;
+	oidvector  *partclass;
+	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_table) GETSTRUCT(tuple);
+
+	Assert(form->partrelid == relid);
+
+	/* Must get partclass, and partexprs the hard way */
+	datum = SysCacheGetAttr(PARTEDRELID, tuple,
+							Anum_pg_partitioned_table_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_table_partexprbin))
+	{
+		Datum		exprsDatum;
+		bool		isnull;
+		char	   *exprsString;
+
+		exprsDatum = SysCacheGetAttr(PARTEDRELID, tuple,
+									 Anum_pg_partitioned_table_partexprbin, &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);
+
+	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,
+											0, 0);
+
+			appendStringInfoString(&buf, 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/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index ba9c276..c805a84 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -1253,9 +1253,10 @@ expand_table_name_patterns(Archive *fout,
 						  "SELECT c.oid"
 						  "\nFROM pg_catalog.pg_class c"
 		"\n     LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace"
-					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c')\n",
+					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c')\n",
 						  RELKIND_RELATION, RELKIND_SEQUENCE, RELKIND_VIEW,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 		processSQLNamePattern(GetConnection(fout), query, cell->val, true,
 							  false, "n.nspname", "c.relname", NULL,
 							  "pg_catalog.pg_table_is_visible(c.oid)");
@@ -2125,6 +2126,9 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo, bool oids)
 	/* Skip FOREIGN TABLEs (no data to dump) */
 	if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 		return;
+	/* Skip partitioned tables (data in partitions) */
+	if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+		return;
 
 	/* Don't dump data in unlogged tables, if so requested */
 	if (tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED &&
@@ -5204,6 +5208,7 @@ getTables(Archive *fout, int *numTables)
 	int			i_reloftype;
 	int			i_relpages;
 	int			i_changed_acl;
+	int			i_partkeydef;
 
 	/* Make sure we are in proper schema */
 	selectSourceSchema(fout, "pg_catalog");
@@ -5289,7 +5294,8 @@ getTables(Archive *fout, int *numTables)
 						  "OR %s IS NOT NULL "
 						  "OR %s IS NOT NULL"
 						  "))"
-						  "AS changed_acl "
+						  "AS changed_acl, "
+						  "CASE WHEN c.relkind = 'P' THEN pg_catalog.pg_get_partkeydef(c.oid) ELSE NULL END AS partkeydef "
 						  "FROM pg_class c "
 						  "LEFT JOIN pg_depend d ON "
 						  "(c.relkind = '%c' AND "
@@ -5301,7 +5307,7 @@ getTables(Archive *fout, int *numTables)
 						  "(c.oid = pip.objoid "
 						  "AND pip.classoid = 'pg_class'::regclass "
 						  "AND pip.objsubid = 0) "
-				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c') "
+				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c', '%c') "
 						  "ORDER BY c.oid",
 						  acl_subquery->data,
 						  racl_subquery->data,
@@ -5315,7 +5321,8 @@ getTables(Archive *fout, int *numTables)
 						  RELKIND_SEQUENCE,
 						  RELKIND_RELATION, RELKIND_SEQUENCE,
 						  RELKIND_VIEW, RELKIND_COMPOSITE_TYPE,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 
 		destroyPQExpBuffer(acl_subquery);
 		destroyPQExpBuffer(racl_subquery);
@@ -5877,6 +5884,7 @@ getTables(Archive *fout, int *numTables)
 	i_toastreloptions = PQfnumber(res, "toast_reloptions");
 	i_reloftype = PQfnumber(res, "reloftype");
 	i_changed_acl = PQfnumber(res, "changed_acl");
+	i_partkeydef = PQfnumber(res, "partkeydef");
 
 	if (dopt->lockWaitTimeout && fout->remoteVersion >= 70300)
 	{
@@ -5947,6 +5955,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 */
 
@@ -5991,7 +6000,9 @@ getTables(Archive *fout, int *numTables)
 		 * We only need to lock the table for certain components; see
 		 * pg_dump.h
 		 */
-		if (tblinfo[i].dobj.dump && tblinfo[i].relkind == RELKIND_RELATION &&
+		if (tblinfo[i].dobj.dump &&
+			(tblinfo[i].relkind == RELKIND_RELATION ||
+			 tblinfo->relkind == RELKIND_PARTITIONED_TABLE) &&
 			(tblinfo[i].dobj.dump & DUMP_COMPONENTS_REQUIRING_LOCK))
 		{
 			resetPQExpBuffer(query);
@@ -6093,7 +6104,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);
 
@@ -15446,6 +15460,9 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 				appendPQExpBufferChar(q, ')');
 			}
 
+			if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+				appendPQExpBuffer(q, "\nPARTITION BY %s", tbinfo->partkeydef);
+
 			if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 				appendPQExpBuffer(q, "\nSERVER %s", fmtId(srvname));
 		}
@@ -15506,6 +15523,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 		 */
 		if (dopt->binary_upgrade &&
 			(tbinfo->relkind == RELKIND_RELATION ||
+			 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 			 tbinfo->relkind == RELKIND_FOREIGN_TABLE))
 		{
 			for (j = 0; j < tbinfo->numatts; j++)
@@ -15524,7 +15542,8 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 					appendStringLiteralAH(q, fmtId(tbinfo->dobj.name), fout);
 					appendPQExpBufferStr(q, "::pg_catalog.regclass;\n");
 
-					if (tbinfo->relkind == RELKIND_RELATION)
+					if (tbinfo->relkind == RELKIND_RELATION ||
+						tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
 						appendPQExpBuffer(q, "ALTER TABLE ONLY %s ",
 										  fmtId(tbinfo->dobj.name));
 					else
@@ -15741,6 +15760,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 	 * dump properties we only have ALTER TABLE syntax for
 	 */
 	if ((tbinfo->relkind == RELKIND_RELATION ||
+		 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 		 tbinfo->relkind == RELKIND_MATVIEW) &&
 		tbinfo->relreplident != REPLICA_IDENTITY_DEFAULT)
 	{
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 2bfa2d9..0292859 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -310,6 +310,7 @@ typedef struct _tableInfo
 	bool	   *inhNotNull;		/* true if NOT NULL is inherited */
 	struct _attrDefInfo **attrdefs;		/* DEFAULT expressions */
 	struct _constraintInfo *checkexprs; /* CHECK constraints */
+	char	   *partkeydef;		/* partition key definition */
 
 	/*
 	 * Stuff computed only for dumpable tables.
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 6275a68..10d924a 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -861,6 +861,7 @@ permissionsList(const char *pattern)
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'S' THEN '%s'"
@@ -870,6 +871,7 @@ permissionsList(const char *pattern)
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("sequence"),
@@ -920,7 +922,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
@@ -1567,8 +1569,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)");
 	}
 
@@ -1633,6 +1635,14 @@ describeOneTableDetails(const char *schemaname,
 			printfPQExpBuffer(&title, _("Foreign table \"%s.%s\""),
 							  schemaname, relationname);
 			break;
+		case 'P':
+			if (tableinfo.relpersistence == 'u')
+				printfPQExpBuffer(&title, _("Unlogged table \"%s.%s\""),
+								  schemaname, relationname);
+			else
+				printfPQExpBuffer(&title, _("Table \"%s.%s\""),
+								  schemaname, relationname);
+			break;
 		default:
 			/* untranslated unknown relkind */
 			printfPQExpBuffer(&title, "?%c? \"%s.%s\"",
@@ -1646,8 +1656,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");
@@ -1667,12 +1677,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");
 	}
 
@@ -1772,7 +1782,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);
@@ -1780,14 +1790,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 (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 */
@@ -1926,7 +1955,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;
@@ -2485,7 +2514,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;
@@ -2696,7 +2725,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
@@ -3024,6 +3053,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'i' THEN '%s'"
@@ -3035,6 +3065,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("index"),
@@ -3079,7 +3110,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 019f75a..5e5c370 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/pg_proc.h b/src/include/catalog/pg_proc.h
index e2d08ba..b45688b 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -1980,6 +1980,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/utils/builtins.h b/src/include/utils/builtins.h
index 2ae212a..e800647 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -729,6 +729,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/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 708232d..2fec847 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -408,3 +408,29 @@ CREATE TABLE no_inh_con_parted (
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
 ERROR:  cannot add NO INHERIT constraint to partitioned table "no_inh_con_parted"
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+Table "public.describe_range_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition Key: RANGE ((a + b))
+
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+Table "public.describe_list_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition Key: LIST (a)
+
+DROP TABLE describe_range_key, describe_list_key;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 6e4a8be..4dd6a0a 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -402,3 +402,16 @@ CREATE TABLE no_inh_con_parted (
 	a int,
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
+
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+DROP TABLE describe_range_key, describe_list_key;
-- 
1.7.1


--------------4012470FC48539D6F6714CEA
Content-Type: text/x-diff;
 name="0003-Catalog-and-DDL-for-partitions-4.patch"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
 filename="0003-Catalog-and-DDL-for-partitions-4.patch"



^ permalink  raw  reply  [nested|flat] 110+ messages in thread

* [PATCH 2/9] psql and pg_dump support for partitioned tables.
@ 2016-07-12 08:20  amit <[email protected]>
  0 siblings, 0 replies; 110+ messages in thread

From: amit @ 2016-07-12 08:20 UTC (permalink / raw)

Takes care of both the partition key deparse stuff and the new relkind.
---
 src/backend/utils/adt/ruleutils.c          |  140 ++++++++++++++++++++++++++++
 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/pg_proc.h              |    2 +
 src/include/utils/builtins.h               |    1 +
 src/test/regress/expected/create_table.out |   26 +++++
 src/test/regress/sql/create_table.sql      |   13 +++
 9 files changed, 260 insertions(+), 26 deletions(-)

diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 8a81d7a..77ce807 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_table.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
@@ -315,6 +316,7 @@ static char *pg_get_indexdef_worker(Oid indexrelid, int colno,
 					   const Oid *excludeOps,
 					   bool attrsOnly, bool showTblSpc,
 					   int prettyFlags, bool missing_ok);
+static char *pg_get_partkeydef_worker(Oid relId, int prettyFlags);
 static char *pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 							int prettyFlags, bool missing_ok);
 static text *pg_get_expr_worker(text *expr, Oid relid, const char *relname,
@@ -1389,6 +1391,144 @@ 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 [ 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_table	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_table) GETSTRUCT(tuple);
+
+	Assert(form->partedrelid == relId);
+
+	/* Must get partclass, and partexprs the hard way */
+	datum = SysCacheGetAttr(PARTEDRELID, tuple,
+							Anum_pg_partitioned_table_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_table_partexprbin))
+	{
+		Datum		exprsDatum;
+		bool		isnull;
+		char	   *exprsString;
+
+		exprsDatum = SysCacheGetAttr(PARTEDRELID, tuple,
+									 Anum_pg_partitioned_table_partexprbin, &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);
+
+	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,
+											0, 0);
+
+			appendStringInfoString(&buf, 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/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index a5c2d09..c418ba7 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -1253,9 +1253,10 @@ expand_table_name_patterns(Archive *fout,
 						  "SELECT c.oid"
 						  "\nFROM pg_catalog.pg_class c"
 		"\n     LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace"
-					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c')\n",
+					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c')\n",
 						  RELKIND_RELATION, RELKIND_SEQUENCE, RELKIND_VIEW,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 		processSQLNamePattern(GetConnection(fout), query, cell->val, true,
 							  false, "n.nspname", "c.relname", NULL,
 							  "pg_catalog.pg_table_is_visible(c.oid)");
@@ -2117,6 +2118,9 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo, bool oids)
 	/* Skip FOREIGN TABLEs (no data to dump) */
 	if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 		return;
+	/* Skip partitioned tables (data in partitions) */
+	if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+		return;
 
 	/* Don't dump data in unlogged tables, if so requested */
 	if (tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED &&
@@ -5195,6 +5199,7 @@ getTables(Archive *fout, int *numTables)
 	int			i_reloftype;
 	int			i_relpages;
 	int			i_changed_acl;
+	int			i_partkeydef;
 
 	/* Make sure we are in proper schema */
 	selectSourceSchema(fout, "pg_catalog");
@@ -5280,7 +5285,8 @@ getTables(Archive *fout, int *numTables)
 						  "OR %s IS NOT NULL "
 						  "OR %s IS NOT NULL"
 						  "))"
-						  "AS changed_acl "
+						  "AS changed_acl, "
+						  "CASE WHEN c.relkind = 'P' THEN pg_catalog.pg_get_partkeydef(c.oid) ELSE NULL END AS partkeydef "
 						  "FROM pg_class c "
 						  "LEFT JOIN pg_depend d ON "
 						  "(c.relkind = '%c' AND "
@@ -5292,7 +5298,7 @@ getTables(Archive *fout, int *numTables)
 						  "(c.oid = pip.objoid "
 						  "AND pip.classoid = 'pg_class'::regclass "
 						  "AND pip.objsubid = 0) "
-				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c') "
+				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c', '%c') "
 						  "ORDER BY c.oid",
 						  acl_subquery->data,
 						  racl_subquery->data,
@@ -5306,7 +5312,8 @@ getTables(Archive *fout, int *numTables)
 						  RELKIND_SEQUENCE,
 						  RELKIND_RELATION, RELKIND_SEQUENCE,
 						  RELKIND_VIEW, RELKIND_COMPOSITE_TYPE,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 
 		destroyPQExpBuffer(acl_subquery);
 		destroyPQExpBuffer(racl_subquery);
@@ -5868,6 +5875,7 @@ getTables(Archive *fout, int *numTables)
 	i_toastreloptions = PQfnumber(res, "toast_reloptions");
 	i_reloftype = PQfnumber(res, "reloftype");
 	i_changed_acl = PQfnumber(res, "changed_acl");
+	i_partkeydef = PQfnumber(res, "partkeydef");
 
 	if (dopt->lockWaitTimeout && fout->remoteVersion >= 70300)
 	{
@@ -5938,6 +5946,7 @@ getTables(Archive *fout, int *numTables)
 		else
 			tblinfo[i].checkoption = pg_strdup(PQgetvalue(res, i, i_checkoption));
 		tblinfo[i].toast_reloptions = pg_strdup(PQgetvalue(res, i, i_toastreloptions));
+		tblinfo[i].partkeydef = pg_strdup(PQgetvalue(res, i, i_partkeydef));
 
 		/* other fields were zeroed above */
 
@@ -5982,7 +5991,9 @@ getTables(Archive *fout, int *numTables)
 		 * We only need to lock the table for certain components; see
 		 * pg_dump.h
 		 */
-		if (tblinfo[i].dobj.dump && tblinfo[i].relkind == RELKIND_RELATION &&
+		if (tblinfo[i].dobj.dump &&
+			(tblinfo[i].relkind == RELKIND_RELATION ||
+			 tblinfo->relkind == RELKIND_PARTITIONED_TABLE) &&
 			(tblinfo[i].dobj.dump & DUMP_COMPONENTS_REQUIRING_LOCK))
 		{
 			resetPQExpBuffer(query);
@@ -6084,7 +6095,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);
 
@@ -15437,6 +15451,9 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 				appendPQExpBufferChar(q, ')');
 			}
 
+			if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+				appendPQExpBuffer(q, "\nPARTITION BY %s", tbinfo->partkeydef);
+
 			if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 				appendPQExpBuffer(q, "\nSERVER %s", fmtId(srvname));
 		}
@@ -15497,6 +15514,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 		 */
 		if (dopt->binary_upgrade &&
 			(tbinfo->relkind == RELKIND_RELATION ||
+			 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 			 tbinfo->relkind == RELKIND_FOREIGN_TABLE))
 		{
 			for (j = 0; j < tbinfo->numatts; j++)
@@ -15515,7 +15533,8 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 					appendStringLiteralAH(q, fmtId(tbinfo->dobj.name), fout);
 					appendPQExpBufferStr(q, "::pg_catalog.regclass;\n");
 
-					if (tbinfo->relkind == RELKIND_RELATION)
+					if (tbinfo->relkind == RELKIND_RELATION ||
+						tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
 						appendPQExpBuffer(q, "ALTER TABLE ONLY %s ",
 										  fmtId(tbinfo->dobj.name));
 					else
@@ -15732,6 +15751,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 	 * dump properties we only have ALTER TABLE syntax for
 	 */
 	if ((tbinfo->relkind == RELKIND_RELATION ||
+		 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 		 tbinfo->relkind == RELKIND_MATVIEW) &&
 		tbinfo->relreplident != REPLICA_IDENTITY_DEFAULT)
 	{
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 2bfa2d9..0292859 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -310,6 +310,7 @@ typedef struct _tableInfo
 	bool	   *inhNotNull;		/* true if NOT NULL is inherited */
 	struct _attrDefInfo **attrdefs;		/* DEFAULT expressions */
 	struct _constraintInfo *checkexprs; /* CHECK constraints */
+	char	   *partkeydef;		/* partition key definition */
 
 	/*
 	 * Stuff computed only for dumpable tables.
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 6275a68..10d924a 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -861,6 +861,7 @@ permissionsList(const char *pattern)
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'S' THEN '%s'"
@@ -870,6 +871,7 @@ permissionsList(const char *pattern)
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("sequence"),
@@ -920,7 +922,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
@@ -1567,8 +1569,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)");
 	}
 
@@ -1633,6 +1635,14 @@ describeOneTableDetails(const char *schemaname,
 			printfPQExpBuffer(&title, _("Foreign table \"%s.%s\""),
 							  schemaname, relationname);
 			break;
+		case 'P':
+			if (tableinfo.relpersistence == 'u')
+				printfPQExpBuffer(&title, _("Unlogged table \"%s.%s\""),
+								  schemaname, relationname);
+			else
+				printfPQExpBuffer(&title, _("Table \"%s.%s\""),
+								  schemaname, relationname);
+			break;
 		default:
 			/* untranslated unknown relkind */
 			printfPQExpBuffer(&title, "?%c? \"%s.%s\"",
@@ -1646,8 +1656,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");
@@ -1667,12 +1677,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");
 	}
 
@@ -1772,7 +1782,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);
@@ -1780,14 +1790,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 (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 */
@@ -1926,7 +1955,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;
@@ -2485,7 +2514,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;
@@ -2696,7 +2725,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
@@ -3024,6 +3053,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'i' THEN '%s'"
@@ -3035,6 +3065,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("index"),
@@ -3079,7 +3110,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 1345e4e..4686cd1 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/pg_proc.h b/src/include/catalog/pg_proc.h
index e2d08ba..b45688b 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -1980,6 +1980,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/utils/builtins.h b/src/include/utils/builtins.h
index 2ae212a..e800647 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -729,6 +729,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/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 708232d..2fec847 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -408,3 +408,29 @@ CREATE TABLE no_inh_con_parted (
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
 ERROR:  cannot add NO INHERIT constraint to partitioned table "no_inh_con_parted"
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+Table "public.describe_range_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition Key: RANGE ((a + b))
+
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+Table "public.describe_list_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition Key: LIST (a)
+
+DROP TABLE describe_range_key, describe_list_key;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 6e4a8be..4dd6a0a 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -402,3 +402,16 @@ CREATE TABLE no_inh_con_parted (
 	a int,
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
+
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+DROP TABLE describe_range_key, describe_list_key;
-- 
1.7.1


--------------954458C17BBEB1BF6FC758E7
Content-Type: text/x-diff;
 name="0003-Catalog-and-DDL-for-partitions-2.patch"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
 filename="0003-Catalog-and-DDL-for-partitions-2.patch"



^ permalink  raw  reply  [nested|flat] 110+ messages in thread

* [PATCH 2/9] psql and pg_dump support for partitioned tables.
@ 2016-07-12 08:20  amit <[email protected]>
  0 siblings, 0 replies; 110+ messages in thread

From: amit @ 2016-07-12 08:20 UTC (permalink / raw)

Takes care of both the partition key deparse stuff and the new relkind.
---
 src/backend/utils/adt/ruleutils.c          |  140 ++++++++++++++++++++++++++++
 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/pg_proc.h              |    2 +
 src/include/utils/builtins.h               |    1 +
 src/test/regress/expected/create_table.out |   26 +++++
 src/test/regress/sql/create_table.sql      |   13 +++
 9 files changed, 260 insertions(+), 26 deletions(-)

diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 8a81d7a..77ce807 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_table.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
@@ -315,6 +316,7 @@ static char *pg_get_indexdef_worker(Oid indexrelid, int colno,
 					   const Oid *excludeOps,
 					   bool attrsOnly, bool showTblSpc,
 					   int prettyFlags, bool missing_ok);
+static char *pg_get_partkeydef_worker(Oid relId, int prettyFlags);
 static char *pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 							int prettyFlags, bool missing_ok);
 static text *pg_get_expr_worker(text *expr, Oid relid, const char *relname,
@@ -1389,6 +1391,144 @@ 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 [ 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_table	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_table) GETSTRUCT(tuple);
+
+	Assert(form->partedrelid == relId);
+
+	/* Must get partclass, and partexprs the hard way */
+	datum = SysCacheGetAttr(PARTEDRELID, tuple,
+							Anum_pg_partitioned_table_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_table_partexprbin))
+	{
+		Datum		exprsDatum;
+		bool		isnull;
+		char	   *exprsString;
+
+		exprsDatum = SysCacheGetAttr(PARTEDRELID, tuple,
+									 Anum_pg_partitioned_table_partexprbin, &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);
+
+	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,
+											0, 0);
+
+			appendStringInfoString(&buf, 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/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index a5c2d09..c418ba7 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -1253,9 +1253,10 @@ expand_table_name_patterns(Archive *fout,
 						  "SELECT c.oid"
 						  "\nFROM pg_catalog.pg_class c"
 		"\n     LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace"
-					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c')\n",
+					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c')\n",
 						  RELKIND_RELATION, RELKIND_SEQUENCE, RELKIND_VIEW,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 		processSQLNamePattern(GetConnection(fout), query, cell->val, true,
 							  false, "n.nspname", "c.relname", NULL,
 							  "pg_catalog.pg_table_is_visible(c.oid)");
@@ -2117,6 +2118,9 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo, bool oids)
 	/* Skip FOREIGN TABLEs (no data to dump) */
 	if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 		return;
+	/* Skip partitioned tables (data in partitions) */
+	if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+		return;
 
 	/* Don't dump data in unlogged tables, if so requested */
 	if (tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED &&
@@ -5195,6 +5199,7 @@ getTables(Archive *fout, int *numTables)
 	int			i_reloftype;
 	int			i_relpages;
 	int			i_changed_acl;
+	int			i_partkeydef;
 
 	/* Make sure we are in proper schema */
 	selectSourceSchema(fout, "pg_catalog");
@@ -5280,7 +5285,8 @@ getTables(Archive *fout, int *numTables)
 						  "OR %s IS NOT NULL "
 						  "OR %s IS NOT NULL"
 						  "))"
-						  "AS changed_acl "
+						  "AS changed_acl, "
+						  "CASE WHEN c.relkind = 'P' THEN pg_catalog.pg_get_partkeydef(c.oid) ELSE NULL END AS partkeydef "
 						  "FROM pg_class c "
 						  "LEFT JOIN pg_depend d ON "
 						  "(c.relkind = '%c' AND "
@@ -5292,7 +5298,7 @@ getTables(Archive *fout, int *numTables)
 						  "(c.oid = pip.objoid "
 						  "AND pip.classoid = 'pg_class'::regclass "
 						  "AND pip.objsubid = 0) "
-				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c') "
+				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c', '%c') "
 						  "ORDER BY c.oid",
 						  acl_subquery->data,
 						  racl_subquery->data,
@@ -5306,7 +5312,8 @@ getTables(Archive *fout, int *numTables)
 						  RELKIND_SEQUENCE,
 						  RELKIND_RELATION, RELKIND_SEQUENCE,
 						  RELKIND_VIEW, RELKIND_COMPOSITE_TYPE,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 
 		destroyPQExpBuffer(acl_subquery);
 		destroyPQExpBuffer(racl_subquery);
@@ -5868,6 +5875,7 @@ getTables(Archive *fout, int *numTables)
 	i_toastreloptions = PQfnumber(res, "toast_reloptions");
 	i_reloftype = PQfnumber(res, "reloftype");
 	i_changed_acl = PQfnumber(res, "changed_acl");
+	i_partkeydef = PQfnumber(res, "partkeydef");
 
 	if (dopt->lockWaitTimeout && fout->remoteVersion >= 70300)
 	{
@@ -5938,6 +5946,7 @@ getTables(Archive *fout, int *numTables)
 		else
 			tblinfo[i].checkoption = pg_strdup(PQgetvalue(res, i, i_checkoption));
 		tblinfo[i].toast_reloptions = pg_strdup(PQgetvalue(res, i, i_toastreloptions));
+		tblinfo[i].partkeydef = pg_strdup(PQgetvalue(res, i, i_partkeydef));
 
 		/* other fields were zeroed above */
 
@@ -5982,7 +5991,9 @@ getTables(Archive *fout, int *numTables)
 		 * We only need to lock the table for certain components; see
 		 * pg_dump.h
 		 */
-		if (tblinfo[i].dobj.dump && tblinfo[i].relkind == RELKIND_RELATION &&
+		if (tblinfo[i].dobj.dump &&
+			(tblinfo[i].relkind == RELKIND_RELATION ||
+			 tblinfo->relkind == RELKIND_PARTITIONED_TABLE) &&
 			(tblinfo[i].dobj.dump & DUMP_COMPONENTS_REQUIRING_LOCK))
 		{
 			resetPQExpBuffer(query);
@@ -6084,7 +6095,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);
 
@@ -15437,6 +15451,9 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 				appendPQExpBufferChar(q, ')');
 			}
 
+			if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+				appendPQExpBuffer(q, "\nPARTITION BY %s", tbinfo->partkeydef);
+
 			if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 				appendPQExpBuffer(q, "\nSERVER %s", fmtId(srvname));
 		}
@@ -15497,6 +15514,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 		 */
 		if (dopt->binary_upgrade &&
 			(tbinfo->relkind == RELKIND_RELATION ||
+			 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 			 tbinfo->relkind == RELKIND_FOREIGN_TABLE))
 		{
 			for (j = 0; j < tbinfo->numatts; j++)
@@ -15515,7 +15533,8 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 					appendStringLiteralAH(q, fmtId(tbinfo->dobj.name), fout);
 					appendPQExpBufferStr(q, "::pg_catalog.regclass;\n");
 
-					if (tbinfo->relkind == RELKIND_RELATION)
+					if (tbinfo->relkind == RELKIND_RELATION ||
+						tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
 						appendPQExpBuffer(q, "ALTER TABLE ONLY %s ",
 										  fmtId(tbinfo->dobj.name));
 					else
@@ -15732,6 +15751,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 	 * dump properties we only have ALTER TABLE syntax for
 	 */
 	if ((tbinfo->relkind == RELKIND_RELATION ||
+		 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 		 tbinfo->relkind == RELKIND_MATVIEW) &&
 		tbinfo->relreplident != REPLICA_IDENTITY_DEFAULT)
 	{
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 2bfa2d9..0292859 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -310,6 +310,7 @@ typedef struct _tableInfo
 	bool	   *inhNotNull;		/* true if NOT NULL is inherited */
 	struct _attrDefInfo **attrdefs;		/* DEFAULT expressions */
 	struct _constraintInfo *checkexprs; /* CHECK constraints */
+	char	   *partkeydef;		/* partition key definition */
 
 	/*
 	 * Stuff computed only for dumpable tables.
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 6275a68..10d924a 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -861,6 +861,7 @@ permissionsList(const char *pattern)
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'S' THEN '%s'"
@@ -870,6 +871,7 @@ permissionsList(const char *pattern)
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("sequence"),
@@ -920,7 +922,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
@@ -1567,8 +1569,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)");
 	}
 
@@ -1633,6 +1635,14 @@ describeOneTableDetails(const char *schemaname,
 			printfPQExpBuffer(&title, _("Foreign table \"%s.%s\""),
 							  schemaname, relationname);
 			break;
+		case 'P':
+			if (tableinfo.relpersistence == 'u')
+				printfPQExpBuffer(&title, _("Unlogged table \"%s.%s\""),
+								  schemaname, relationname);
+			else
+				printfPQExpBuffer(&title, _("Table \"%s.%s\""),
+								  schemaname, relationname);
+			break;
 		default:
 			/* untranslated unknown relkind */
 			printfPQExpBuffer(&title, "?%c? \"%s.%s\"",
@@ -1646,8 +1656,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");
@@ -1667,12 +1677,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");
 	}
 
@@ -1772,7 +1782,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);
@@ -1780,14 +1790,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 (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 */
@@ -1926,7 +1955,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;
@@ -2485,7 +2514,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;
@@ -2696,7 +2725,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
@@ -3024,6 +3053,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'i' THEN '%s'"
@@ -3035,6 +3065,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("index"),
@@ -3079,7 +3110,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 019f75a..5e5c370 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/pg_proc.h b/src/include/catalog/pg_proc.h
index e2d08ba..b45688b 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -1980,6 +1980,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/utils/builtins.h b/src/include/utils/builtins.h
index 2ae212a..e800647 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -729,6 +729,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/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 708232d..2fec847 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -408,3 +408,29 @@ CREATE TABLE no_inh_con_parted (
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
 ERROR:  cannot add NO INHERIT constraint to partitioned table "no_inh_con_parted"
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+Table "public.describe_range_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition Key: RANGE ((a + b))
+
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+Table "public.describe_list_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition Key: LIST (a)
+
+DROP TABLE describe_range_key, describe_list_key;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 6e4a8be..4dd6a0a 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -402,3 +402,16 @@ CREATE TABLE no_inh_con_parted (
 	a int,
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
+
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+DROP TABLE describe_range_key, describe_list_key;
-- 
1.7.1


--------------6EB5B318125CB7DB1A4FD229
Content-Type: text/x-diff;
 name="0003-Catalog-and-DDL-for-partitions-3.patch"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
 filename="0003-Catalog-and-DDL-for-partitions-3.patch"



^ permalink  raw  reply  [nested|flat] 110+ messages in thread

* [PATCH 2/9] psql and pg_dump support for partitioned tables.
@ 2016-07-12 08:20  amit <[email protected]>
  0 siblings, 0 replies; 110+ messages in thread

From: amit @ 2016-07-12 08:20 UTC (permalink / raw)

Takes care of both the partition key deparse stuff and the new relkind.
---
 src/backend/utils/adt/ruleutils.c          |  140 ++++++++++++++++++++++++++++
 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/pg_proc.h              |    2 +
 src/include/utils/builtins.h               |    1 +
 src/test/regress/expected/create_table.out |   26 +++++
 src/test/regress/sql/create_table.sql      |   13 +++
 9 files changed, 260 insertions(+), 26 deletions(-)

diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 8a81d7a..03be202 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_table.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
@@ -315,6 +316,7 @@ static char *pg_get_indexdef_worker(Oid indexrelid, int colno,
 					   const Oid *excludeOps,
 					   bool attrsOnly, bool showTblSpc,
 					   int prettyFlags, bool missing_ok);
+static char *pg_get_partkeydef_worker(Oid relid, int prettyFlags);
 static char *pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 							int prettyFlags, bool missing_ok);
 static text *pg_get_expr_worker(text *expr, Oid relid, const char *relname,
@@ -1389,6 +1391,144 @@ 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 [ opclass_name ] [, ...])
+ */
+Datum
+pg_get_partkeydef(PG_FUNCTION_ARGS)
+{
+	Oid			relid = PG_GETARG_OID(0);
+	int			prettyFlags;
+
+	prettyFlags = PRETTYFLAG_INDENT;
+	PG_RETURN_TEXT_P(string_to_text(pg_get_partkeydef_worker(relid,
+									prettyFlags)));
+}
+
+/*
+ * Internal workhorse to decompile a partition key definition.
+ */
+static char *
+pg_get_partkeydef_worker(Oid relid, int prettyFlags)
+{
+	Form_pg_partitioned_table	form;
+	HeapTuple	tuple;
+	oidvector  *partclass;
+	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_table) GETSTRUCT(tuple);
+
+	Assert(form->partrelid == relid);
+
+	/* Must get partclass, and partexprs the hard way */
+	datum = SysCacheGetAttr(PARTEDRELID, tuple,
+							Anum_pg_partitioned_table_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_table_partexprbin))
+	{
+		Datum		exprsDatum;
+		bool		isnull;
+		char	   *exprsString;
+
+		exprsDatum = SysCacheGetAttr(PARTEDRELID, tuple,
+									 Anum_pg_partitioned_table_partexprbin, &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);
+
+	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,
+											0, 0);
+
+			appendStringInfoString(&buf, 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/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index ba9c276..c805a84 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -1253,9 +1253,10 @@ expand_table_name_patterns(Archive *fout,
 						  "SELECT c.oid"
 						  "\nFROM pg_catalog.pg_class c"
 		"\n     LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace"
-					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c')\n",
+					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c')\n",
 						  RELKIND_RELATION, RELKIND_SEQUENCE, RELKIND_VIEW,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 		processSQLNamePattern(GetConnection(fout), query, cell->val, true,
 							  false, "n.nspname", "c.relname", NULL,
 							  "pg_catalog.pg_table_is_visible(c.oid)");
@@ -2125,6 +2126,9 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo, bool oids)
 	/* Skip FOREIGN TABLEs (no data to dump) */
 	if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 		return;
+	/* Skip partitioned tables (data in partitions) */
+	if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+		return;
 
 	/* Don't dump data in unlogged tables, if so requested */
 	if (tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED &&
@@ -5204,6 +5208,7 @@ getTables(Archive *fout, int *numTables)
 	int			i_reloftype;
 	int			i_relpages;
 	int			i_changed_acl;
+	int			i_partkeydef;
 
 	/* Make sure we are in proper schema */
 	selectSourceSchema(fout, "pg_catalog");
@@ -5289,7 +5294,8 @@ getTables(Archive *fout, int *numTables)
 						  "OR %s IS NOT NULL "
 						  "OR %s IS NOT NULL"
 						  "))"
-						  "AS changed_acl "
+						  "AS changed_acl, "
+						  "CASE WHEN c.relkind = 'P' THEN pg_catalog.pg_get_partkeydef(c.oid) ELSE NULL END AS partkeydef "
 						  "FROM pg_class c "
 						  "LEFT JOIN pg_depend d ON "
 						  "(c.relkind = '%c' AND "
@@ -5301,7 +5307,7 @@ getTables(Archive *fout, int *numTables)
 						  "(c.oid = pip.objoid "
 						  "AND pip.classoid = 'pg_class'::regclass "
 						  "AND pip.objsubid = 0) "
-				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c') "
+				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c', '%c') "
 						  "ORDER BY c.oid",
 						  acl_subquery->data,
 						  racl_subquery->data,
@@ -5315,7 +5321,8 @@ getTables(Archive *fout, int *numTables)
 						  RELKIND_SEQUENCE,
 						  RELKIND_RELATION, RELKIND_SEQUENCE,
 						  RELKIND_VIEW, RELKIND_COMPOSITE_TYPE,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 
 		destroyPQExpBuffer(acl_subquery);
 		destroyPQExpBuffer(racl_subquery);
@@ -5877,6 +5884,7 @@ getTables(Archive *fout, int *numTables)
 	i_toastreloptions = PQfnumber(res, "toast_reloptions");
 	i_reloftype = PQfnumber(res, "reloftype");
 	i_changed_acl = PQfnumber(res, "changed_acl");
+	i_partkeydef = PQfnumber(res, "partkeydef");
 
 	if (dopt->lockWaitTimeout && fout->remoteVersion >= 70300)
 	{
@@ -5947,6 +5955,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 */
 
@@ -5991,7 +6000,9 @@ getTables(Archive *fout, int *numTables)
 		 * We only need to lock the table for certain components; see
 		 * pg_dump.h
 		 */
-		if (tblinfo[i].dobj.dump && tblinfo[i].relkind == RELKIND_RELATION &&
+		if (tblinfo[i].dobj.dump &&
+			(tblinfo[i].relkind == RELKIND_RELATION ||
+			 tblinfo->relkind == RELKIND_PARTITIONED_TABLE) &&
 			(tblinfo[i].dobj.dump & DUMP_COMPONENTS_REQUIRING_LOCK))
 		{
 			resetPQExpBuffer(query);
@@ -6093,7 +6104,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);
 
@@ -15446,6 +15460,9 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 				appendPQExpBufferChar(q, ')');
 			}
 
+			if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+				appendPQExpBuffer(q, "\nPARTITION BY %s", tbinfo->partkeydef);
+
 			if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 				appendPQExpBuffer(q, "\nSERVER %s", fmtId(srvname));
 		}
@@ -15506,6 +15523,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 		 */
 		if (dopt->binary_upgrade &&
 			(tbinfo->relkind == RELKIND_RELATION ||
+			 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 			 tbinfo->relkind == RELKIND_FOREIGN_TABLE))
 		{
 			for (j = 0; j < tbinfo->numatts; j++)
@@ -15524,7 +15542,8 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 					appendStringLiteralAH(q, fmtId(tbinfo->dobj.name), fout);
 					appendPQExpBufferStr(q, "::pg_catalog.regclass;\n");
 
-					if (tbinfo->relkind == RELKIND_RELATION)
+					if (tbinfo->relkind == RELKIND_RELATION ||
+						tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
 						appendPQExpBuffer(q, "ALTER TABLE ONLY %s ",
 										  fmtId(tbinfo->dobj.name));
 					else
@@ -15741,6 +15760,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 	 * dump properties we only have ALTER TABLE syntax for
 	 */
 	if ((tbinfo->relkind == RELKIND_RELATION ||
+		 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 		 tbinfo->relkind == RELKIND_MATVIEW) &&
 		tbinfo->relreplident != REPLICA_IDENTITY_DEFAULT)
 	{
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 2bfa2d9..0292859 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -310,6 +310,7 @@ typedef struct _tableInfo
 	bool	   *inhNotNull;		/* true if NOT NULL is inherited */
 	struct _attrDefInfo **attrdefs;		/* DEFAULT expressions */
 	struct _constraintInfo *checkexprs; /* CHECK constraints */
+	char	   *partkeydef;		/* partition key definition */
 
 	/*
 	 * Stuff computed only for dumpable tables.
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 6275a68..10d924a 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -861,6 +861,7 @@ permissionsList(const char *pattern)
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'S' THEN '%s'"
@@ -870,6 +871,7 @@ permissionsList(const char *pattern)
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("sequence"),
@@ -920,7 +922,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
@@ -1567,8 +1569,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)");
 	}
 
@@ -1633,6 +1635,14 @@ describeOneTableDetails(const char *schemaname,
 			printfPQExpBuffer(&title, _("Foreign table \"%s.%s\""),
 							  schemaname, relationname);
 			break;
+		case 'P':
+			if (tableinfo.relpersistence == 'u')
+				printfPQExpBuffer(&title, _("Unlogged table \"%s.%s\""),
+								  schemaname, relationname);
+			else
+				printfPQExpBuffer(&title, _("Table \"%s.%s\""),
+								  schemaname, relationname);
+			break;
 		default:
 			/* untranslated unknown relkind */
 			printfPQExpBuffer(&title, "?%c? \"%s.%s\"",
@@ -1646,8 +1656,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");
@@ -1667,12 +1677,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");
 	}
 
@@ -1772,7 +1782,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);
@@ -1780,14 +1790,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 (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 */
@@ -1926,7 +1955,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;
@@ -2485,7 +2514,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;
@@ -2696,7 +2725,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
@@ -3024,6 +3053,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'i' THEN '%s'"
@@ -3035,6 +3065,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("index"),
@@ -3079,7 +3110,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 019f75a..5e5c370 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/pg_proc.h b/src/include/catalog/pg_proc.h
index e2d08ba..b45688b 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -1980,6 +1980,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/utils/builtins.h b/src/include/utils/builtins.h
index 2ae212a..e800647 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -729,6 +729,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/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 708232d..2fec847 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -408,3 +408,29 @@ CREATE TABLE no_inh_con_parted (
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
 ERROR:  cannot add NO INHERIT constraint to partitioned table "no_inh_con_parted"
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+Table "public.describe_range_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition Key: RANGE ((a + b))
+
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+Table "public.describe_list_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition Key: LIST (a)
+
+DROP TABLE describe_range_key, describe_list_key;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 6e4a8be..4dd6a0a 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -402,3 +402,16 @@ CREATE TABLE no_inh_con_parted (
 	a int,
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
+
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+DROP TABLE describe_range_key, describe_list_key;
-- 
1.7.1


--------------4012470FC48539D6F6714CEA
Content-Type: text/x-diff;
 name="0003-Catalog-and-DDL-for-partitions-4.patch"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
 filename="0003-Catalog-and-DDL-for-partitions-4.patch"



^ permalink  raw  reply  [nested|flat] 110+ messages in thread

* [PATCH 2/9] psql and pg_dump support for partitioned tables.
@ 2016-07-12 08:20  amit <[email protected]>
  0 siblings, 0 replies; 110+ messages in thread

From: amit @ 2016-07-12 08:20 UTC (permalink / raw)

Takes care of both the partition key deparse stuff and the new relkind.
---
 src/backend/utils/adt/ruleutils.c          |  140 ++++++++++++++++++++++++++++
 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/pg_proc.h              |    2 +
 src/include/utils/builtins.h               |    1 +
 src/test/regress/expected/create_table.out |   26 +++++
 src/test/regress/sql/create_table.sql      |   13 +++
 9 files changed, 260 insertions(+), 26 deletions(-)

diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 8a81d7a..03be202 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_table.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
@@ -315,6 +316,7 @@ static char *pg_get_indexdef_worker(Oid indexrelid, int colno,
 					   const Oid *excludeOps,
 					   bool attrsOnly, bool showTblSpc,
 					   int prettyFlags, bool missing_ok);
+static char *pg_get_partkeydef_worker(Oid relid, int prettyFlags);
 static char *pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 							int prettyFlags, bool missing_ok);
 static text *pg_get_expr_worker(text *expr, Oid relid, const char *relname,
@@ -1389,6 +1391,144 @@ 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 [ opclass_name ] [, ...])
+ */
+Datum
+pg_get_partkeydef(PG_FUNCTION_ARGS)
+{
+	Oid			relid = PG_GETARG_OID(0);
+	int			prettyFlags;
+
+	prettyFlags = PRETTYFLAG_INDENT;
+	PG_RETURN_TEXT_P(string_to_text(pg_get_partkeydef_worker(relid,
+									prettyFlags)));
+}
+
+/*
+ * Internal workhorse to decompile a partition key definition.
+ */
+static char *
+pg_get_partkeydef_worker(Oid relid, int prettyFlags)
+{
+	Form_pg_partitioned_table	form;
+	HeapTuple	tuple;
+	oidvector  *partclass;
+	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_table) GETSTRUCT(tuple);
+
+	Assert(form->partrelid == relid);
+
+	/* Must get partclass, and partexprs the hard way */
+	datum = SysCacheGetAttr(PARTEDRELID, tuple,
+							Anum_pg_partitioned_table_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_table_partexprbin))
+	{
+		Datum		exprsDatum;
+		bool		isnull;
+		char	   *exprsString;
+
+		exprsDatum = SysCacheGetAttr(PARTEDRELID, tuple,
+									 Anum_pg_partitioned_table_partexprbin, &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);
+
+	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,
+											0, 0);
+
+			appendStringInfoString(&buf, 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/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 51b8a1a..854dddc 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -1253,9 +1253,10 @@ expand_table_name_patterns(Archive *fout,
 						  "SELECT c.oid"
 						  "\nFROM pg_catalog.pg_class c"
 		"\n     LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace"
-					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c')\n",
+					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c')\n",
 						  RELKIND_RELATION, RELKIND_SEQUENCE, RELKIND_VIEW,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 		processSQLNamePattern(GetConnection(fout), query, cell->val, true,
 							  false, "n.nspname", "c.relname", NULL,
 							  "pg_catalog.pg_table_is_visible(c.oid)");
@@ -2125,6 +2126,9 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo, bool oids)
 	/* Skip FOREIGN TABLEs (no data to dump) */
 	if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 		return;
+	/* Skip partitioned tables (data in partitions) */
+	if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+		return;
 
 	/* Don't dump data in unlogged tables, if so requested */
 	if (tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED &&
@@ -5211,6 +5215,7 @@ getTables(Archive *fout, int *numTables)
 	int			i_reloftype;
 	int			i_relpages;
 	int			i_changed_acl;
+	int			i_partkeydef;
 
 	/* Make sure we are in proper schema */
 	selectSourceSchema(fout, "pg_catalog");
@@ -5296,7 +5301,8 @@ getTables(Archive *fout, int *numTables)
 						  "OR %s IS NOT NULL "
 						  "OR %s IS NOT NULL"
 						  "))"
-						  "AS changed_acl "
+						  "AS changed_acl, "
+						  "CASE WHEN c.relkind = 'P' THEN pg_catalog.pg_get_partkeydef(c.oid) ELSE NULL END AS partkeydef "
 						  "FROM pg_class c "
 						  "LEFT JOIN pg_depend d ON "
 						  "(c.relkind = '%c' AND "
@@ -5308,7 +5314,7 @@ getTables(Archive *fout, int *numTables)
 						  "(c.oid = pip.objoid "
 						  "AND pip.classoid = 'pg_class'::regclass "
 						  "AND pip.objsubid = 0) "
-				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c') "
+				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c', '%c') "
 						  "ORDER BY c.oid",
 						  acl_subquery->data,
 						  racl_subquery->data,
@@ -5322,7 +5328,8 @@ getTables(Archive *fout, int *numTables)
 						  RELKIND_SEQUENCE,
 						  RELKIND_RELATION, RELKIND_SEQUENCE,
 						  RELKIND_VIEW, RELKIND_COMPOSITE_TYPE,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 
 		destroyPQExpBuffer(acl_subquery);
 		destroyPQExpBuffer(racl_subquery);
@@ -5884,6 +5891,7 @@ getTables(Archive *fout, int *numTables)
 	i_toastreloptions = PQfnumber(res, "toast_reloptions");
 	i_reloftype = PQfnumber(res, "reloftype");
 	i_changed_acl = PQfnumber(res, "changed_acl");
+	i_partkeydef = PQfnumber(res, "partkeydef");
 
 	if (dopt->lockWaitTimeout && fout->remoteVersion >= 70300)
 	{
@@ -5954,6 +5962,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 */
 
@@ -5998,7 +6007,9 @@ getTables(Archive *fout, int *numTables)
 		 * We only need to lock the table for certain components; see
 		 * pg_dump.h
 		 */
-		if (tblinfo[i].dobj.dump && tblinfo[i].relkind == RELKIND_RELATION &&
+		if (tblinfo[i].dobj.dump &&
+			(tblinfo[i].relkind == RELKIND_RELATION ||
+			 tblinfo->relkind == RELKIND_PARTITIONED_TABLE) &&
 			(tblinfo[i].dobj.dump & DUMP_COMPONENTS_REQUIRING_LOCK))
 		{
 			resetPQExpBuffer(query);
@@ -6100,7 +6111,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);
 
@@ -15453,6 +15467,9 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 				appendPQExpBufferChar(q, ')');
 			}
 
+			if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+				appendPQExpBuffer(q, "\nPARTITION BY %s", tbinfo->partkeydef);
+
 			if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 				appendPQExpBuffer(q, "\nSERVER %s", fmtId(srvname));
 		}
@@ -15513,6 +15530,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 		 */
 		if (dopt->binary_upgrade &&
 			(tbinfo->relkind == RELKIND_RELATION ||
+			 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 			 tbinfo->relkind == RELKIND_FOREIGN_TABLE))
 		{
 			for (j = 0; j < tbinfo->numatts; j++)
@@ -15531,7 +15549,8 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 					appendStringLiteralAH(q, fmtId(tbinfo->dobj.name), fout);
 					appendPQExpBufferStr(q, "::pg_catalog.regclass;\n");
 
-					if (tbinfo->relkind == RELKIND_RELATION)
+					if (tbinfo->relkind == RELKIND_RELATION ||
+						tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
 						appendPQExpBuffer(q, "ALTER TABLE ONLY %s ",
 										  fmtId(tbinfo->dobj.name));
 					else
@@ -15748,6 +15767,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 	 * dump properties we only have ALTER TABLE syntax for
 	 */
 	if ((tbinfo->relkind == RELKIND_RELATION ||
+		 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 		 tbinfo->relkind == RELKIND_MATVIEW) &&
 		tbinfo->relreplident != REPLICA_IDENTITY_DEFAULT)
 	{
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 2bfa2d9..0292859 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -310,6 +310,7 @@ typedef struct _tableInfo
 	bool	   *inhNotNull;		/* true if NOT NULL is inherited */
 	struct _attrDefInfo **attrdefs;		/* DEFAULT expressions */
 	struct _constraintInfo *checkexprs; /* CHECK constraints */
+	char	   *partkeydef;		/* partition key definition */
 
 	/*
 	 * Stuff computed only for dumpable tables.
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 6275a68..db6dc5c 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -861,6 +861,7 @@ permissionsList(const char *pattern)
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'S' THEN '%s'"
@@ -870,6 +871,7 @@ permissionsList(const char *pattern)
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("sequence"),
@@ -920,7 +922,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
@@ -1567,8 +1569,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)");
 	}
 
@@ -1633,6 +1635,14 @@ describeOneTableDetails(const char *schemaname,
 			printfPQExpBuffer(&title, _("Foreign table \"%s.%s\""),
 							  schemaname, relationname);
 			break;
+		case 'P':
+			if (tableinfo.relpersistence == 'u')
+				printfPQExpBuffer(&title, _("Unlogged table \"%s.%s\""),
+								  schemaname, relationname);
+			else
+				printfPQExpBuffer(&title, _("Table \"%s.%s\""),
+								  schemaname, relationname);
+			break;
 		default:
 			/* untranslated unknown relkind */
 			printfPQExpBuffer(&title, "?%c? \"%s.%s\"",
@@ -1646,8 +1656,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");
@@ -1667,12 +1677,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");
 	}
 
@@ -1772,7 +1782,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);
@@ -1780,14 +1790,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 (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 */
@@ -1926,7 +1955,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;
@@ -2485,7 +2514,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;
@@ -2696,7 +2725,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
@@ -3024,6 +3053,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'i' THEN '%s'"
@@ -3035,6 +3065,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("index"),
@@ -3079,7 +3110,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 50a45eb..8284a9c 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/pg_proc.h b/src/include/catalog/pg_proc.h
index e2d08ba..b45688b 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -1980,6 +1980,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/utils/builtins.h b/src/include/utils/builtins.h
index 2ae212a..e800647 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -729,6 +729,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/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 1394362..260ec34 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -407,3 +407,29 @@ CREATE TABLE no_inh_con_parted (
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
 ERROR:  cannot add NO INHERIT constraint to partitioned table "no_inh_con_parted"
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+Table "public.describe_range_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition key: RANGE ((a + b))
+
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+Table "public.describe_list_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition key: LIST (a)
+
+DROP TABLE describe_range_key, describe_list_key;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index d383f58..7a1f8de 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -401,3 +401,16 @@ CREATE TABLE no_inh_con_parted (
 	a int,
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
+
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+DROP TABLE describe_range_key, describe_list_key;
-- 
1.7.1


--------------C3235EE96C69D8DAE4DB543E
Content-Type: text/x-diff;
 name="0003-Catalog-and-DDL-for-partitions-6.patch"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
 filename="0003-Catalog-and-DDL-for-partitions-6.patch"



^ permalink  raw  reply  [nested|flat] 110+ messages in thread

* [PATCH 2/8] psql and pg_dump support for partitioned tables.
@ 2016-07-12 08:20  amit <[email protected]>
  0 siblings, 0 replies; 110+ messages in thread

From: amit @ 2016-07-12 08:20 UTC (permalink / raw)

Takes care of both the partition key deparse stuff and the new relkind.
---
 src/backend/utils/adt/ruleutils.c          |  147 ++++++++++++++++++++++++++++
 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/pg_proc.h              |    2 +
 src/include/utils/builtins.h               |    1 +
 src/test/regress/expected/create_table.out |   26 +++++
 src/test/regress/sql/create_table.sql      |   13 +++
 9 files changed, 267 insertions(+), 26 deletions(-)

diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index ec966c7..4691b5b 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, bool missing_ok);
+static char *pg_get_partkeydef_worker(Oid relId, int prettyFlags);
 static char *pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 							int prettyFlags, bool missing_ok);
 static text *pg_get_expr_worker(text *expr, Oid relid, const char *relname,
@@ -1389,6 +1391,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 [ 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/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 4ee10fc..a127672 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -1253,9 +1253,10 @@ expand_table_name_patterns(Archive *fout,
 						  "SELECT c.oid"
 						  "\nFROM pg_catalog.pg_class c"
 		"\n     LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace"
-					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c')\n",
+					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c')\n",
 						  RELKIND_RELATION, RELKIND_SEQUENCE, RELKIND_VIEW,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_REL);
 		processSQLNamePattern(GetConnection(fout), query, cell->val, true,
 							  false, "n.nspname", "c.relname", NULL,
 							  "pg_catalog.pg_table_is_visible(c.oid)");
@@ -2117,6 +2118,9 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo, bool oids)
 	/* Skip FOREIGN TABLEs (no data to dump) */
 	if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 		return;
+	/* Skip partitioned tables (data in partitions) */
+	if (tbinfo->relkind == RELKIND_PARTITIONED_REL)
+		return;
 
 	/* Don't dump data in unlogged tables, if so requested */
 	if (tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED &&
@@ -5195,6 +5199,7 @@ getTables(Archive *fout, int *numTables)
 	int			i_reloftype;
 	int			i_relpages;
 	int			i_changed_acl;
+	int			i_partkeydef;
 
 	/* Make sure we are in proper schema */
 	selectSourceSchema(fout, "pg_catalog");
@@ -5280,7 +5285,8 @@ getTables(Archive *fout, int *numTables)
 						  "OR %s IS NOT NULL "
 						  "OR %s IS NOT NULL"
 						  "))"
-						  "AS changed_acl "
+						  "AS changed_acl, "
+						  "CASE WHEN c.relkind = 'P' THEN pg_catalog.pg_get_partkeydef(c.oid) ELSE NULL END AS partkeydef "
 						  "FROM pg_class c "
 						  "LEFT JOIN pg_depend d ON "
 						  "(c.relkind = '%c' AND "
@@ -5292,7 +5298,7 @@ getTables(Archive *fout, int *numTables)
 						  "(c.oid = pip.objoid "
 						  "AND pip.classoid = 'pg_class'::regclass "
 						  "AND pip.objsubid = 0) "
-				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c') "
+				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c', '%c') "
 						  "ORDER BY c.oid",
 						  acl_subquery->data,
 						  racl_subquery->data,
@@ -5306,7 +5312,8 @@ getTables(Archive *fout, int *numTables)
 						  RELKIND_SEQUENCE,
 						  RELKIND_RELATION, RELKIND_SEQUENCE,
 						  RELKIND_VIEW, RELKIND_COMPOSITE_TYPE,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_REL);
 
 		destroyPQExpBuffer(acl_subquery);
 		destroyPQExpBuffer(racl_subquery);
@@ -5868,6 +5875,7 @@ getTables(Archive *fout, int *numTables)
 	i_toastreloptions = PQfnumber(res, "toast_reloptions");
 	i_reloftype = PQfnumber(res, "reloftype");
 	i_changed_acl = PQfnumber(res, "changed_acl");
+	i_partkeydef = PQfnumber(res, "partkeydef");
 
 	if (dopt->lockWaitTimeout && fout->remoteVersion >= 70300)
 	{
@@ -5938,6 +5946,7 @@ getTables(Archive *fout, int *numTables)
 		else
 			tblinfo[i].checkoption = pg_strdup(PQgetvalue(res, i, i_checkoption));
 		tblinfo[i].toast_reloptions = pg_strdup(PQgetvalue(res, i, i_toastreloptions));
+		tblinfo[i].partkeydef = pg_strdup(PQgetvalue(res, i, i_partkeydef));
 
 		/* other fields were zeroed above */
 
@@ -5982,7 +5991,9 @@ getTables(Archive *fout, int *numTables)
 		 * We only need to lock the table for certain components; see
 		 * pg_dump.h
 		 */
-		if (tblinfo[i].dobj.dump && tblinfo[i].relkind == RELKIND_RELATION &&
+		if (tblinfo[i].dobj.dump &&
+			(tblinfo[i].relkind == RELKIND_RELATION ||
+			 tblinfo->relkind == RELKIND_PARTITIONED_REL) &&
 			(tblinfo[i].dobj.dump & DUMP_COMPONENTS_REQUIRING_LOCK))
 		{
 			resetPQExpBuffer(query);
@@ -6083,7 +6094,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);
 
@@ -15436,6 +15450,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));
 		}
@@ -15496,6 +15513,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++)
@@ -15514,7 +15532,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
@@ -15731,6 +15750,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 	 * dump properties we only have ALTER TABLE syntax for
 	 */
 	if ((tbinfo->relkind == RELKIND_RELATION ||
+		 tbinfo->relkind == RELKIND_PARTITIONED_REL ||
 		 tbinfo->relkind == RELKIND_MATVIEW) &&
 		tbinfo->relreplident != REPLICA_IDENTITY_DEFAULT)
 	{
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 2bfa2d9..0292859 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -310,6 +310,7 @@ typedef struct _tableInfo
 	bool	   *inhNotNull;		/* true if NOT NULL is inherited */
 	struct _attrDefInfo **attrdefs;		/* DEFAULT expressions */
 	struct _constraintInfo *checkexprs; /* CHECK constraints */
+	char	   *partkeydef;		/* partition key definition */
 
 	/*
 	 * Stuff computed only for dumpable tables.
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 27be102..3ba8a90 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -856,6 +856,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"),
@@ -865,6 +866,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");
@@ -911,7 +913,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
@@ -1555,8 +1557,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)");
 	}
 
@@ -1621,6 +1623,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\"",
@@ -1634,8 +1644,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");
@@ -1655,12 +1665,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");
 	}
 
@@ -1760,7 +1770,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);
@@ -1768,14 +1778,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 */
@@ -1914,7 +1943,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;
@@ -2473,7 +2502,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;
@@ -2684,7 +2713,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
@@ -3018,6 +3047,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"),
@@ -3029,6 +3059,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"));
 
@@ -3067,7 +3098,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 
 	appendPQExpBufferStr(&buf, "\nWHERE c.relkind IN (");
 	if (showTables)
-		appendPQExpBufferStr(&buf, "'r',");
+		appendPQExpBufferStr(&buf, "'r', 'P',");
 	if (showViews)
 		appendPQExpBufferStr(&buf, "'v',");
 	if (showMatViews)
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 8469d9f..cd4eb7c 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -427,7 +427,7 @@ static const SchemaQuery Query_for_list_of_tables = {
 	/* catname */
 	"pg_catalog.pg_class c",
 	/* selcondition */
-	"c.relkind IN ('r')",
+	"c.relkind IN ('r', 'P')",
 	/* viscondition */
 	"pg_catalog.pg_table_is_visible(c.oid)",
 	/* namespace */
@@ -458,7 +458,7 @@ static const SchemaQuery Query_for_list_of_updatables = {
 	/* catname */
 	"pg_catalog.pg_class c",
 	/* selcondition */
-	"c.relkind IN ('r', 'f', 'v')",
+	"c.relkind IN ('r', 'f', 'v', 'P')",
 	/* viscondition */
 	"pg_catalog.pg_table_is_visible(c.oid)",
 	/* namespace */
@@ -488,7 +488,7 @@ static const SchemaQuery Query_for_list_of_tsvmf = {
 	/* catname */
 	"pg_catalog.pg_class c",
 	/* selcondition */
-	"c.relkind IN ('r', 'S', 'v', 'm', 'f')",
+	"c.relkind IN ('r', 'S', 'v', 'm', 'f', 'P')",
 	/* viscondition */
 	"pg_catalog.pg_table_is_visible(c.oid)",
 	/* namespace */
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index 270dd21..5a444d9 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/utils/builtins.h b/src/include/utils/builtins.h
index 8cebc86..351bfd3 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -722,6 +722,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/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index fb7a1b3..4fc8c06 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -413,3 +413,29 @@ SELECT connoinherit FROM pg_constraint WHERE conrelid = 'no_inh_con_parted'::reg
 (1 row)
 
 DROP TABLE no_inh_con_parted;
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+Partitioned table "public.describe_range_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition Key: PARTITION BY RANGE (((a + b)))
+
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+Partitioned table "public.describe_list_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition Key: PARTITION BY LIST (a)
+
+DROP TABLE describe_range_key, describe_list_key;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index cb17517..7c7a18a 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -404,3 +404,16 @@ CREATE TABLE no_inh_con_parted (
 ) PARTITION BY RANGE (a);
 SELECT connoinherit FROM pg_constraint WHERE conrelid = 'no_inh_con_parted'::regclass;
 DROP TABLE no_inh_con_parted;
+
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+DROP TABLE describe_range_key, describe_list_key;
-- 
1.7.1


--------------8C4C48E640631A327D3D2826
Content-Type: text/x-diff;
 name="0003-Catalog-and-DDL-for-partitions.patch"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
 filename="0003-Catalog-and-DDL-for-partitions.patch"



^ permalink  raw  reply  [nested|flat] 110+ messages in thread

* [PATCH 2/9] psql and pg_dump support for partitioned tables.
@ 2016-07-12 08:20  amit <[email protected]>
  0 siblings, 0 replies; 110+ messages in thread

From: amit @ 2016-07-12 08:20 UTC (permalink / raw)

Takes care of both the partition key deparse stuff and the new relkind.
---
 src/backend/utils/adt/ruleutils.c          |  140 ++++++++++++++++++++++++++++
 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/pg_proc.h              |    2 +
 src/include/utils/builtins.h               |    1 +
 src/test/regress/expected/create_table.out |   26 +++++
 src/test/regress/sql/create_table.sql      |   13 +++
 9 files changed, 260 insertions(+), 26 deletions(-)

diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 8a81d7a..77ce807 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_table.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
@@ -315,6 +316,7 @@ static char *pg_get_indexdef_worker(Oid indexrelid, int colno,
 					   const Oid *excludeOps,
 					   bool attrsOnly, bool showTblSpc,
 					   int prettyFlags, bool missing_ok);
+static char *pg_get_partkeydef_worker(Oid relId, int prettyFlags);
 static char *pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 							int prettyFlags, bool missing_ok);
 static text *pg_get_expr_worker(text *expr, Oid relid, const char *relname,
@@ -1389,6 +1391,144 @@ 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 [ 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_table	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_table) GETSTRUCT(tuple);
+
+	Assert(form->partedrelid == relId);
+
+	/* Must get partclass, and partexprs the hard way */
+	datum = SysCacheGetAttr(PARTEDRELID, tuple,
+							Anum_pg_partitioned_table_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_table_partexprbin))
+	{
+		Datum		exprsDatum;
+		bool		isnull;
+		char	   *exprsString;
+
+		exprsDatum = SysCacheGetAttr(PARTEDRELID, tuple,
+									 Anum_pg_partitioned_table_partexprbin, &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);
+
+	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,
+											0, 0);
+
+			appendStringInfoString(&buf, 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/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index a5c2d09..c418ba7 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -1253,9 +1253,10 @@ expand_table_name_patterns(Archive *fout,
 						  "SELECT c.oid"
 						  "\nFROM pg_catalog.pg_class c"
 		"\n     LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace"
-					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c')\n",
+					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c')\n",
 						  RELKIND_RELATION, RELKIND_SEQUENCE, RELKIND_VIEW,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 		processSQLNamePattern(GetConnection(fout), query, cell->val, true,
 							  false, "n.nspname", "c.relname", NULL,
 							  "pg_catalog.pg_table_is_visible(c.oid)");
@@ -2117,6 +2118,9 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo, bool oids)
 	/* Skip FOREIGN TABLEs (no data to dump) */
 	if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 		return;
+	/* Skip partitioned tables (data in partitions) */
+	if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+		return;
 
 	/* Don't dump data in unlogged tables, if so requested */
 	if (tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED &&
@@ -5195,6 +5199,7 @@ getTables(Archive *fout, int *numTables)
 	int			i_reloftype;
 	int			i_relpages;
 	int			i_changed_acl;
+	int			i_partkeydef;
 
 	/* Make sure we are in proper schema */
 	selectSourceSchema(fout, "pg_catalog");
@@ -5280,7 +5285,8 @@ getTables(Archive *fout, int *numTables)
 						  "OR %s IS NOT NULL "
 						  "OR %s IS NOT NULL"
 						  "))"
-						  "AS changed_acl "
+						  "AS changed_acl, "
+						  "CASE WHEN c.relkind = 'P' THEN pg_catalog.pg_get_partkeydef(c.oid) ELSE NULL END AS partkeydef "
 						  "FROM pg_class c "
 						  "LEFT JOIN pg_depend d ON "
 						  "(c.relkind = '%c' AND "
@@ -5292,7 +5298,7 @@ getTables(Archive *fout, int *numTables)
 						  "(c.oid = pip.objoid "
 						  "AND pip.classoid = 'pg_class'::regclass "
 						  "AND pip.objsubid = 0) "
-				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c') "
+				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c', '%c') "
 						  "ORDER BY c.oid",
 						  acl_subquery->data,
 						  racl_subquery->data,
@@ -5306,7 +5312,8 @@ getTables(Archive *fout, int *numTables)
 						  RELKIND_SEQUENCE,
 						  RELKIND_RELATION, RELKIND_SEQUENCE,
 						  RELKIND_VIEW, RELKIND_COMPOSITE_TYPE,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 
 		destroyPQExpBuffer(acl_subquery);
 		destroyPQExpBuffer(racl_subquery);
@@ -5868,6 +5875,7 @@ getTables(Archive *fout, int *numTables)
 	i_toastreloptions = PQfnumber(res, "toast_reloptions");
 	i_reloftype = PQfnumber(res, "reloftype");
 	i_changed_acl = PQfnumber(res, "changed_acl");
+	i_partkeydef = PQfnumber(res, "partkeydef");
 
 	if (dopt->lockWaitTimeout && fout->remoteVersion >= 70300)
 	{
@@ -5938,6 +5946,7 @@ getTables(Archive *fout, int *numTables)
 		else
 			tblinfo[i].checkoption = pg_strdup(PQgetvalue(res, i, i_checkoption));
 		tblinfo[i].toast_reloptions = pg_strdup(PQgetvalue(res, i, i_toastreloptions));
+		tblinfo[i].partkeydef = pg_strdup(PQgetvalue(res, i, i_partkeydef));
 
 		/* other fields were zeroed above */
 
@@ -5982,7 +5991,9 @@ getTables(Archive *fout, int *numTables)
 		 * We only need to lock the table for certain components; see
 		 * pg_dump.h
 		 */
-		if (tblinfo[i].dobj.dump && tblinfo[i].relkind == RELKIND_RELATION &&
+		if (tblinfo[i].dobj.dump &&
+			(tblinfo[i].relkind == RELKIND_RELATION ||
+			 tblinfo->relkind == RELKIND_PARTITIONED_TABLE) &&
 			(tblinfo[i].dobj.dump & DUMP_COMPONENTS_REQUIRING_LOCK))
 		{
 			resetPQExpBuffer(query);
@@ -6084,7 +6095,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);
 
@@ -15437,6 +15451,9 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 				appendPQExpBufferChar(q, ')');
 			}
 
+			if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+				appendPQExpBuffer(q, "\nPARTITION BY %s", tbinfo->partkeydef);
+
 			if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 				appendPQExpBuffer(q, "\nSERVER %s", fmtId(srvname));
 		}
@@ -15497,6 +15514,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 		 */
 		if (dopt->binary_upgrade &&
 			(tbinfo->relkind == RELKIND_RELATION ||
+			 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 			 tbinfo->relkind == RELKIND_FOREIGN_TABLE))
 		{
 			for (j = 0; j < tbinfo->numatts; j++)
@@ -15515,7 +15533,8 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 					appendStringLiteralAH(q, fmtId(tbinfo->dobj.name), fout);
 					appendPQExpBufferStr(q, "::pg_catalog.regclass;\n");
 
-					if (tbinfo->relkind == RELKIND_RELATION)
+					if (tbinfo->relkind == RELKIND_RELATION ||
+						tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
 						appendPQExpBuffer(q, "ALTER TABLE ONLY %s ",
 										  fmtId(tbinfo->dobj.name));
 					else
@@ -15732,6 +15751,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 	 * dump properties we only have ALTER TABLE syntax for
 	 */
 	if ((tbinfo->relkind == RELKIND_RELATION ||
+		 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 		 tbinfo->relkind == RELKIND_MATVIEW) &&
 		tbinfo->relreplident != REPLICA_IDENTITY_DEFAULT)
 	{
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 2bfa2d9..0292859 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -310,6 +310,7 @@ typedef struct _tableInfo
 	bool	   *inhNotNull;		/* true if NOT NULL is inherited */
 	struct _attrDefInfo **attrdefs;		/* DEFAULT expressions */
 	struct _constraintInfo *checkexprs; /* CHECK constraints */
+	char	   *partkeydef;		/* partition key definition */
 
 	/*
 	 * Stuff computed only for dumpable tables.
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 6275a68..10d924a 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -861,6 +861,7 @@ permissionsList(const char *pattern)
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'S' THEN '%s'"
@@ -870,6 +871,7 @@ permissionsList(const char *pattern)
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("sequence"),
@@ -920,7 +922,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
@@ -1567,8 +1569,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)");
 	}
 
@@ -1633,6 +1635,14 @@ describeOneTableDetails(const char *schemaname,
 			printfPQExpBuffer(&title, _("Foreign table \"%s.%s\""),
 							  schemaname, relationname);
 			break;
+		case 'P':
+			if (tableinfo.relpersistence == 'u')
+				printfPQExpBuffer(&title, _("Unlogged table \"%s.%s\""),
+								  schemaname, relationname);
+			else
+				printfPQExpBuffer(&title, _("Table \"%s.%s\""),
+								  schemaname, relationname);
+			break;
 		default:
 			/* untranslated unknown relkind */
 			printfPQExpBuffer(&title, "?%c? \"%s.%s\"",
@@ -1646,8 +1656,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");
@@ -1667,12 +1677,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");
 	}
 
@@ -1772,7 +1782,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);
@@ -1780,14 +1790,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 (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 */
@@ -1926,7 +1955,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;
@@ -2485,7 +2514,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;
@@ -2696,7 +2725,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
@@ -3024,6 +3053,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'i' THEN '%s'"
@@ -3035,6 +3065,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("index"),
@@ -3079,7 +3110,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 1345e4e..4686cd1 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/pg_proc.h b/src/include/catalog/pg_proc.h
index e2d08ba..b45688b 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -1980,6 +1980,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/utils/builtins.h b/src/include/utils/builtins.h
index 2ae212a..e800647 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -729,6 +729,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/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 708232d..2fec847 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -408,3 +408,29 @@ CREATE TABLE no_inh_con_parted (
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
 ERROR:  cannot add NO INHERIT constraint to partitioned table "no_inh_con_parted"
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+Table "public.describe_range_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition Key: RANGE ((a + b))
+
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+Table "public.describe_list_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition Key: LIST (a)
+
+DROP TABLE describe_range_key, describe_list_key;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 6e4a8be..4dd6a0a 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -402,3 +402,16 @@ CREATE TABLE no_inh_con_parted (
 	a int,
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
+
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+DROP TABLE describe_range_key, describe_list_key;
-- 
1.7.1


--------------954458C17BBEB1BF6FC758E7
Content-Type: text/x-diff;
 name="0003-Catalog-and-DDL-for-partitions-2.patch"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
 filename="0003-Catalog-and-DDL-for-partitions-2.patch"



^ permalink  raw  reply  [nested|flat] 110+ messages in thread

* [PATCH 2/9] psql and pg_dump support for partitioned tables.
@ 2016-07-12 08:20  amit <[email protected]>
  0 siblings, 0 replies; 110+ messages in thread

From: amit @ 2016-07-12 08:20 UTC (permalink / raw)

Takes care of both the partition key deparse stuff and the new relkind.
---
 src/backend/utils/adt/ruleutils.c          |  140 ++++++++++++++++++++++++++++
 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/pg_proc.h              |    2 +
 src/include/utils/builtins.h               |    1 +
 src/test/regress/expected/create_table.out |   26 +++++
 src/test/regress/sql/create_table.sql      |   13 +++
 9 files changed, 260 insertions(+), 26 deletions(-)

diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 8a81d7a..03be202 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_table.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
@@ -315,6 +316,7 @@ static char *pg_get_indexdef_worker(Oid indexrelid, int colno,
 					   const Oid *excludeOps,
 					   bool attrsOnly, bool showTblSpc,
 					   int prettyFlags, bool missing_ok);
+static char *pg_get_partkeydef_worker(Oid relid, int prettyFlags);
 static char *pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 							int prettyFlags, bool missing_ok);
 static text *pg_get_expr_worker(text *expr, Oid relid, const char *relname,
@@ -1389,6 +1391,144 @@ 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 [ opclass_name ] [, ...])
+ */
+Datum
+pg_get_partkeydef(PG_FUNCTION_ARGS)
+{
+	Oid			relid = PG_GETARG_OID(0);
+	int			prettyFlags;
+
+	prettyFlags = PRETTYFLAG_INDENT;
+	PG_RETURN_TEXT_P(string_to_text(pg_get_partkeydef_worker(relid,
+									prettyFlags)));
+}
+
+/*
+ * Internal workhorse to decompile a partition key definition.
+ */
+static char *
+pg_get_partkeydef_worker(Oid relid, int prettyFlags)
+{
+	Form_pg_partitioned_table	form;
+	HeapTuple	tuple;
+	oidvector  *partclass;
+	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_table) GETSTRUCT(tuple);
+
+	Assert(form->partrelid == relid);
+
+	/* Must get partclass, and partexprs the hard way */
+	datum = SysCacheGetAttr(PARTEDRELID, tuple,
+							Anum_pg_partitioned_table_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_table_partexprbin))
+	{
+		Datum		exprsDatum;
+		bool		isnull;
+		char	   *exprsString;
+
+		exprsDatum = SysCacheGetAttr(PARTEDRELID, tuple,
+									 Anum_pg_partitioned_table_partexprbin, &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);
+
+	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,
+											0, 0);
+
+			appendStringInfoString(&buf, 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/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index ba9c276..c805a84 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -1253,9 +1253,10 @@ expand_table_name_patterns(Archive *fout,
 						  "SELECT c.oid"
 						  "\nFROM pg_catalog.pg_class c"
 		"\n     LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace"
-					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c')\n",
+					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c')\n",
 						  RELKIND_RELATION, RELKIND_SEQUENCE, RELKIND_VIEW,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 		processSQLNamePattern(GetConnection(fout), query, cell->val, true,
 							  false, "n.nspname", "c.relname", NULL,
 							  "pg_catalog.pg_table_is_visible(c.oid)");
@@ -2125,6 +2126,9 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo, bool oids)
 	/* Skip FOREIGN TABLEs (no data to dump) */
 	if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 		return;
+	/* Skip partitioned tables (data in partitions) */
+	if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+		return;
 
 	/* Don't dump data in unlogged tables, if so requested */
 	if (tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED &&
@@ -5204,6 +5208,7 @@ getTables(Archive *fout, int *numTables)
 	int			i_reloftype;
 	int			i_relpages;
 	int			i_changed_acl;
+	int			i_partkeydef;
 
 	/* Make sure we are in proper schema */
 	selectSourceSchema(fout, "pg_catalog");
@@ -5289,7 +5294,8 @@ getTables(Archive *fout, int *numTables)
 						  "OR %s IS NOT NULL "
 						  "OR %s IS NOT NULL"
 						  "))"
-						  "AS changed_acl "
+						  "AS changed_acl, "
+						  "CASE WHEN c.relkind = 'P' THEN pg_catalog.pg_get_partkeydef(c.oid) ELSE NULL END AS partkeydef "
 						  "FROM pg_class c "
 						  "LEFT JOIN pg_depend d ON "
 						  "(c.relkind = '%c' AND "
@@ -5301,7 +5307,7 @@ getTables(Archive *fout, int *numTables)
 						  "(c.oid = pip.objoid "
 						  "AND pip.classoid = 'pg_class'::regclass "
 						  "AND pip.objsubid = 0) "
-				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c') "
+				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c', '%c') "
 						  "ORDER BY c.oid",
 						  acl_subquery->data,
 						  racl_subquery->data,
@@ -5315,7 +5321,8 @@ getTables(Archive *fout, int *numTables)
 						  RELKIND_SEQUENCE,
 						  RELKIND_RELATION, RELKIND_SEQUENCE,
 						  RELKIND_VIEW, RELKIND_COMPOSITE_TYPE,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 
 		destroyPQExpBuffer(acl_subquery);
 		destroyPQExpBuffer(racl_subquery);
@@ -5877,6 +5884,7 @@ getTables(Archive *fout, int *numTables)
 	i_toastreloptions = PQfnumber(res, "toast_reloptions");
 	i_reloftype = PQfnumber(res, "reloftype");
 	i_changed_acl = PQfnumber(res, "changed_acl");
+	i_partkeydef = PQfnumber(res, "partkeydef");
 
 	if (dopt->lockWaitTimeout && fout->remoteVersion >= 70300)
 	{
@@ -5947,6 +5955,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 */
 
@@ -5991,7 +6000,9 @@ getTables(Archive *fout, int *numTables)
 		 * We only need to lock the table for certain components; see
 		 * pg_dump.h
 		 */
-		if (tblinfo[i].dobj.dump && tblinfo[i].relkind == RELKIND_RELATION &&
+		if (tblinfo[i].dobj.dump &&
+			(tblinfo[i].relkind == RELKIND_RELATION ||
+			 tblinfo->relkind == RELKIND_PARTITIONED_TABLE) &&
 			(tblinfo[i].dobj.dump & DUMP_COMPONENTS_REQUIRING_LOCK))
 		{
 			resetPQExpBuffer(query);
@@ -6093,7 +6104,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);
 
@@ -15446,6 +15460,9 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 				appendPQExpBufferChar(q, ')');
 			}
 
+			if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+				appendPQExpBuffer(q, "\nPARTITION BY %s", tbinfo->partkeydef);
+
 			if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 				appendPQExpBuffer(q, "\nSERVER %s", fmtId(srvname));
 		}
@@ -15506,6 +15523,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 		 */
 		if (dopt->binary_upgrade &&
 			(tbinfo->relkind == RELKIND_RELATION ||
+			 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 			 tbinfo->relkind == RELKIND_FOREIGN_TABLE))
 		{
 			for (j = 0; j < tbinfo->numatts; j++)
@@ -15524,7 +15542,8 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 					appendStringLiteralAH(q, fmtId(tbinfo->dobj.name), fout);
 					appendPQExpBufferStr(q, "::pg_catalog.regclass;\n");
 
-					if (tbinfo->relkind == RELKIND_RELATION)
+					if (tbinfo->relkind == RELKIND_RELATION ||
+						tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
 						appendPQExpBuffer(q, "ALTER TABLE ONLY %s ",
 										  fmtId(tbinfo->dobj.name));
 					else
@@ -15741,6 +15760,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 	 * dump properties we only have ALTER TABLE syntax for
 	 */
 	if ((tbinfo->relkind == RELKIND_RELATION ||
+		 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 		 tbinfo->relkind == RELKIND_MATVIEW) &&
 		tbinfo->relreplident != REPLICA_IDENTITY_DEFAULT)
 	{
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 2bfa2d9..0292859 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -310,6 +310,7 @@ typedef struct _tableInfo
 	bool	   *inhNotNull;		/* true if NOT NULL is inherited */
 	struct _attrDefInfo **attrdefs;		/* DEFAULT expressions */
 	struct _constraintInfo *checkexprs; /* CHECK constraints */
+	char	   *partkeydef;		/* partition key definition */
 
 	/*
 	 * Stuff computed only for dumpable tables.
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 6275a68..10d924a 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -861,6 +861,7 @@ permissionsList(const char *pattern)
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'S' THEN '%s'"
@@ -870,6 +871,7 @@ permissionsList(const char *pattern)
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("sequence"),
@@ -920,7 +922,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
@@ -1567,8 +1569,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)");
 	}
 
@@ -1633,6 +1635,14 @@ describeOneTableDetails(const char *schemaname,
 			printfPQExpBuffer(&title, _("Foreign table \"%s.%s\""),
 							  schemaname, relationname);
 			break;
+		case 'P':
+			if (tableinfo.relpersistence == 'u')
+				printfPQExpBuffer(&title, _("Unlogged table \"%s.%s\""),
+								  schemaname, relationname);
+			else
+				printfPQExpBuffer(&title, _("Table \"%s.%s\""),
+								  schemaname, relationname);
+			break;
 		default:
 			/* untranslated unknown relkind */
 			printfPQExpBuffer(&title, "?%c? \"%s.%s\"",
@@ -1646,8 +1656,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");
@@ -1667,12 +1677,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");
 	}
 
@@ -1772,7 +1782,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);
@@ -1780,14 +1790,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 (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 */
@@ -1926,7 +1955,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;
@@ -2485,7 +2514,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;
@@ -2696,7 +2725,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
@@ -3024,6 +3053,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'i' THEN '%s'"
@@ -3035,6 +3065,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("index"),
@@ -3079,7 +3110,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 019f75a..5e5c370 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/pg_proc.h b/src/include/catalog/pg_proc.h
index e2d08ba..b45688b 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -1980,6 +1980,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/utils/builtins.h b/src/include/utils/builtins.h
index 2ae212a..e800647 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -729,6 +729,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/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 708232d..2fec847 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -408,3 +408,29 @@ CREATE TABLE no_inh_con_parted (
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
 ERROR:  cannot add NO INHERIT constraint to partitioned table "no_inh_con_parted"
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+Table "public.describe_range_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition Key: RANGE ((a + b))
+
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+Table "public.describe_list_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition Key: LIST (a)
+
+DROP TABLE describe_range_key, describe_list_key;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 6e4a8be..4dd6a0a 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -402,3 +402,16 @@ CREATE TABLE no_inh_con_parted (
 	a int,
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
+
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+DROP TABLE describe_range_key, describe_list_key;
-- 
1.7.1


--------------4012470FC48539D6F6714CEA
Content-Type: text/x-diff;
 name="0003-Catalog-and-DDL-for-partitions-4.patch"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
 filename="0003-Catalog-and-DDL-for-partitions-4.patch"



^ permalink  raw  reply  [nested|flat] 110+ messages in thread

* [PATCH 2/9] psql and pg_dump support for partitioned tables.
@ 2016-07-12 08:20  amit <[email protected]>
  0 siblings, 0 replies; 110+ messages in thread

From: amit @ 2016-07-12 08:20 UTC (permalink / raw)

Takes care of both the partition key deparse stuff and the new relkind.
---
 src/backend/utils/adt/ruleutils.c          |  140 ++++++++++++++++++++++++++++
 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/pg_proc.h              |    2 +
 src/include/utils/builtins.h               |    1 +
 src/test/regress/expected/create_table.out |   26 +++++
 src/test/regress/sql/create_table.sql      |   13 +++
 9 files changed, 260 insertions(+), 26 deletions(-)

diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 8a81d7a..03be202 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_table.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
@@ -315,6 +316,7 @@ static char *pg_get_indexdef_worker(Oid indexrelid, int colno,
 					   const Oid *excludeOps,
 					   bool attrsOnly, bool showTblSpc,
 					   int prettyFlags, bool missing_ok);
+static char *pg_get_partkeydef_worker(Oid relid, int prettyFlags);
 static char *pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 							int prettyFlags, bool missing_ok);
 static text *pg_get_expr_worker(text *expr, Oid relid, const char *relname,
@@ -1389,6 +1391,144 @@ 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 [ opclass_name ] [, ...])
+ */
+Datum
+pg_get_partkeydef(PG_FUNCTION_ARGS)
+{
+	Oid			relid = PG_GETARG_OID(0);
+	int			prettyFlags;
+
+	prettyFlags = PRETTYFLAG_INDENT;
+	PG_RETURN_TEXT_P(string_to_text(pg_get_partkeydef_worker(relid,
+									prettyFlags)));
+}
+
+/*
+ * Internal workhorse to decompile a partition key definition.
+ */
+static char *
+pg_get_partkeydef_worker(Oid relid, int prettyFlags)
+{
+	Form_pg_partitioned_table	form;
+	HeapTuple	tuple;
+	oidvector  *partclass;
+	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_table) GETSTRUCT(tuple);
+
+	Assert(form->partrelid == relid);
+
+	/* Must get partclass, and partexprs the hard way */
+	datum = SysCacheGetAttr(PARTEDRELID, tuple,
+							Anum_pg_partitioned_table_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_table_partexprbin))
+	{
+		Datum		exprsDatum;
+		bool		isnull;
+		char	   *exprsString;
+
+		exprsDatum = SysCacheGetAttr(PARTEDRELID, tuple,
+									 Anum_pg_partitioned_table_partexprbin, &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);
+
+	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,
+											0, 0);
+
+			appendStringInfoString(&buf, 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/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 51b8a1a..854dddc 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -1253,9 +1253,10 @@ expand_table_name_patterns(Archive *fout,
 						  "SELECT c.oid"
 						  "\nFROM pg_catalog.pg_class c"
 		"\n     LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace"
-					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c')\n",
+					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c')\n",
 						  RELKIND_RELATION, RELKIND_SEQUENCE, RELKIND_VIEW,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 		processSQLNamePattern(GetConnection(fout), query, cell->val, true,
 							  false, "n.nspname", "c.relname", NULL,
 							  "pg_catalog.pg_table_is_visible(c.oid)");
@@ -2125,6 +2126,9 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo, bool oids)
 	/* Skip FOREIGN TABLEs (no data to dump) */
 	if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 		return;
+	/* Skip partitioned tables (data in partitions) */
+	if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+		return;
 
 	/* Don't dump data in unlogged tables, if so requested */
 	if (tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED &&
@@ -5211,6 +5215,7 @@ getTables(Archive *fout, int *numTables)
 	int			i_reloftype;
 	int			i_relpages;
 	int			i_changed_acl;
+	int			i_partkeydef;
 
 	/* Make sure we are in proper schema */
 	selectSourceSchema(fout, "pg_catalog");
@@ -5296,7 +5301,8 @@ getTables(Archive *fout, int *numTables)
 						  "OR %s IS NOT NULL "
 						  "OR %s IS NOT NULL"
 						  "))"
-						  "AS changed_acl "
+						  "AS changed_acl, "
+						  "CASE WHEN c.relkind = 'P' THEN pg_catalog.pg_get_partkeydef(c.oid) ELSE NULL END AS partkeydef "
 						  "FROM pg_class c "
 						  "LEFT JOIN pg_depend d ON "
 						  "(c.relkind = '%c' AND "
@@ -5308,7 +5314,7 @@ getTables(Archive *fout, int *numTables)
 						  "(c.oid = pip.objoid "
 						  "AND pip.classoid = 'pg_class'::regclass "
 						  "AND pip.objsubid = 0) "
-				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c') "
+				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c', '%c') "
 						  "ORDER BY c.oid",
 						  acl_subquery->data,
 						  racl_subquery->data,
@@ -5322,7 +5328,8 @@ getTables(Archive *fout, int *numTables)
 						  RELKIND_SEQUENCE,
 						  RELKIND_RELATION, RELKIND_SEQUENCE,
 						  RELKIND_VIEW, RELKIND_COMPOSITE_TYPE,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 
 		destroyPQExpBuffer(acl_subquery);
 		destroyPQExpBuffer(racl_subquery);
@@ -5884,6 +5891,7 @@ getTables(Archive *fout, int *numTables)
 	i_toastreloptions = PQfnumber(res, "toast_reloptions");
 	i_reloftype = PQfnumber(res, "reloftype");
 	i_changed_acl = PQfnumber(res, "changed_acl");
+	i_partkeydef = PQfnumber(res, "partkeydef");
 
 	if (dopt->lockWaitTimeout && fout->remoteVersion >= 70300)
 	{
@@ -5954,6 +5962,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 */
 
@@ -5998,7 +6007,9 @@ getTables(Archive *fout, int *numTables)
 		 * We only need to lock the table for certain components; see
 		 * pg_dump.h
 		 */
-		if (tblinfo[i].dobj.dump && tblinfo[i].relkind == RELKIND_RELATION &&
+		if (tblinfo[i].dobj.dump &&
+			(tblinfo[i].relkind == RELKIND_RELATION ||
+			 tblinfo->relkind == RELKIND_PARTITIONED_TABLE) &&
 			(tblinfo[i].dobj.dump & DUMP_COMPONENTS_REQUIRING_LOCK))
 		{
 			resetPQExpBuffer(query);
@@ -6100,7 +6111,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);
 
@@ -15453,6 +15467,9 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 				appendPQExpBufferChar(q, ')');
 			}
 
+			if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+				appendPQExpBuffer(q, "\nPARTITION BY %s", tbinfo->partkeydef);
+
 			if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 				appendPQExpBuffer(q, "\nSERVER %s", fmtId(srvname));
 		}
@@ -15513,6 +15530,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 		 */
 		if (dopt->binary_upgrade &&
 			(tbinfo->relkind == RELKIND_RELATION ||
+			 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 			 tbinfo->relkind == RELKIND_FOREIGN_TABLE))
 		{
 			for (j = 0; j < tbinfo->numatts; j++)
@@ -15531,7 +15549,8 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 					appendStringLiteralAH(q, fmtId(tbinfo->dobj.name), fout);
 					appendPQExpBufferStr(q, "::pg_catalog.regclass;\n");
 
-					if (tbinfo->relkind == RELKIND_RELATION)
+					if (tbinfo->relkind == RELKIND_RELATION ||
+						tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
 						appendPQExpBuffer(q, "ALTER TABLE ONLY %s ",
 										  fmtId(tbinfo->dobj.name));
 					else
@@ -15748,6 +15767,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 	 * dump properties we only have ALTER TABLE syntax for
 	 */
 	if ((tbinfo->relkind == RELKIND_RELATION ||
+		 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 		 tbinfo->relkind == RELKIND_MATVIEW) &&
 		tbinfo->relreplident != REPLICA_IDENTITY_DEFAULT)
 	{
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 2bfa2d9..0292859 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -310,6 +310,7 @@ typedef struct _tableInfo
 	bool	   *inhNotNull;		/* true if NOT NULL is inherited */
 	struct _attrDefInfo **attrdefs;		/* DEFAULT expressions */
 	struct _constraintInfo *checkexprs; /* CHECK constraints */
+	char	   *partkeydef;		/* partition key definition */
 
 	/*
 	 * Stuff computed only for dumpable tables.
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 6275a68..db6dc5c 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -861,6 +861,7 @@ permissionsList(const char *pattern)
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'S' THEN '%s'"
@@ -870,6 +871,7 @@ permissionsList(const char *pattern)
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("sequence"),
@@ -920,7 +922,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
@@ -1567,8 +1569,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)");
 	}
 
@@ -1633,6 +1635,14 @@ describeOneTableDetails(const char *schemaname,
 			printfPQExpBuffer(&title, _("Foreign table \"%s.%s\""),
 							  schemaname, relationname);
 			break;
+		case 'P':
+			if (tableinfo.relpersistence == 'u')
+				printfPQExpBuffer(&title, _("Unlogged table \"%s.%s\""),
+								  schemaname, relationname);
+			else
+				printfPQExpBuffer(&title, _("Table \"%s.%s\""),
+								  schemaname, relationname);
+			break;
 		default:
 			/* untranslated unknown relkind */
 			printfPQExpBuffer(&title, "?%c? \"%s.%s\"",
@@ -1646,8 +1656,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");
@@ -1667,12 +1677,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");
 	}
 
@@ -1772,7 +1782,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);
@@ -1780,14 +1790,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 (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 */
@@ -1926,7 +1955,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;
@@ -2485,7 +2514,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;
@@ -2696,7 +2725,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
@@ -3024,6 +3053,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'i' THEN '%s'"
@@ -3035,6 +3065,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("index"),
@@ -3079,7 +3110,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 50a45eb..8284a9c 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/pg_proc.h b/src/include/catalog/pg_proc.h
index e2d08ba..b45688b 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -1980,6 +1980,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/utils/builtins.h b/src/include/utils/builtins.h
index 2ae212a..e800647 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -729,6 +729,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/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 1394362..260ec34 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -407,3 +407,29 @@ CREATE TABLE no_inh_con_parted (
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
 ERROR:  cannot add NO INHERIT constraint to partitioned table "no_inh_con_parted"
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+Table "public.describe_range_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition key: RANGE ((a + b))
+
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+Table "public.describe_list_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition key: LIST (a)
+
+DROP TABLE describe_range_key, describe_list_key;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index d383f58..7a1f8de 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -401,3 +401,16 @@ CREATE TABLE no_inh_con_parted (
 	a int,
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
+
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+DROP TABLE describe_range_key, describe_list_key;
-- 
1.7.1


--------------C3235EE96C69D8DAE4DB543E
Content-Type: text/x-diff;
 name="0003-Catalog-and-DDL-for-partitions-6.patch"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
 filename="0003-Catalog-and-DDL-for-partitions-6.patch"



^ permalink  raw  reply  [nested|flat] 110+ messages in thread

* [PATCH 2/9] psql and pg_dump support for partitioned tables.
@ 2016-07-12 08:20  amit <[email protected]>
  0 siblings, 0 replies; 110+ messages in thread

From: amit @ 2016-07-12 08:20 UTC (permalink / raw)

Takes care of both the partition key deparse stuff and the new relkind.
---
 src/backend/utils/adt/ruleutils.c          |  140 ++++++++++++++++++++++++++++
 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/pg_proc.h              |    2 +
 src/include/utils/builtins.h               |    1 +
 src/test/regress/expected/create_table.out |   26 +++++
 src/test/regress/sql/create_table.sql      |   13 +++
 9 files changed, 260 insertions(+), 26 deletions(-)

diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 8a81d7a..03be202 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_table.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
@@ -315,6 +316,7 @@ static char *pg_get_indexdef_worker(Oid indexrelid, int colno,
 					   const Oid *excludeOps,
 					   bool attrsOnly, bool showTblSpc,
 					   int prettyFlags, bool missing_ok);
+static char *pg_get_partkeydef_worker(Oid relid, int prettyFlags);
 static char *pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 							int prettyFlags, bool missing_ok);
 static text *pg_get_expr_worker(text *expr, Oid relid, const char *relname,
@@ -1389,6 +1391,144 @@ 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 [ opclass_name ] [, ...])
+ */
+Datum
+pg_get_partkeydef(PG_FUNCTION_ARGS)
+{
+	Oid			relid = PG_GETARG_OID(0);
+	int			prettyFlags;
+
+	prettyFlags = PRETTYFLAG_INDENT;
+	PG_RETURN_TEXT_P(string_to_text(pg_get_partkeydef_worker(relid,
+									prettyFlags)));
+}
+
+/*
+ * Internal workhorse to decompile a partition key definition.
+ */
+static char *
+pg_get_partkeydef_worker(Oid relid, int prettyFlags)
+{
+	Form_pg_partitioned_table	form;
+	HeapTuple	tuple;
+	oidvector  *partclass;
+	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_table) GETSTRUCT(tuple);
+
+	Assert(form->partrelid == relid);
+
+	/* Must get partclass, and partexprs the hard way */
+	datum = SysCacheGetAttr(PARTEDRELID, tuple,
+							Anum_pg_partitioned_table_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_table_partexprbin))
+	{
+		Datum		exprsDatum;
+		bool		isnull;
+		char	   *exprsString;
+
+		exprsDatum = SysCacheGetAttr(PARTEDRELID, tuple,
+									 Anum_pg_partitioned_table_partexprbin, &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);
+
+	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,
+											0, 0);
+
+			appendStringInfoString(&buf, 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/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index ba9c276..c805a84 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -1253,9 +1253,10 @@ expand_table_name_patterns(Archive *fout,
 						  "SELECT c.oid"
 						  "\nFROM pg_catalog.pg_class c"
 		"\n     LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace"
-					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c')\n",
+					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c')\n",
 						  RELKIND_RELATION, RELKIND_SEQUENCE, RELKIND_VIEW,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 		processSQLNamePattern(GetConnection(fout), query, cell->val, true,
 							  false, "n.nspname", "c.relname", NULL,
 							  "pg_catalog.pg_table_is_visible(c.oid)");
@@ -2125,6 +2126,9 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo, bool oids)
 	/* Skip FOREIGN TABLEs (no data to dump) */
 	if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 		return;
+	/* Skip partitioned tables (data in partitions) */
+	if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+		return;
 
 	/* Don't dump data in unlogged tables, if so requested */
 	if (tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED &&
@@ -5204,6 +5208,7 @@ getTables(Archive *fout, int *numTables)
 	int			i_reloftype;
 	int			i_relpages;
 	int			i_changed_acl;
+	int			i_partkeydef;
 
 	/* Make sure we are in proper schema */
 	selectSourceSchema(fout, "pg_catalog");
@@ -5289,7 +5294,8 @@ getTables(Archive *fout, int *numTables)
 						  "OR %s IS NOT NULL "
 						  "OR %s IS NOT NULL"
 						  "))"
-						  "AS changed_acl "
+						  "AS changed_acl, "
+						  "CASE WHEN c.relkind = 'P' THEN pg_catalog.pg_get_partkeydef(c.oid) ELSE NULL END AS partkeydef "
 						  "FROM pg_class c "
 						  "LEFT JOIN pg_depend d ON "
 						  "(c.relkind = '%c' AND "
@@ -5301,7 +5307,7 @@ getTables(Archive *fout, int *numTables)
 						  "(c.oid = pip.objoid "
 						  "AND pip.classoid = 'pg_class'::regclass "
 						  "AND pip.objsubid = 0) "
-				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c') "
+				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c', '%c') "
 						  "ORDER BY c.oid",
 						  acl_subquery->data,
 						  racl_subquery->data,
@@ -5315,7 +5321,8 @@ getTables(Archive *fout, int *numTables)
 						  RELKIND_SEQUENCE,
 						  RELKIND_RELATION, RELKIND_SEQUENCE,
 						  RELKIND_VIEW, RELKIND_COMPOSITE_TYPE,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 
 		destroyPQExpBuffer(acl_subquery);
 		destroyPQExpBuffer(racl_subquery);
@@ -5877,6 +5884,7 @@ getTables(Archive *fout, int *numTables)
 	i_toastreloptions = PQfnumber(res, "toast_reloptions");
 	i_reloftype = PQfnumber(res, "reloftype");
 	i_changed_acl = PQfnumber(res, "changed_acl");
+	i_partkeydef = PQfnumber(res, "partkeydef");
 
 	if (dopt->lockWaitTimeout && fout->remoteVersion >= 70300)
 	{
@@ -5947,6 +5955,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 */
 
@@ -5991,7 +6000,9 @@ getTables(Archive *fout, int *numTables)
 		 * We only need to lock the table for certain components; see
 		 * pg_dump.h
 		 */
-		if (tblinfo[i].dobj.dump && tblinfo[i].relkind == RELKIND_RELATION &&
+		if (tblinfo[i].dobj.dump &&
+			(tblinfo[i].relkind == RELKIND_RELATION ||
+			 tblinfo->relkind == RELKIND_PARTITIONED_TABLE) &&
 			(tblinfo[i].dobj.dump & DUMP_COMPONENTS_REQUIRING_LOCK))
 		{
 			resetPQExpBuffer(query);
@@ -6093,7 +6104,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);
 
@@ -15446,6 +15460,9 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 				appendPQExpBufferChar(q, ')');
 			}
 
+			if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+				appendPQExpBuffer(q, "\nPARTITION BY %s", tbinfo->partkeydef);
+
 			if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 				appendPQExpBuffer(q, "\nSERVER %s", fmtId(srvname));
 		}
@@ -15506,6 +15523,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 		 */
 		if (dopt->binary_upgrade &&
 			(tbinfo->relkind == RELKIND_RELATION ||
+			 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 			 tbinfo->relkind == RELKIND_FOREIGN_TABLE))
 		{
 			for (j = 0; j < tbinfo->numatts; j++)
@@ -15524,7 +15542,8 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 					appendStringLiteralAH(q, fmtId(tbinfo->dobj.name), fout);
 					appendPQExpBufferStr(q, "::pg_catalog.regclass;\n");
 
-					if (tbinfo->relkind == RELKIND_RELATION)
+					if (tbinfo->relkind == RELKIND_RELATION ||
+						tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
 						appendPQExpBuffer(q, "ALTER TABLE ONLY %s ",
 										  fmtId(tbinfo->dobj.name));
 					else
@@ -15741,6 +15760,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 	 * dump properties we only have ALTER TABLE syntax for
 	 */
 	if ((tbinfo->relkind == RELKIND_RELATION ||
+		 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 		 tbinfo->relkind == RELKIND_MATVIEW) &&
 		tbinfo->relreplident != REPLICA_IDENTITY_DEFAULT)
 	{
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 2bfa2d9..0292859 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -310,6 +310,7 @@ typedef struct _tableInfo
 	bool	   *inhNotNull;		/* true if NOT NULL is inherited */
 	struct _attrDefInfo **attrdefs;		/* DEFAULT expressions */
 	struct _constraintInfo *checkexprs; /* CHECK constraints */
+	char	   *partkeydef;		/* partition key definition */
 
 	/*
 	 * Stuff computed only for dumpable tables.
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 6275a68..10d924a 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -861,6 +861,7 @@ permissionsList(const char *pattern)
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'S' THEN '%s'"
@@ -870,6 +871,7 @@ permissionsList(const char *pattern)
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("sequence"),
@@ -920,7 +922,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
@@ -1567,8 +1569,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)");
 	}
 
@@ -1633,6 +1635,14 @@ describeOneTableDetails(const char *schemaname,
 			printfPQExpBuffer(&title, _("Foreign table \"%s.%s\""),
 							  schemaname, relationname);
 			break;
+		case 'P':
+			if (tableinfo.relpersistence == 'u')
+				printfPQExpBuffer(&title, _("Unlogged table \"%s.%s\""),
+								  schemaname, relationname);
+			else
+				printfPQExpBuffer(&title, _("Table \"%s.%s\""),
+								  schemaname, relationname);
+			break;
 		default:
 			/* untranslated unknown relkind */
 			printfPQExpBuffer(&title, "?%c? \"%s.%s\"",
@@ -1646,8 +1656,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");
@@ -1667,12 +1677,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");
 	}
 
@@ -1772,7 +1782,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);
@@ -1780,14 +1790,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 (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 */
@@ -1926,7 +1955,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;
@@ -2485,7 +2514,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;
@@ -2696,7 +2725,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
@@ -3024,6 +3053,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'i' THEN '%s'"
@@ -3035,6 +3065,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("index"),
@@ -3079,7 +3110,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 019f75a..5e5c370 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/pg_proc.h b/src/include/catalog/pg_proc.h
index e2d08ba..b45688b 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -1980,6 +1980,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/utils/builtins.h b/src/include/utils/builtins.h
index 2ae212a..e800647 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -729,6 +729,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/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 708232d..2fec847 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -408,3 +408,29 @@ CREATE TABLE no_inh_con_parted (
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
 ERROR:  cannot add NO INHERIT constraint to partitioned table "no_inh_con_parted"
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+Table "public.describe_range_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition Key: RANGE ((a + b))
+
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+Table "public.describe_list_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition Key: LIST (a)
+
+DROP TABLE describe_range_key, describe_list_key;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 6e4a8be..4dd6a0a 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -402,3 +402,16 @@ CREATE TABLE no_inh_con_parted (
 	a int,
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
+
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+DROP TABLE describe_range_key, describe_list_key;
-- 
1.7.1


--------------4012470FC48539D6F6714CEA
Content-Type: text/x-diff;
 name="0003-Catalog-and-DDL-for-partitions-4.patch"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
 filename="0003-Catalog-and-DDL-for-partitions-4.patch"



^ permalink  raw  reply  [nested|flat] 110+ messages in thread

* [PATCH 2/9] psql and pg_dump support for partitioned tables.
@ 2016-07-12 08:20  amit <[email protected]>
  0 siblings, 0 replies; 110+ messages in thread

From: amit @ 2016-07-12 08:20 UTC (permalink / raw)

Takes care of both the partition key deparse stuff and the new relkind.
---
 src/backend/utils/adt/ruleutils.c          |  140 ++++++++++++++++++++++++++++
 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/pg_proc.h              |    2 +
 src/include/utils/builtins.h               |    1 +
 src/test/regress/expected/create_table.out |   26 +++++
 src/test/regress/sql/create_table.sql      |   13 +++
 9 files changed, 260 insertions(+), 26 deletions(-)

diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 8a81d7a..03be202 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_table.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
@@ -315,6 +316,7 @@ static char *pg_get_indexdef_worker(Oid indexrelid, int colno,
 					   const Oid *excludeOps,
 					   bool attrsOnly, bool showTblSpc,
 					   int prettyFlags, bool missing_ok);
+static char *pg_get_partkeydef_worker(Oid relid, int prettyFlags);
 static char *pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 							int prettyFlags, bool missing_ok);
 static text *pg_get_expr_worker(text *expr, Oid relid, const char *relname,
@@ -1389,6 +1391,144 @@ 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 [ opclass_name ] [, ...])
+ */
+Datum
+pg_get_partkeydef(PG_FUNCTION_ARGS)
+{
+	Oid			relid = PG_GETARG_OID(0);
+	int			prettyFlags;
+
+	prettyFlags = PRETTYFLAG_INDENT;
+	PG_RETURN_TEXT_P(string_to_text(pg_get_partkeydef_worker(relid,
+									prettyFlags)));
+}
+
+/*
+ * Internal workhorse to decompile a partition key definition.
+ */
+static char *
+pg_get_partkeydef_worker(Oid relid, int prettyFlags)
+{
+	Form_pg_partitioned_table	form;
+	HeapTuple	tuple;
+	oidvector  *partclass;
+	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_table) GETSTRUCT(tuple);
+
+	Assert(form->partrelid == relid);
+
+	/* Must get partclass, and partexprs the hard way */
+	datum = SysCacheGetAttr(PARTEDRELID, tuple,
+							Anum_pg_partitioned_table_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_table_partexprbin))
+	{
+		Datum		exprsDatum;
+		bool		isnull;
+		char	   *exprsString;
+
+		exprsDatum = SysCacheGetAttr(PARTEDRELID, tuple,
+									 Anum_pg_partitioned_table_partexprbin, &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);
+
+	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,
+											0, 0);
+
+			appendStringInfoString(&buf, 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/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 51b8a1a..854dddc 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -1253,9 +1253,10 @@ expand_table_name_patterns(Archive *fout,
 						  "SELECT c.oid"
 						  "\nFROM pg_catalog.pg_class c"
 		"\n     LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace"
-					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c')\n",
+					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c')\n",
 						  RELKIND_RELATION, RELKIND_SEQUENCE, RELKIND_VIEW,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 		processSQLNamePattern(GetConnection(fout), query, cell->val, true,
 							  false, "n.nspname", "c.relname", NULL,
 							  "pg_catalog.pg_table_is_visible(c.oid)");
@@ -2125,6 +2126,9 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo, bool oids)
 	/* Skip FOREIGN TABLEs (no data to dump) */
 	if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 		return;
+	/* Skip partitioned tables (data in partitions) */
+	if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+		return;
 
 	/* Don't dump data in unlogged tables, if so requested */
 	if (tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED &&
@@ -5211,6 +5215,7 @@ getTables(Archive *fout, int *numTables)
 	int			i_reloftype;
 	int			i_relpages;
 	int			i_changed_acl;
+	int			i_partkeydef;
 
 	/* Make sure we are in proper schema */
 	selectSourceSchema(fout, "pg_catalog");
@@ -5296,7 +5301,8 @@ getTables(Archive *fout, int *numTables)
 						  "OR %s IS NOT NULL "
 						  "OR %s IS NOT NULL"
 						  "))"
-						  "AS changed_acl "
+						  "AS changed_acl, "
+						  "CASE WHEN c.relkind = 'P' THEN pg_catalog.pg_get_partkeydef(c.oid) ELSE NULL END AS partkeydef "
 						  "FROM pg_class c "
 						  "LEFT JOIN pg_depend d ON "
 						  "(c.relkind = '%c' AND "
@@ -5308,7 +5314,7 @@ getTables(Archive *fout, int *numTables)
 						  "(c.oid = pip.objoid "
 						  "AND pip.classoid = 'pg_class'::regclass "
 						  "AND pip.objsubid = 0) "
-				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c') "
+				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c', '%c') "
 						  "ORDER BY c.oid",
 						  acl_subquery->data,
 						  racl_subquery->data,
@@ -5322,7 +5328,8 @@ getTables(Archive *fout, int *numTables)
 						  RELKIND_SEQUENCE,
 						  RELKIND_RELATION, RELKIND_SEQUENCE,
 						  RELKIND_VIEW, RELKIND_COMPOSITE_TYPE,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 
 		destroyPQExpBuffer(acl_subquery);
 		destroyPQExpBuffer(racl_subquery);
@@ -5884,6 +5891,7 @@ getTables(Archive *fout, int *numTables)
 	i_toastreloptions = PQfnumber(res, "toast_reloptions");
 	i_reloftype = PQfnumber(res, "reloftype");
 	i_changed_acl = PQfnumber(res, "changed_acl");
+	i_partkeydef = PQfnumber(res, "partkeydef");
 
 	if (dopt->lockWaitTimeout && fout->remoteVersion >= 70300)
 	{
@@ -5954,6 +5962,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 */
 
@@ -5998,7 +6007,9 @@ getTables(Archive *fout, int *numTables)
 		 * We only need to lock the table for certain components; see
 		 * pg_dump.h
 		 */
-		if (tblinfo[i].dobj.dump && tblinfo[i].relkind == RELKIND_RELATION &&
+		if (tblinfo[i].dobj.dump &&
+			(tblinfo[i].relkind == RELKIND_RELATION ||
+			 tblinfo->relkind == RELKIND_PARTITIONED_TABLE) &&
 			(tblinfo[i].dobj.dump & DUMP_COMPONENTS_REQUIRING_LOCK))
 		{
 			resetPQExpBuffer(query);
@@ -6100,7 +6111,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);
 
@@ -15453,6 +15467,9 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 				appendPQExpBufferChar(q, ')');
 			}
 
+			if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+				appendPQExpBuffer(q, "\nPARTITION BY %s", tbinfo->partkeydef);
+
 			if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 				appendPQExpBuffer(q, "\nSERVER %s", fmtId(srvname));
 		}
@@ -15513,6 +15530,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 		 */
 		if (dopt->binary_upgrade &&
 			(tbinfo->relkind == RELKIND_RELATION ||
+			 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 			 tbinfo->relkind == RELKIND_FOREIGN_TABLE))
 		{
 			for (j = 0; j < tbinfo->numatts; j++)
@@ -15531,7 +15549,8 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 					appendStringLiteralAH(q, fmtId(tbinfo->dobj.name), fout);
 					appendPQExpBufferStr(q, "::pg_catalog.regclass;\n");
 
-					if (tbinfo->relkind == RELKIND_RELATION)
+					if (tbinfo->relkind == RELKIND_RELATION ||
+						tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
 						appendPQExpBuffer(q, "ALTER TABLE ONLY %s ",
 										  fmtId(tbinfo->dobj.name));
 					else
@@ -15748,6 +15767,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 	 * dump properties we only have ALTER TABLE syntax for
 	 */
 	if ((tbinfo->relkind == RELKIND_RELATION ||
+		 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 		 tbinfo->relkind == RELKIND_MATVIEW) &&
 		tbinfo->relreplident != REPLICA_IDENTITY_DEFAULT)
 	{
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 2bfa2d9..0292859 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -310,6 +310,7 @@ typedef struct _tableInfo
 	bool	   *inhNotNull;		/* true if NOT NULL is inherited */
 	struct _attrDefInfo **attrdefs;		/* DEFAULT expressions */
 	struct _constraintInfo *checkexprs; /* CHECK constraints */
+	char	   *partkeydef;		/* partition key definition */
 
 	/*
 	 * Stuff computed only for dumpable tables.
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 6275a68..db6dc5c 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -861,6 +861,7 @@ permissionsList(const char *pattern)
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'S' THEN '%s'"
@@ -870,6 +871,7 @@ permissionsList(const char *pattern)
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("sequence"),
@@ -920,7 +922,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
@@ -1567,8 +1569,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)");
 	}
 
@@ -1633,6 +1635,14 @@ describeOneTableDetails(const char *schemaname,
 			printfPQExpBuffer(&title, _("Foreign table \"%s.%s\""),
 							  schemaname, relationname);
 			break;
+		case 'P':
+			if (tableinfo.relpersistence == 'u')
+				printfPQExpBuffer(&title, _("Unlogged table \"%s.%s\""),
+								  schemaname, relationname);
+			else
+				printfPQExpBuffer(&title, _("Table \"%s.%s\""),
+								  schemaname, relationname);
+			break;
 		default:
 			/* untranslated unknown relkind */
 			printfPQExpBuffer(&title, "?%c? \"%s.%s\"",
@@ -1646,8 +1656,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");
@@ -1667,12 +1677,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");
 	}
 
@@ -1772,7 +1782,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);
@@ -1780,14 +1790,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 (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 */
@@ -1926,7 +1955,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;
@@ -2485,7 +2514,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;
@@ -2696,7 +2725,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
@@ -3024,6 +3053,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'i' THEN '%s'"
@@ -3035,6 +3065,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("index"),
@@ -3079,7 +3110,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 50a45eb..8284a9c 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/pg_proc.h b/src/include/catalog/pg_proc.h
index e2d08ba..b45688b 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -1980,6 +1980,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/utils/builtins.h b/src/include/utils/builtins.h
index 2ae212a..e800647 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -729,6 +729,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/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 1394362..260ec34 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -407,3 +407,29 @@ CREATE TABLE no_inh_con_parted (
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
 ERROR:  cannot add NO INHERIT constraint to partitioned table "no_inh_con_parted"
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+Table "public.describe_range_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition key: RANGE ((a + b))
+
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+Table "public.describe_list_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition key: LIST (a)
+
+DROP TABLE describe_range_key, describe_list_key;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index d383f58..7a1f8de 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -401,3 +401,16 @@ CREATE TABLE no_inh_con_parted (
 	a int,
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
+
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+DROP TABLE describe_range_key, describe_list_key;
-- 
1.7.1


--------------C3235EE96C69D8DAE4DB543E
Content-Type: text/x-diff;
 name="0003-Catalog-and-DDL-for-partitions-6.patch"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
 filename="0003-Catalog-and-DDL-for-partitions-6.patch"



^ permalink  raw  reply  [nested|flat] 110+ messages in thread

* [PATCH 2/9] psql and pg_dump support for partitioned tables.
@ 2016-07-12 08:20  amit <[email protected]>
  0 siblings, 0 replies; 110+ messages in thread

From: amit @ 2016-07-12 08:20 UTC (permalink / raw)

Takes care of both the partition key deparse stuff and the new relkind.
---
 src/backend/utils/adt/ruleutils.c          |  140 ++++++++++++++++++++++++++++
 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/pg_proc.h              |    2 +
 src/include/utils/builtins.h               |    1 +
 src/test/regress/expected/create_table.out |   26 +++++
 src/test/regress/sql/create_table.sql      |   13 +++
 9 files changed, 260 insertions(+), 26 deletions(-)

diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 8a81d7a..77ce807 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_table.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
@@ -315,6 +316,7 @@ static char *pg_get_indexdef_worker(Oid indexrelid, int colno,
 					   const Oid *excludeOps,
 					   bool attrsOnly, bool showTblSpc,
 					   int prettyFlags, bool missing_ok);
+static char *pg_get_partkeydef_worker(Oid relId, int prettyFlags);
 static char *pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 							int prettyFlags, bool missing_ok);
 static text *pg_get_expr_worker(text *expr, Oid relid, const char *relname,
@@ -1389,6 +1391,144 @@ 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 [ 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_table	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_table) GETSTRUCT(tuple);
+
+	Assert(form->partedrelid == relId);
+
+	/* Must get partclass, and partexprs the hard way */
+	datum = SysCacheGetAttr(PARTEDRELID, tuple,
+							Anum_pg_partitioned_table_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_table_partexprbin))
+	{
+		Datum		exprsDatum;
+		bool		isnull;
+		char	   *exprsString;
+
+		exprsDatum = SysCacheGetAttr(PARTEDRELID, tuple,
+									 Anum_pg_partitioned_table_partexprbin, &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);
+
+	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,
+											0, 0);
+
+			appendStringInfoString(&buf, 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/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index a5c2d09..c418ba7 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -1253,9 +1253,10 @@ expand_table_name_patterns(Archive *fout,
 						  "SELECT c.oid"
 						  "\nFROM pg_catalog.pg_class c"
 		"\n     LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace"
-					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c')\n",
+					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c')\n",
 						  RELKIND_RELATION, RELKIND_SEQUENCE, RELKIND_VIEW,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 		processSQLNamePattern(GetConnection(fout), query, cell->val, true,
 							  false, "n.nspname", "c.relname", NULL,
 							  "pg_catalog.pg_table_is_visible(c.oid)");
@@ -2117,6 +2118,9 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo, bool oids)
 	/* Skip FOREIGN TABLEs (no data to dump) */
 	if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 		return;
+	/* Skip partitioned tables (data in partitions) */
+	if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+		return;
 
 	/* Don't dump data in unlogged tables, if so requested */
 	if (tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED &&
@@ -5195,6 +5199,7 @@ getTables(Archive *fout, int *numTables)
 	int			i_reloftype;
 	int			i_relpages;
 	int			i_changed_acl;
+	int			i_partkeydef;
 
 	/* Make sure we are in proper schema */
 	selectSourceSchema(fout, "pg_catalog");
@@ -5280,7 +5285,8 @@ getTables(Archive *fout, int *numTables)
 						  "OR %s IS NOT NULL "
 						  "OR %s IS NOT NULL"
 						  "))"
-						  "AS changed_acl "
+						  "AS changed_acl, "
+						  "CASE WHEN c.relkind = 'P' THEN pg_catalog.pg_get_partkeydef(c.oid) ELSE NULL END AS partkeydef "
 						  "FROM pg_class c "
 						  "LEFT JOIN pg_depend d ON "
 						  "(c.relkind = '%c' AND "
@@ -5292,7 +5298,7 @@ getTables(Archive *fout, int *numTables)
 						  "(c.oid = pip.objoid "
 						  "AND pip.classoid = 'pg_class'::regclass "
 						  "AND pip.objsubid = 0) "
-				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c') "
+				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c', '%c') "
 						  "ORDER BY c.oid",
 						  acl_subquery->data,
 						  racl_subquery->data,
@@ -5306,7 +5312,8 @@ getTables(Archive *fout, int *numTables)
 						  RELKIND_SEQUENCE,
 						  RELKIND_RELATION, RELKIND_SEQUENCE,
 						  RELKIND_VIEW, RELKIND_COMPOSITE_TYPE,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 
 		destroyPQExpBuffer(acl_subquery);
 		destroyPQExpBuffer(racl_subquery);
@@ -5868,6 +5875,7 @@ getTables(Archive *fout, int *numTables)
 	i_toastreloptions = PQfnumber(res, "toast_reloptions");
 	i_reloftype = PQfnumber(res, "reloftype");
 	i_changed_acl = PQfnumber(res, "changed_acl");
+	i_partkeydef = PQfnumber(res, "partkeydef");
 
 	if (dopt->lockWaitTimeout && fout->remoteVersion >= 70300)
 	{
@@ -5938,6 +5946,7 @@ getTables(Archive *fout, int *numTables)
 		else
 			tblinfo[i].checkoption = pg_strdup(PQgetvalue(res, i, i_checkoption));
 		tblinfo[i].toast_reloptions = pg_strdup(PQgetvalue(res, i, i_toastreloptions));
+		tblinfo[i].partkeydef = pg_strdup(PQgetvalue(res, i, i_partkeydef));
 
 		/* other fields were zeroed above */
 
@@ -5982,7 +5991,9 @@ getTables(Archive *fout, int *numTables)
 		 * We only need to lock the table for certain components; see
 		 * pg_dump.h
 		 */
-		if (tblinfo[i].dobj.dump && tblinfo[i].relkind == RELKIND_RELATION &&
+		if (tblinfo[i].dobj.dump &&
+			(tblinfo[i].relkind == RELKIND_RELATION ||
+			 tblinfo->relkind == RELKIND_PARTITIONED_TABLE) &&
 			(tblinfo[i].dobj.dump & DUMP_COMPONENTS_REQUIRING_LOCK))
 		{
 			resetPQExpBuffer(query);
@@ -6084,7 +6095,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);
 
@@ -15437,6 +15451,9 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 				appendPQExpBufferChar(q, ')');
 			}
 
+			if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+				appendPQExpBuffer(q, "\nPARTITION BY %s", tbinfo->partkeydef);
+
 			if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 				appendPQExpBuffer(q, "\nSERVER %s", fmtId(srvname));
 		}
@@ -15497,6 +15514,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 		 */
 		if (dopt->binary_upgrade &&
 			(tbinfo->relkind == RELKIND_RELATION ||
+			 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 			 tbinfo->relkind == RELKIND_FOREIGN_TABLE))
 		{
 			for (j = 0; j < tbinfo->numatts; j++)
@@ -15515,7 +15533,8 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 					appendStringLiteralAH(q, fmtId(tbinfo->dobj.name), fout);
 					appendPQExpBufferStr(q, "::pg_catalog.regclass;\n");
 
-					if (tbinfo->relkind == RELKIND_RELATION)
+					if (tbinfo->relkind == RELKIND_RELATION ||
+						tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
 						appendPQExpBuffer(q, "ALTER TABLE ONLY %s ",
 										  fmtId(tbinfo->dobj.name));
 					else
@@ -15732,6 +15751,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 	 * dump properties we only have ALTER TABLE syntax for
 	 */
 	if ((tbinfo->relkind == RELKIND_RELATION ||
+		 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 		 tbinfo->relkind == RELKIND_MATVIEW) &&
 		tbinfo->relreplident != REPLICA_IDENTITY_DEFAULT)
 	{
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 2bfa2d9..0292859 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -310,6 +310,7 @@ typedef struct _tableInfo
 	bool	   *inhNotNull;		/* true if NOT NULL is inherited */
 	struct _attrDefInfo **attrdefs;		/* DEFAULT expressions */
 	struct _constraintInfo *checkexprs; /* CHECK constraints */
+	char	   *partkeydef;		/* partition key definition */
 
 	/*
 	 * Stuff computed only for dumpable tables.
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 6275a68..10d924a 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -861,6 +861,7 @@ permissionsList(const char *pattern)
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'S' THEN '%s'"
@@ -870,6 +871,7 @@ permissionsList(const char *pattern)
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("sequence"),
@@ -920,7 +922,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
@@ -1567,8 +1569,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)");
 	}
 
@@ -1633,6 +1635,14 @@ describeOneTableDetails(const char *schemaname,
 			printfPQExpBuffer(&title, _("Foreign table \"%s.%s\""),
 							  schemaname, relationname);
 			break;
+		case 'P':
+			if (tableinfo.relpersistence == 'u')
+				printfPQExpBuffer(&title, _("Unlogged table \"%s.%s\""),
+								  schemaname, relationname);
+			else
+				printfPQExpBuffer(&title, _("Table \"%s.%s\""),
+								  schemaname, relationname);
+			break;
 		default:
 			/* untranslated unknown relkind */
 			printfPQExpBuffer(&title, "?%c? \"%s.%s\"",
@@ -1646,8 +1656,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");
@@ -1667,12 +1677,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");
 	}
 
@@ -1772,7 +1782,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);
@@ -1780,14 +1790,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 (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 */
@@ -1926,7 +1955,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;
@@ -2485,7 +2514,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;
@@ -2696,7 +2725,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
@@ -3024,6 +3053,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'i' THEN '%s'"
@@ -3035,6 +3065,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("index"),
@@ -3079,7 +3110,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 019f75a..5e5c370 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/pg_proc.h b/src/include/catalog/pg_proc.h
index e2d08ba..b45688b 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -1980,6 +1980,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/utils/builtins.h b/src/include/utils/builtins.h
index 2ae212a..e800647 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -729,6 +729,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/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 708232d..2fec847 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -408,3 +408,29 @@ CREATE TABLE no_inh_con_parted (
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
 ERROR:  cannot add NO INHERIT constraint to partitioned table "no_inh_con_parted"
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+Table "public.describe_range_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition Key: RANGE ((a + b))
+
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+Table "public.describe_list_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition Key: LIST (a)
+
+DROP TABLE describe_range_key, describe_list_key;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 6e4a8be..4dd6a0a 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -402,3 +402,16 @@ CREATE TABLE no_inh_con_parted (
 	a int,
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
+
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+DROP TABLE describe_range_key, describe_list_key;
-- 
1.7.1


--------------6EB5B318125CB7DB1A4FD229
Content-Type: text/x-diff;
 name="0003-Catalog-and-DDL-for-partitions-3.patch"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
 filename="0003-Catalog-and-DDL-for-partitions-3.patch"



^ permalink  raw  reply  [nested|flat] 110+ messages in thread

* [PATCH 2/9] psql and pg_dump support for partitioned tables.
@ 2016-07-12 08:20  amit <[email protected]>
  0 siblings, 0 replies; 110+ messages in thread

From: amit @ 2016-07-12 08:20 UTC (permalink / raw)

Takes care of both the partition key deparse stuff and the new relkind.
---
 src/backend/utils/adt/ruleutils.c          |  140 ++++++++++++++++++++++++++++
 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/pg_proc.h              |    2 +
 src/include/utils/builtins.h               |    1 +
 src/test/regress/expected/create_table.out |   26 +++++
 src/test/regress/sql/create_table.sql      |   13 +++
 9 files changed, 260 insertions(+), 26 deletions(-)

diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 8a81d7a..03be202 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_table.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
@@ -315,6 +316,7 @@ static char *pg_get_indexdef_worker(Oid indexrelid, int colno,
 					   const Oid *excludeOps,
 					   bool attrsOnly, bool showTblSpc,
 					   int prettyFlags, bool missing_ok);
+static char *pg_get_partkeydef_worker(Oid relid, int prettyFlags);
 static char *pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 							int prettyFlags, bool missing_ok);
 static text *pg_get_expr_worker(text *expr, Oid relid, const char *relname,
@@ -1389,6 +1391,144 @@ 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 [ opclass_name ] [, ...])
+ */
+Datum
+pg_get_partkeydef(PG_FUNCTION_ARGS)
+{
+	Oid			relid = PG_GETARG_OID(0);
+	int			prettyFlags;
+
+	prettyFlags = PRETTYFLAG_INDENT;
+	PG_RETURN_TEXT_P(string_to_text(pg_get_partkeydef_worker(relid,
+									prettyFlags)));
+}
+
+/*
+ * Internal workhorse to decompile a partition key definition.
+ */
+static char *
+pg_get_partkeydef_worker(Oid relid, int prettyFlags)
+{
+	Form_pg_partitioned_table	form;
+	HeapTuple	tuple;
+	oidvector  *partclass;
+	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_table) GETSTRUCT(tuple);
+
+	Assert(form->partrelid == relid);
+
+	/* Must get partclass, and partexprs the hard way */
+	datum = SysCacheGetAttr(PARTEDRELID, tuple,
+							Anum_pg_partitioned_table_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_table_partexprbin))
+	{
+		Datum		exprsDatum;
+		bool		isnull;
+		char	   *exprsString;
+
+		exprsDatum = SysCacheGetAttr(PARTEDRELID, tuple,
+									 Anum_pg_partitioned_table_partexprbin, &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);
+
+	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,
+											0, 0);
+
+			appendStringInfoString(&buf, 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/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index ba9c276..c805a84 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -1253,9 +1253,10 @@ expand_table_name_patterns(Archive *fout,
 						  "SELECT c.oid"
 						  "\nFROM pg_catalog.pg_class c"
 		"\n     LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace"
-					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c')\n",
+					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c')\n",
 						  RELKIND_RELATION, RELKIND_SEQUENCE, RELKIND_VIEW,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 		processSQLNamePattern(GetConnection(fout), query, cell->val, true,
 							  false, "n.nspname", "c.relname", NULL,
 							  "pg_catalog.pg_table_is_visible(c.oid)");
@@ -2125,6 +2126,9 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo, bool oids)
 	/* Skip FOREIGN TABLEs (no data to dump) */
 	if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 		return;
+	/* Skip partitioned tables (data in partitions) */
+	if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+		return;
 
 	/* Don't dump data in unlogged tables, if so requested */
 	if (tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED &&
@@ -5204,6 +5208,7 @@ getTables(Archive *fout, int *numTables)
 	int			i_reloftype;
 	int			i_relpages;
 	int			i_changed_acl;
+	int			i_partkeydef;
 
 	/* Make sure we are in proper schema */
 	selectSourceSchema(fout, "pg_catalog");
@@ -5289,7 +5294,8 @@ getTables(Archive *fout, int *numTables)
 						  "OR %s IS NOT NULL "
 						  "OR %s IS NOT NULL"
 						  "))"
-						  "AS changed_acl "
+						  "AS changed_acl, "
+						  "CASE WHEN c.relkind = 'P' THEN pg_catalog.pg_get_partkeydef(c.oid) ELSE NULL END AS partkeydef "
 						  "FROM pg_class c "
 						  "LEFT JOIN pg_depend d ON "
 						  "(c.relkind = '%c' AND "
@@ -5301,7 +5307,7 @@ getTables(Archive *fout, int *numTables)
 						  "(c.oid = pip.objoid "
 						  "AND pip.classoid = 'pg_class'::regclass "
 						  "AND pip.objsubid = 0) "
-				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c') "
+				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c', '%c') "
 						  "ORDER BY c.oid",
 						  acl_subquery->data,
 						  racl_subquery->data,
@@ -5315,7 +5321,8 @@ getTables(Archive *fout, int *numTables)
 						  RELKIND_SEQUENCE,
 						  RELKIND_RELATION, RELKIND_SEQUENCE,
 						  RELKIND_VIEW, RELKIND_COMPOSITE_TYPE,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 
 		destroyPQExpBuffer(acl_subquery);
 		destroyPQExpBuffer(racl_subquery);
@@ -5877,6 +5884,7 @@ getTables(Archive *fout, int *numTables)
 	i_toastreloptions = PQfnumber(res, "toast_reloptions");
 	i_reloftype = PQfnumber(res, "reloftype");
 	i_changed_acl = PQfnumber(res, "changed_acl");
+	i_partkeydef = PQfnumber(res, "partkeydef");
 
 	if (dopt->lockWaitTimeout && fout->remoteVersion >= 70300)
 	{
@@ -5947,6 +5955,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 */
 
@@ -5991,7 +6000,9 @@ getTables(Archive *fout, int *numTables)
 		 * We only need to lock the table for certain components; see
 		 * pg_dump.h
 		 */
-		if (tblinfo[i].dobj.dump && tblinfo[i].relkind == RELKIND_RELATION &&
+		if (tblinfo[i].dobj.dump &&
+			(tblinfo[i].relkind == RELKIND_RELATION ||
+			 tblinfo->relkind == RELKIND_PARTITIONED_TABLE) &&
 			(tblinfo[i].dobj.dump & DUMP_COMPONENTS_REQUIRING_LOCK))
 		{
 			resetPQExpBuffer(query);
@@ -6093,7 +6104,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);
 
@@ -15446,6 +15460,9 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 				appendPQExpBufferChar(q, ')');
 			}
 
+			if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+				appendPQExpBuffer(q, "\nPARTITION BY %s", tbinfo->partkeydef);
+
 			if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 				appendPQExpBuffer(q, "\nSERVER %s", fmtId(srvname));
 		}
@@ -15506,6 +15523,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 		 */
 		if (dopt->binary_upgrade &&
 			(tbinfo->relkind == RELKIND_RELATION ||
+			 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 			 tbinfo->relkind == RELKIND_FOREIGN_TABLE))
 		{
 			for (j = 0; j < tbinfo->numatts; j++)
@@ -15524,7 +15542,8 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 					appendStringLiteralAH(q, fmtId(tbinfo->dobj.name), fout);
 					appendPQExpBufferStr(q, "::pg_catalog.regclass;\n");
 
-					if (tbinfo->relkind == RELKIND_RELATION)
+					if (tbinfo->relkind == RELKIND_RELATION ||
+						tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
 						appendPQExpBuffer(q, "ALTER TABLE ONLY %s ",
 										  fmtId(tbinfo->dobj.name));
 					else
@@ -15741,6 +15760,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 	 * dump properties we only have ALTER TABLE syntax for
 	 */
 	if ((tbinfo->relkind == RELKIND_RELATION ||
+		 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 		 tbinfo->relkind == RELKIND_MATVIEW) &&
 		tbinfo->relreplident != REPLICA_IDENTITY_DEFAULT)
 	{
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 2bfa2d9..0292859 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -310,6 +310,7 @@ typedef struct _tableInfo
 	bool	   *inhNotNull;		/* true if NOT NULL is inherited */
 	struct _attrDefInfo **attrdefs;		/* DEFAULT expressions */
 	struct _constraintInfo *checkexprs; /* CHECK constraints */
+	char	   *partkeydef;		/* partition key definition */
 
 	/*
 	 * Stuff computed only for dumpable tables.
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 6275a68..10d924a 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -861,6 +861,7 @@ permissionsList(const char *pattern)
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'S' THEN '%s'"
@@ -870,6 +871,7 @@ permissionsList(const char *pattern)
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("sequence"),
@@ -920,7 +922,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
@@ -1567,8 +1569,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)");
 	}
 
@@ -1633,6 +1635,14 @@ describeOneTableDetails(const char *schemaname,
 			printfPQExpBuffer(&title, _("Foreign table \"%s.%s\""),
 							  schemaname, relationname);
 			break;
+		case 'P':
+			if (tableinfo.relpersistence == 'u')
+				printfPQExpBuffer(&title, _("Unlogged table \"%s.%s\""),
+								  schemaname, relationname);
+			else
+				printfPQExpBuffer(&title, _("Table \"%s.%s\""),
+								  schemaname, relationname);
+			break;
 		default:
 			/* untranslated unknown relkind */
 			printfPQExpBuffer(&title, "?%c? \"%s.%s\"",
@@ -1646,8 +1656,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");
@@ -1667,12 +1677,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");
 	}
 
@@ -1772,7 +1782,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);
@@ -1780,14 +1790,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 (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 */
@@ -1926,7 +1955,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;
@@ -2485,7 +2514,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;
@@ -2696,7 +2725,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
@@ -3024,6 +3053,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'i' THEN '%s'"
@@ -3035,6 +3065,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("index"),
@@ -3079,7 +3110,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 019f75a..5e5c370 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/pg_proc.h b/src/include/catalog/pg_proc.h
index e2d08ba..b45688b 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -1980,6 +1980,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/utils/builtins.h b/src/include/utils/builtins.h
index 2ae212a..e800647 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -729,6 +729,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/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 708232d..2fec847 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -408,3 +408,29 @@ CREATE TABLE no_inh_con_parted (
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
 ERROR:  cannot add NO INHERIT constraint to partitioned table "no_inh_con_parted"
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+Table "public.describe_range_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition Key: RANGE ((a + b))
+
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+Table "public.describe_list_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition Key: LIST (a)
+
+DROP TABLE describe_range_key, describe_list_key;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 6e4a8be..4dd6a0a 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -402,3 +402,16 @@ CREATE TABLE no_inh_con_parted (
 	a int,
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
+
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+DROP TABLE describe_range_key, describe_list_key;
-- 
1.7.1


--------------4012470FC48539D6F6714CEA
Content-Type: text/x-diff;
 name="0003-Catalog-and-DDL-for-partitions-4.patch"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
 filename="0003-Catalog-and-DDL-for-partitions-4.patch"



^ permalink  raw  reply  [nested|flat] 110+ messages in thread

* [PATCH 2/9] psql and pg_dump support for partitioned tables.
@ 2016-07-12 08:20  amit <[email protected]>
  0 siblings, 0 replies; 110+ messages in thread

From: amit @ 2016-07-12 08:20 UTC (permalink / raw)

Takes care of both the partition key deparse stuff and the new relkind.
---
 src/backend/utils/adt/ruleutils.c          |  140 ++++++++++++++++++++++++++++
 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/pg_proc.h              |    2 +
 src/include/utils/builtins.h               |    1 +
 src/test/regress/expected/create_table.out |   26 +++++
 src/test/regress/sql/create_table.sql      |   13 +++
 9 files changed, 260 insertions(+), 26 deletions(-)

diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 8a81d7a..03be202 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_table.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
@@ -315,6 +316,7 @@ static char *pg_get_indexdef_worker(Oid indexrelid, int colno,
 					   const Oid *excludeOps,
 					   bool attrsOnly, bool showTblSpc,
 					   int prettyFlags, bool missing_ok);
+static char *pg_get_partkeydef_worker(Oid relid, int prettyFlags);
 static char *pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 							int prettyFlags, bool missing_ok);
 static text *pg_get_expr_worker(text *expr, Oid relid, const char *relname,
@@ -1389,6 +1391,144 @@ 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 [ opclass_name ] [, ...])
+ */
+Datum
+pg_get_partkeydef(PG_FUNCTION_ARGS)
+{
+	Oid			relid = PG_GETARG_OID(0);
+	int			prettyFlags;
+
+	prettyFlags = PRETTYFLAG_INDENT;
+	PG_RETURN_TEXT_P(string_to_text(pg_get_partkeydef_worker(relid,
+									prettyFlags)));
+}
+
+/*
+ * Internal workhorse to decompile a partition key definition.
+ */
+static char *
+pg_get_partkeydef_worker(Oid relid, int prettyFlags)
+{
+	Form_pg_partitioned_table	form;
+	HeapTuple	tuple;
+	oidvector  *partclass;
+	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_table) GETSTRUCT(tuple);
+
+	Assert(form->partrelid == relid);
+
+	/* Must get partclass, and partexprs the hard way */
+	datum = SysCacheGetAttr(PARTEDRELID, tuple,
+							Anum_pg_partitioned_table_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_table_partexprbin))
+	{
+		Datum		exprsDatum;
+		bool		isnull;
+		char	   *exprsString;
+
+		exprsDatum = SysCacheGetAttr(PARTEDRELID, tuple,
+									 Anum_pg_partitioned_table_partexprbin, &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);
+
+	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,
+											0, 0);
+
+			appendStringInfoString(&buf, 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/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index ba9c276..c805a84 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -1253,9 +1253,10 @@ expand_table_name_patterns(Archive *fout,
 						  "SELECT c.oid"
 						  "\nFROM pg_catalog.pg_class c"
 		"\n     LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace"
-					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c')\n",
+					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c')\n",
 						  RELKIND_RELATION, RELKIND_SEQUENCE, RELKIND_VIEW,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 		processSQLNamePattern(GetConnection(fout), query, cell->val, true,
 							  false, "n.nspname", "c.relname", NULL,
 							  "pg_catalog.pg_table_is_visible(c.oid)");
@@ -2125,6 +2126,9 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo, bool oids)
 	/* Skip FOREIGN TABLEs (no data to dump) */
 	if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 		return;
+	/* Skip partitioned tables (data in partitions) */
+	if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+		return;
 
 	/* Don't dump data in unlogged tables, if so requested */
 	if (tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED &&
@@ -5204,6 +5208,7 @@ getTables(Archive *fout, int *numTables)
 	int			i_reloftype;
 	int			i_relpages;
 	int			i_changed_acl;
+	int			i_partkeydef;
 
 	/* Make sure we are in proper schema */
 	selectSourceSchema(fout, "pg_catalog");
@@ -5289,7 +5294,8 @@ getTables(Archive *fout, int *numTables)
 						  "OR %s IS NOT NULL "
 						  "OR %s IS NOT NULL"
 						  "))"
-						  "AS changed_acl "
+						  "AS changed_acl, "
+						  "CASE WHEN c.relkind = 'P' THEN pg_catalog.pg_get_partkeydef(c.oid) ELSE NULL END AS partkeydef "
 						  "FROM pg_class c "
 						  "LEFT JOIN pg_depend d ON "
 						  "(c.relkind = '%c' AND "
@@ -5301,7 +5307,7 @@ getTables(Archive *fout, int *numTables)
 						  "(c.oid = pip.objoid "
 						  "AND pip.classoid = 'pg_class'::regclass "
 						  "AND pip.objsubid = 0) "
-				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c') "
+				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c', '%c') "
 						  "ORDER BY c.oid",
 						  acl_subquery->data,
 						  racl_subquery->data,
@@ -5315,7 +5321,8 @@ getTables(Archive *fout, int *numTables)
 						  RELKIND_SEQUENCE,
 						  RELKIND_RELATION, RELKIND_SEQUENCE,
 						  RELKIND_VIEW, RELKIND_COMPOSITE_TYPE,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 
 		destroyPQExpBuffer(acl_subquery);
 		destroyPQExpBuffer(racl_subquery);
@@ -5877,6 +5884,7 @@ getTables(Archive *fout, int *numTables)
 	i_toastreloptions = PQfnumber(res, "toast_reloptions");
 	i_reloftype = PQfnumber(res, "reloftype");
 	i_changed_acl = PQfnumber(res, "changed_acl");
+	i_partkeydef = PQfnumber(res, "partkeydef");
 
 	if (dopt->lockWaitTimeout && fout->remoteVersion >= 70300)
 	{
@@ -5947,6 +5955,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 */
 
@@ -5991,7 +6000,9 @@ getTables(Archive *fout, int *numTables)
 		 * We only need to lock the table for certain components; see
 		 * pg_dump.h
 		 */
-		if (tblinfo[i].dobj.dump && tblinfo[i].relkind == RELKIND_RELATION &&
+		if (tblinfo[i].dobj.dump &&
+			(tblinfo[i].relkind == RELKIND_RELATION ||
+			 tblinfo->relkind == RELKIND_PARTITIONED_TABLE) &&
 			(tblinfo[i].dobj.dump & DUMP_COMPONENTS_REQUIRING_LOCK))
 		{
 			resetPQExpBuffer(query);
@@ -6093,7 +6104,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);
 
@@ -15446,6 +15460,9 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 				appendPQExpBufferChar(q, ')');
 			}
 
+			if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+				appendPQExpBuffer(q, "\nPARTITION BY %s", tbinfo->partkeydef);
+
 			if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 				appendPQExpBuffer(q, "\nSERVER %s", fmtId(srvname));
 		}
@@ -15506,6 +15523,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 		 */
 		if (dopt->binary_upgrade &&
 			(tbinfo->relkind == RELKIND_RELATION ||
+			 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 			 tbinfo->relkind == RELKIND_FOREIGN_TABLE))
 		{
 			for (j = 0; j < tbinfo->numatts; j++)
@@ -15524,7 +15542,8 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 					appendStringLiteralAH(q, fmtId(tbinfo->dobj.name), fout);
 					appendPQExpBufferStr(q, "::pg_catalog.regclass;\n");
 
-					if (tbinfo->relkind == RELKIND_RELATION)
+					if (tbinfo->relkind == RELKIND_RELATION ||
+						tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
 						appendPQExpBuffer(q, "ALTER TABLE ONLY %s ",
 										  fmtId(tbinfo->dobj.name));
 					else
@@ -15741,6 +15760,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 	 * dump properties we only have ALTER TABLE syntax for
 	 */
 	if ((tbinfo->relkind == RELKIND_RELATION ||
+		 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 		 tbinfo->relkind == RELKIND_MATVIEW) &&
 		tbinfo->relreplident != REPLICA_IDENTITY_DEFAULT)
 	{
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 2bfa2d9..0292859 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -310,6 +310,7 @@ typedef struct _tableInfo
 	bool	   *inhNotNull;		/* true if NOT NULL is inherited */
 	struct _attrDefInfo **attrdefs;		/* DEFAULT expressions */
 	struct _constraintInfo *checkexprs; /* CHECK constraints */
+	char	   *partkeydef;		/* partition key definition */
 
 	/*
 	 * Stuff computed only for dumpable tables.
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 6275a68..10d924a 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -861,6 +861,7 @@ permissionsList(const char *pattern)
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'S' THEN '%s'"
@@ -870,6 +871,7 @@ permissionsList(const char *pattern)
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("sequence"),
@@ -920,7 +922,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
@@ -1567,8 +1569,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)");
 	}
 
@@ -1633,6 +1635,14 @@ describeOneTableDetails(const char *schemaname,
 			printfPQExpBuffer(&title, _("Foreign table \"%s.%s\""),
 							  schemaname, relationname);
 			break;
+		case 'P':
+			if (tableinfo.relpersistence == 'u')
+				printfPQExpBuffer(&title, _("Unlogged table \"%s.%s\""),
+								  schemaname, relationname);
+			else
+				printfPQExpBuffer(&title, _("Table \"%s.%s\""),
+								  schemaname, relationname);
+			break;
 		default:
 			/* untranslated unknown relkind */
 			printfPQExpBuffer(&title, "?%c? \"%s.%s\"",
@@ -1646,8 +1656,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");
@@ -1667,12 +1677,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");
 	}
 
@@ -1772,7 +1782,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);
@@ -1780,14 +1790,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 (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 */
@@ -1926,7 +1955,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;
@@ -2485,7 +2514,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;
@@ -2696,7 +2725,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
@@ -3024,6 +3053,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'i' THEN '%s'"
@@ -3035,6 +3065,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("index"),
@@ -3079,7 +3110,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 50a45eb..8284a9c 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/pg_proc.h b/src/include/catalog/pg_proc.h
index e2d08ba..b45688b 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -1980,6 +1980,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/utils/builtins.h b/src/include/utils/builtins.h
index 2ae212a..e800647 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -729,6 +729,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/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 0af1422..91ba1be 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -412,3 +412,29 @@ CREATE TABLE no_inh_con_parted (
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
 ERROR:  cannot add NO INHERIT constraint to partitioned table "no_inh_con_parted"
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+Table "public.describe_range_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition Key: RANGE ((a + b))
+
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+Table "public.describe_list_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition Key: LIST (a)
+
+DROP TABLE describe_range_key, describe_list_key;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 418ac55..b75542d 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -406,3 +406,16 @@ CREATE TABLE no_inh_con_parted (
 	a int,
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
+
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+DROP TABLE describe_range_key, describe_list_key;
-- 
1.7.1


--------------D008BA677674C4836D7B65AD
Content-Type: text/x-diff;
 name="0003-Catalog-and-DDL-for-partitions-5.patch"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
 filename="0003-Catalog-and-DDL-for-partitions-5.patch"



^ permalink  raw  reply  [nested|flat] 110+ messages in thread

* [PATCH 2/8] psql and pg_dump support for partitioned tables.
@ 2016-07-12 08:20  amit <[email protected]>
  0 siblings, 0 replies; 110+ messages in thread

From: amit @ 2016-07-12 08:20 UTC (permalink / raw)

Takes care of both the partition key deparse stuff and the new relkind.
---
 src/backend/utils/adt/ruleutils.c          |  147 ++++++++++++++++++++++++++++
 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/pg_proc.h              |    2 +
 src/include/utils/builtins.h               |    1 +
 src/test/regress/expected/create_table.out |   26 +++++
 src/test/regress/sql/create_table.sql      |   13 +++
 9 files changed, 267 insertions(+), 26 deletions(-)

diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index ec966c7..4691b5b 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, bool missing_ok);
+static char *pg_get_partkeydef_worker(Oid relId, int prettyFlags);
 static char *pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 							int prettyFlags, bool missing_ok);
 static text *pg_get_expr_worker(text *expr, Oid relid, const char *relname,
@@ -1389,6 +1391,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 [ 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/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 4ee10fc..a127672 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -1253,9 +1253,10 @@ expand_table_name_patterns(Archive *fout,
 						  "SELECT c.oid"
 						  "\nFROM pg_catalog.pg_class c"
 		"\n     LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace"
-					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c')\n",
+					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c')\n",
 						  RELKIND_RELATION, RELKIND_SEQUENCE, RELKIND_VIEW,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_REL);
 		processSQLNamePattern(GetConnection(fout), query, cell->val, true,
 							  false, "n.nspname", "c.relname", NULL,
 							  "pg_catalog.pg_table_is_visible(c.oid)");
@@ -2117,6 +2118,9 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo, bool oids)
 	/* Skip FOREIGN TABLEs (no data to dump) */
 	if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 		return;
+	/* Skip partitioned tables (data in partitions) */
+	if (tbinfo->relkind == RELKIND_PARTITIONED_REL)
+		return;
 
 	/* Don't dump data in unlogged tables, if so requested */
 	if (tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED &&
@@ -5195,6 +5199,7 @@ getTables(Archive *fout, int *numTables)
 	int			i_reloftype;
 	int			i_relpages;
 	int			i_changed_acl;
+	int			i_partkeydef;
 
 	/* Make sure we are in proper schema */
 	selectSourceSchema(fout, "pg_catalog");
@@ -5280,7 +5285,8 @@ getTables(Archive *fout, int *numTables)
 						  "OR %s IS NOT NULL "
 						  "OR %s IS NOT NULL"
 						  "))"
-						  "AS changed_acl "
+						  "AS changed_acl, "
+						  "CASE WHEN c.relkind = 'P' THEN pg_catalog.pg_get_partkeydef(c.oid) ELSE NULL END AS partkeydef "
 						  "FROM pg_class c "
 						  "LEFT JOIN pg_depend d ON "
 						  "(c.relkind = '%c' AND "
@@ -5292,7 +5298,7 @@ getTables(Archive *fout, int *numTables)
 						  "(c.oid = pip.objoid "
 						  "AND pip.classoid = 'pg_class'::regclass "
 						  "AND pip.objsubid = 0) "
-				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c') "
+				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c', '%c') "
 						  "ORDER BY c.oid",
 						  acl_subquery->data,
 						  racl_subquery->data,
@@ -5306,7 +5312,8 @@ getTables(Archive *fout, int *numTables)
 						  RELKIND_SEQUENCE,
 						  RELKIND_RELATION, RELKIND_SEQUENCE,
 						  RELKIND_VIEW, RELKIND_COMPOSITE_TYPE,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_REL);
 
 		destroyPQExpBuffer(acl_subquery);
 		destroyPQExpBuffer(racl_subquery);
@@ -5868,6 +5875,7 @@ getTables(Archive *fout, int *numTables)
 	i_toastreloptions = PQfnumber(res, "toast_reloptions");
 	i_reloftype = PQfnumber(res, "reloftype");
 	i_changed_acl = PQfnumber(res, "changed_acl");
+	i_partkeydef = PQfnumber(res, "partkeydef");
 
 	if (dopt->lockWaitTimeout && fout->remoteVersion >= 70300)
 	{
@@ -5938,6 +5946,7 @@ getTables(Archive *fout, int *numTables)
 		else
 			tblinfo[i].checkoption = pg_strdup(PQgetvalue(res, i, i_checkoption));
 		tblinfo[i].toast_reloptions = pg_strdup(PQgetvalue(res, i, i_toastreloptions));
+		tblinfo[i].partkeydef = pg_strdup(PQgetvalue(res, i, i_partkeydef));
 
 		/* other fields were zeroed above */
 
@@ -5982,7 +5991,9 @@ getTables(Archive *fout, int *numTables)
 		 * We only need to lock the table for certain components; see
 		 * pg_dump.h
 		 */
-		if (tblinfo[i].dobj.dump && tblinfo[i].relkind == RELKIND_RELATION &&
+		if (tblinfo[i].dobj.dump &&
+			(tblinfo[i].relkind == RELKIND_RELATION ||
+			 tblinfo->relkind == RELKIND_PARTITIONED_REL) &&
 			(tblinfo[i].dobj.dump & DUMP_COMPONENTS_REQUIRING_LOCK))
 		{
 			resetPQExpBuffer(query);
@@ -6083,7 +6094,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);
 
@@ -15436,6 +15450,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));
 		}
@@ -15496,6 +15513,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++)
@@ -15514,7 +15532,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
@@ -15731,6 +15750,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 	 * dump properties we only have ALTER TABLE syntax for
 	 */
 	if ((tbinfo->relkind == RELKIND_RELATION ||
+		 tbinfo->relkind == RELKIND_PARTITIONED_REL ||
 		 tbinfo->relkind == RELKIND_MATVIEW) &&
 		tbinfo->relreplident != REPLICA_IDENTITY_DEFAULT)
 	{
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 2bfa2d9..0292859 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -310,6 +310,7 @@ typedef struct _tableInfo
 	bool	   *inhNotNull;		/* true if NOT NULL is inherited */
 	struct _attrDefInfo **attrdefs;		/* DEFAULT expressions */
 	struct _constraintInfo *checkexprs; /* CHECK constraints */
+	char	   *partkeydef;		/* partition key definition */
 
 	/*
 	 * Stuff computed only for dumpable tables.
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 27be102..3ba8a90 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -856,6 +856,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"),
@@ -865,6 +866,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");
@@ -911,7 +913,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
@@ -1555,8 +1557,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)");
 	}
 
@@ -1621,6 +1623,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\"",
@@ -1634,8 +1644,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");
@@ -1655,12 +1665,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");
 	}
 
@@ -1760,7 +1770,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);
@@ -1768,14 +1778,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 */
@@ -1914,7 +1943,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;
@@ -2473,7 +2502,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;
@@ -2684,7 +2713,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
@@ -3018,6 +3047,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"),
@@ -3029,6 +3059,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"));
 
@@ -3067,7 +3098,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 
 	appendPQExpBufferStr(&buf, "\nWHERE c.relkind IN (");
 	if (showTables)
-		appendPQExpBufferStr(&buf, "'r',");
+		appendPQExpBufferStr(&buf, "'r', 'P',");
 	if (showViews)
 		appendPQExpBufferStr(&buf, "'v',");
 	if (showMatViews)
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 8469d9f..cd4eb7c 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -427,7 +427,7 @@ static const SchemaQuery Query_for_list_of_tables = {
 	/* catname */
 	"pg_catalog.pg_class c",
 	/* selcondition */
-	"c.relkind IN ('r')",
+	"c.relkind IN ('r', 'P')",
 	/* viscondition */
 	"pg_catalog.pg_table_is_visible(c.oid)",
 	/* namespace */
@@ -458,7 +458,7 @@ static const SchemaQuery Query_for_list_of_updatables = {
 	/* catname */
 	"pg_catalog.pg_class c",
 	/* selcondition */
-	"c.relkind IN ('r', 'f', 'v')",
+	"c.relkind IN ('r', 'f', 'v', 'P')",
 	/* viscondition */
 	"pg_catalog.pg_table_is_visible(c.oid)",
 	/* namespace */
@@ -488,7 +488,7 @@ static const SchemaQuery Query_for_list_of_tsvmf = {
 	/* catname */
 	"pg_catalog.pg_class c",
 	/* selcondition */
-	"c.relkind IN ('r', 'S', 'v', 'm', 'f')",
+	"c.relkind IN ('r', 'S', 'v', 'm', 'f', 'P')",
 	/* viscondition */
 	"pg_catalog.pg_table_is_visible(c.oid)",
 	/* namespace */
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index 270dd21..5a444d9 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/utils/builtins.h b/src/include/utils/builtins.h
index 8cebc86..351bfd3 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -722,6 +722,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/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index fb7a1b3..4fc8c06 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -413,3 +413,29 @@ SELECT connoinherit FROM pg_constraint WHERE conrelid = 'no_inh_con_parted'::reg
 (1 row)
 
 DROP TABLE no_inh_con_parted;
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+Partitioned table "public.describe_range_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition Key: PARTITION BY RANGE (((a + b)))
+
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+Partitioned table "public.describe_list_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition Key: PARTITION BY LIST (a)
+
+DROP TABLE describe_range_key, describe_list_key;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index cb17517..7c7a18a 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -404,3 +404,16 @@ CREATE TABLE no_inh_con_parted (
 ) PARTITION BY RANGE (a);
 SELECT connoinherit FROM pg_constraint WHERE conrelid = 'no_inh_con_parted'::regclass;
 DROP TABLE no_inh_con_parted;
+
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+DROP TABLE describe_range_key, describe_list_key;
-- 
1.7.1


--------------8C4C48E640631A327D3D2826
Content-Type: text/x-diff;
 name="0003-Catalog-and-DDL-for-partitions.patch"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
 filename="0003-Catalog-and-DDL-for-partitions.patch"



^ permalink  raw  reply  [nested|flat] 110+ messages in thread

* [PATCH 2/9] psql and pg_dump support for partitioned tables.
@ 2016-07-12 08:20  amit <[email protected]>
  0 siblings, 0 replies; 110+ messages in thread

From: amit @ 2016-07-12 08:20 UTC (permalink / raw)

Takes care of both the partition key deparse stuff and the new relkind.
---
 src/backend/utils/adt/ruleutils.c          |  140 ++++++++++++++++++++++++++++
 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/pg_proc.h              |    2 +
 src/include/utils/builtins.h               |    1 +
 src/test/regress/expected/create_table.out |   26 +++++
 src/test/regress/sql/create_table.sql      |   13 +++
 9 files changed, 260 insertions(+), 26 deletions(-)

diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 8a81d7a..77ce807 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_table.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
@@ -315,6 +316,7 @@ static char *pg_get_indexdef_worker(Oid indexrelid, int colno,
 					   const Oid *excludeOps,
 					   bool attrsOnly, bool showTblSpc,
 					   int prettyFlags, bool missing_ok);
+static char *pg_get_partkeydef_worker(Oid relId, int prettyFlags);
 static char *pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 							int prettyFlags, bool missing_ok);
 static text *pg_get_expr_worker(text *expr, Oid relid, const char *relname,
@@ -1389,6 +1391,144 @@ 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 [ 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_table	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_table) GETSTRUCT(tuple);
+
+	Assert(form->partedrelid == relId);
+
+	/* Must get partclass, and partexprs the hard way */
+	datum = SysCacheGetAttr(PARTEDRELID, tuple,
+							Anum_pg_partitioned_table_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_table_partexprbin))
+	{
+		Datum		exprsDatum;
+		bool		isnull;
+		char	   *exprsString;
+
+		exprsDatum = SysCacheGetAttr(PARTEDRELID, tuple,
+									 Anum_pg_partitioned_table_partexprbin, &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);
+
+	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,
+											0, 0);
+
+			appendStringInfoString(&buf, 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/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index a5c2d09..c418ba7 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -1253,9 +1253,10 @@ expand_table_name_patterns(Archive *fout,
 						  "SELECT c.oid"
 						  "\nFROM pg_catalog.pg_class c"
 		"\n     LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace"
-					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c')\n",
+					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c')\n",
 						  RELKIND_RELATION, RELKIND_SEQUENCE, RELKIND_VIEW,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 		processSQLNamePattern(GetConnection(fout), query, cell->val, true,
 							  false, "n.nspname", "c.relname", NULL,
 							  "pg_catalog.pg_table_is_visible(c.oid)");
@@ -2117,6 +2118,9 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo, bool oids)
 	/* Skip FOREIGN TABLEs (no data to dump) */
 	if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 		return;
+	/* Skip partitioned tables (data in partitions) */
+	if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+		return;
 
 	/* Don't dump data in unlogged tables, if so requested */
 	if (tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED &&
@@ -5195,6 +5199,7 @@ getTables(Archive *fout, int *numTables)
 	int			i_reloftype;
 	int			i_relpages;
 	int			i_changed_acl;
+	int			i_partkeydef;
 
 	/* Make sure we are in proper schema */
 	selectSourceSchema(fout, "pg_catalog");
@@ -5280,7 +5285,8 @@ getTables(Archive *fout, int *numTables)
 						  "OR %s IS NOT NULL "
 						  "OR %s IS NOT NULL"
 						  "))"
-						  "AS changed_acl "
+						  "AS changed_acl, "
+						  "CASE WHEN c.relkind = 'P' THEN pg_catalog.pg_get_partkeydef(c.oid) ELSE NULL END AS partkeydef "
 						  "FROM pg_class c "
 						  "LEFT JOIN pg_depend d ON "
 						  "(c.relkind = '%c' AND "
@@ -5292,7 +5298,7 @@ getTables(Archive *fout, int *numTables)
 						  "(c.oid = pip.objoid "
 						  "AND pip.classoid = 'pg_class'::regclass "
 						  "AND pip.objsubid = 0) "
-				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c') "
+				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c', '%c') "
 						  "ORDER BY c.oid",
 						  acl_subquery->data,
 						  racl_subquery->data,
@@ -5306,7 +5312,8 @@ getTables(Archive *fout, int *numTables)
 						  RELKIND_SEQUENCE,
 						  RELKIND_RELATION, RELKIND_SEQUENCE,
 						  RELKIND_VIEW, RELKIND_COMPOSITE_TYPE,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 
 		destroyPQExpBuffer(acl_subquery);
 		destroyPQExpBuffer(racl_subquery);
@@ -5868,6 +5875,7 @@ getTables(Archive *fout, int *numTables)
 	i_toastreloptions = PQfnumber(res, "toast_reloptions");
 	i_reloftype = PQfnumber(res, "reloftype");
 	i_changed_acl = PQfnumber(res, "changed_acl");
+	i_partkeydef = PQfnumber(res, "partkeydef");
 
 	if (dopt->lockWaitTimeout && fout->remoteVersion >= 70300)
 	{
@@ -5938,6 +5946,7 @@ getTables(Archive *fout, int *numTables)
 		else
 			tblinfo[i].checkoption = pg_strdup(PQgetvalue(res, i, i_checkoption));
 		tblinfo[i].toast_reloptions = pg_strdup(PQgetvalue(res, i, i_toastreloptions));
+		tblinfo[i].partkeydef = pg_strdup(PQgetvalue(res, i, i_partkeydef));
 
 		/* other fields were zeroed above */
 
@@ -5982,7 +5991,9 @@ getTables(Archive *fout, int *numTables)
 		 * We only need to lock the table for certain components; see
 		 * pg_dump.h
 		 */
-		if (tblinfo[i].dobj.dump && tblinfo[i].relkind == RELKIND_RELATION &&
+		if (tblinfo[i].dobj.dump &&
+			(tblinfo[i].relkind == RELKIND_RELATION ||
+			 tblinfo->relkind == RELKIND_PARTITIONED_TABLE) &&
 			(tblinfo[i].dobj.dump & DUMP_COMPONENTS_REQUIRING_LOCK))
 		{
 			resetPQExpBuffer(query);
@@ -6084,7 +6095,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);
 
@@ -15437,6 +15451,9 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 				appendPQExpBufferChar(q, ')');
 			}
 
+			if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+				appendPQExpBuffer(q, "\nPARTITION BY %s", tbinfo->partkeydef);
+
 			if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 				appendPQExpBuffer(q, "\nSERVER %s", fmtId(srvname));
 		}
@@ -15497,6 +15514,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 		 */
 		if (dopt->binary_upgrade &&
 			(tbinfo->relkind == RELKIND_RELATION ||
+			 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 			 tbinfo->relkind == RELKIND_FOREIGN_TABLE))
 		{
 			for (j = 0; j < tbinfo->numatts; j++)
@@ -15515,7 +15533,8 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 					appendStringLiteralAH(q, fmtId(tbinfo->dobj.name), fout);
 					appendPQExpBufferStr(q, "::pg_catalog.regclass;\n");
 
-					if (tbinfo->relkind == RELKIND_RELATION)
+					if (tbinfo->relkind == RELKIND_RELATION ||
+						tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
 						appendPQExpBuffer(q, "ALTER TABLE ONLY %s ",
 										  fmtId(tbinfo->dobj.name));
 					else
@@ -15732,6 +15751,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 	 * dump properties we only have ALTER TABLE syntax for
 	 */
 	if ((tbinfo->relkind == RELKIND_RELATION ||
+		 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 		 tbinfo->relkind == RELKIND_MATVIEW) &&
 		tbinfo->relreplident != REPLICA_IDENTITY_DEFAULT)
 	{
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 2bfa2d9..0292859 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -310,6 +310,7 @@ typedef struct _tableInfo
 	bool	   *inhNotNull;		/* true if NOT NULL is inherited */
 	struct _attrDefInfo **attrdefs;		/* DEFAULT expressions */
 	struct _constraintInfo *checkexprs; /* CHECK constraints */
+	char	   *partkeydef;		/* partition key definition */
 
 	/*
 	 * Stuff computed only for dumpable tables.
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 6275a68..10d924a 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -861,6 +861,7 @@ permissionsList(const char *pattern)
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'S' THEN '%s'"
@@ -870,6 +871,7 @@ permissionsList(const char *pattern)
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("sequence"),
@@ -920,7 +922,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
@@ -1567,8 +1569,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)");
 	}
 
@@ -1633,6 +1635,14 @@ describeOneTableDetails(const char *schemaname,
 			printfPQExpBuffer(&title, _("Foreign table \"%s.%s\""),
 							  schemaname, relationname);
 			break;
+		case 'P':
+			if (tableinfo.relpersistence == 'u')
+				printfPQExpBuffer(&title, _("Unlogged table \"%s.%s\""),
+								  schemaname, relationname);
+			else
+				printfPQExpBuffer(&title, _("Table \"%s.%s\""),
+								  schemaname, relationname);
+			break;
 		default:
 			/* untranslated unknown relkind */
 			printfPQExpBuffer(&title, "?%c? \"%s.%s\"",
@@ -1646,8 +1656,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");
@@ -1667,12 +1677,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");
 	}
 
@@ -1772,7 +1782,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);
@@ -1780,14 +1790,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 (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 */
@@ -1926,7 +1955,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;
@@ -2485,7 +2514,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;
@@ -2696,7 +2725,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
@@ -3024,6 +3053,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'i' THEN '%s'"
@@ -3035,6 +3065,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("index"),
@@ -3079,7 +3110,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 1345e4e..4686cd1 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/pg_proc.h b/src/include/catalog/pg_proc.h
index e2d08ba..b45688b 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -1980,6 +1980,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/utils/builtins.h b/src/include/utils/builtins.h
index 2ae212a..e800647 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -729,6 +729,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/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 708232d..2fec847 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -408,3 +408,29 @@ CREATE TABLE no_inh_con_parted (
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
 ERROR:  cannot add NO INHERIT constraint to partitioned table "no_inh_con_parted"
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+Table "public.describe_range_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition Key: RANGE ((a + b))
+
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+Table "public.describe_list_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition Key: LIST (a)
+
+DROP TABLE describe_range_key, describe_list_key;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 6e4a8be..4dd6a0a 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -402,3 +402,16 @@ CREATE TABLE no_inh_con_parted (
 	a int,
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
+
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+DROP TABLE describe_range_key, describe_list_key;
-- 
1.7.1


--------------954458C17BBEB1BF6FC758E7
Content-Type: text/x-diff;
 name="0003-Catalog-and-DDL-for-partitions-2.patch"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
 filename="0003-Catalog-and-DDL-for-partitions-2.patch"



^ permalink  raw  reply  [nested|flat] 110+ messages in thread

* [PATCH 2/9] psql and pg_dump support for partitioned tables.
@ 2016-07-12 08:20  amit <[email protected]>
  0 siblings, 0 replies; 110+ messages in thread

From: amit @ 2016-07-12 08:20 UTC (permalink / raw)

Takes care of both the partition key deparse stuff and the new relkind.
---
 src/backend/utils/adt/ruleutils.c          |  140 ++++++++++++++++++++++++++++
 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/pg_proc.h              |    2 +
 src/include/utils/builtins.h               |    1 +
 src/test/regress/expected/create_table.out |   26 +++++
 src/test/regress/sql/create_table.sql      |   13 +++
 9 files changed, 260 insertions(+), 26 deletions(-)

diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 8a81d7a..77ce807 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_table.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
@@ -315,6 +316,7 @@ static char *pg_get_indexdef_worker(Oid indexrelid, int colno,
 					   const Oid *excludeOps,
 					   bool attrsOnly, bool showTblSpc,
 					   int prettyFlags, bool missing_ok);
+static char *pg_get_partkeydef_worker(Oid relId, int prettyFlags);
 static char *pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 							int prettyFlags, bool missing_ok);
 static text *pg_get_expr_worker(text *expr, Oid relid, const char *relname,
@@ -1389,6 +1391,144 @@ 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 [ 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_table	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_table) GETSTRUCT(tuple);
+
+	Assert(form->partedrelid == relId);
+
+	/* Must get partclass, and partexprs the hard way */
+	datum = SysCacheGetAttr(PARTEDRELID, tuple,
+							Anum_pg_partitioned_table_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_table_partexprbin))
+	{
+		Datum		exprsDatum;
+		bool		isnull;
+		char	   *exprsString;
+
+		exprsDatum = SysCacheGetAttr(PARTEDRELID, tuple,
+									 Anum_pg_partitioned_table_partexprbin, &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);
+
+	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,
+											0, 0);
+
+			appendStringInfoString(&buf, 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/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index a5c2d09..c418ba7 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -1253,9 +1253,10 @@ expand_table_name_patterns(Archive *fout,
 						  "SELECT c.oid"
 						  "\nFROM pg_catalog.pg_class c"
 		"\n     LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace"
-					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c')\n",
+					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c')\n",
 						  RELKIND_RELATION, RELKIND_SEQUENCE, RELKIND_VIEW,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 		processSQLNamePattern(GetConnection(fout), query, cell->val, true,
 							  false, "n.nspname", "c.relname", NULL,
 							  "pg_catalog.pg_table_is_visible(c.oid)");
@@ -2117,6 +2118,9 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo, bool oids)
 	/* Skip FOREIGN TABLEs (no data to dump) */
 	if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 		return;
+	/* Skip partitioned tables (data in partitions) */
+	if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+		return;
 
 	/* Don't dump data in unlogged tables, if so requested */
 	if (tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED &&
@@ -5195,6 +5199,7 @@ getTables(Archive *fout, int *numTables)
 	int			i_reloftype;
 	int			i_relpages;
 	int			i_changed_acl;
+	int			i_partkeydef;
 
 	/* Make sure we are in proper schema */
 	selectSourceSchema(fout, "pg_catalog");
@@ -5280,7 +5285,8 @@ getTables(Archive *fout, int *numTables)
 						  "OR %s IS NOT NULL "
 						  "OR %s IS NOT NULL"
 						  "))"
-						  "AS changed_acl "
+						  "AS changed_acl, "
+						  "CASE WHEN c.relkind = 'P' THEN pg_catalog.pg_get_partkeydef(c.oid) ELSE NULL END AS partkeydef "
 						  "FROM pg_class c "
 						  "LEFT JOIN pg_depend d ON "
 						  "(c.relkind = '%c' AND "
@@ -5292,7 +5298,7 @@ getTables(Archive *fout, int *numTables)
 						  "(c.oid = pip.objoid "
 						  "AND pip.classoid = 'pg_class'::regclass "
 						  "AND pip.objsubid = 0) "
-				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c') "
+				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c', '%c') "
 						  "ORDER BY c.oid",
 						  acl_subquery->data,
 						  racl_subquery->data,
@@ -5306,7 +5312,8 @@ getTables(Archive *fout, int *numTables)
 						  RELKIND_SEQUENCE,
 						  RELKIND_RELATION, RELKIND_SEQUENCE,
 						  RELKIND_VIEW, RELKIND_COMPOSITE_TYPE,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 
 		destroyPQExpBuffer(acl_subquery);
 		destroyPQExpBuffer(racl_subquery);
@@ -5868,6 +5875,7 @@ getTables(Archive *fout, int *numTables)
 	i_toastreloptions = PQfnumber(res, "toast_reloptions");
 	i_reloftype = PQfnumber(res, "reloftype");
 	i_changed_acl = PQfnumber(res, "changed_acl");
+	i_partkeydef = PQfnumber(res, "partkeydef");
 
 	if (dopt->lockWaitTimeout && fout->remoteVersion >= 70300)
 	{
@@ -5938,6 +5946,7 @@ getTables(Archive *fout, int *numTables)
 		else
 			tblinfo[i].checkoption = pg_strdup(PQgetvalue(res, i, i_checkoption));
 		tblinfo[i].toast_reloptions = pg_strdup(PQgetvalue(res, i, i_toastreloptions));
+		tblinfo[i].partkeydef = pg_strdup(PQgetvalue(res, i, i_partkeydef));
 
 		/* other fields were zeroed above */
 
@@ -5982,7 +5991,9 @@ getTables(Archive *fout, int *numTables)
 		 * We only need to lock the table for certain components; see
 		 * pg_dump.h
 		 */
-		if (tblinfo[i].dobj.dump && tblinfo[i].relkind == RELKIND_RELATION &&
+		if (tblinfo[i].dobj.dump &&
+			(tblinfo[i].relkind == RELKIND_RELATION ||
+			 tblinfo->relkind == RELKIND_PARTITIONED_TABLE) &&
 			(tblinfo[i].dobj.dump & DUMP_COMPONENTS_REQUIRING_LOCK))
 		{
 			resetPQExpBuffer(query);
@@ -6084,7 +6095,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);
 
@@ -15437,6 +15451,9 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 				appendPQExpBufferChar(q, ')');
 			}
 
+			if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+				appendPQExpBuffer(q, "\nPARTITION BY %s", tbinfo->partkeydef);
+
 			if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 				appendPQExpBuffer(q, "\nSERVER %s", fmtId(srvname));
 		}
@@ -15497,6 +15514,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 		 */
 		if (dopt->binary_upgrade &&
 			(tbinfo->relkind == RELKIND_RELATION ||
+			 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 			 tbinfo->relkind == RELKIND_FOREIGN_TABLE))
 		{
 			for (j = 0; j < tbinfo->numatts; j++)
@@ -15515,7 +15533,8 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 					appendStringLiteralAH(q, fmtId(tbinfo->dobj.name), fout);
 					appendPQExpBufferStr(q, "::pg_catalog.regclass;\n");
 
-					if (tbinfo->relkind == RELKIND_RELATION)
+					if (tbinfo->relkind == RELKIND_RELATION ||
+						tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
 						appendPQExpBuffer(q, "ALTER TABLE ONLY %s ",
 										  fmtId(tbinfo->dobj.name));
 					else
@@ -15732,6 +15751,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 	 * dump properties we only have ALTER TABLE syntax for
 	 */
 	if ((tbinfo->relkind == RELKIND_RELATION ||
+		 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 		 tbinfo->relkind == RELKIND_MATVIEW) &&
 		tbinfo->relreplident != REPLICA_IDENTITY_DEFAULT)
 	{
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 2bfa2d9..0292859 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -310,6 +310,7 @@ typedef struct _tableInfo
 	bool	   *inhNotNull;		/* true if NOT NULL is inherited */
 	struct _attrDefInfo **attrdefs;		/* DEFAULT expressions */
 	struct _constraintInfo *checkexprs; /* CHECK constraints */
+	char	   *partkeydef;		/* partition key definition */
 
 	/*
 	 * Stuff computed only for dumpable tables.
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 6275a68..10d924a 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -861,6 +861,7 @@ permissionsList(const char *pattern)
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'S' THEN '%s'"
@@ -870,6 +871,7 @@ permissionsList(const char *pattern)
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("sequence"),
@@ -920,7 +922,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
@@ -1567,8 +1569,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)");
 	}
 
@@ -1633,6 +1635,14 @@ describeOneTableDetails(const char *schemaname,
 			printfPQExpBuffer(&title, _("Foreign table \"%s.%s\""),
 							  schemaname, relationname);
 			break;
+		case 'P':
+			if (tableinfo.relpersistence == 'u')
+				printfPQExpBuffer(&title, _("Unlogged table \"%s.%s\""),
+								  schemaname, relationname);
+			else
+				printfPQExpBuffer(&title, _("Table \"%s.%s\""),
+								  schemaname, relationname);
+			break;
 		default:
 			/* untranslated unknown relkind */
 			printfPQExpBuffer(&title, "?%c? \"%s.%s\"",
@@ -1646,8 +1656,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");
@@ -1667,12 +1677,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");
 	}
 
@@ -1772,7 +1782,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);
@@ -1780,14 +1790,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 (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 */
@@ -1926,7 +1955,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;
@@ -2485,7 +2514,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;
@@ -2696,7 +2725,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
@@ -3024,6 +3053,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'i' THEN '%s'"
@@ -3035,6 +3065,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("index"),
@@ -3079,7 +3110,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 019f75a..5e5c370 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/pg_proc.h b/src/include/catalog/pg_proc.h
index e2d08ba..b45688b 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -1980,6 +1980,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/utils/builtins.h b/src/include/utils/builtins.h
index 2ae212a..e800647 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -729,6 +729,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/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 708232d..2fec847 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -408,3 +408,29 @@ CREATE TABLE no_inh_con_parted (
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
 ERROR:  cannot add NO INHERIT constraint to partitioned table "no_inh_con_parted"
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+Table "public.describe_range_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition Key: RANGE ((a + b))
+
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+Table "public.describe_list_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition Key: LIST (a)
+
+DROP TABLE describe_range_key, describe_list_key;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 6e4a8be..4dd6a0a 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -402,3 +402,16 @@ CREATE TABLE no_inh_con_parted (
 	a int,
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
+
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+DROP TABLE describe_range_key, describe_list_key;
-- 
1.7.1


--------------6EB5B318125CB7DB1A4FD229
Content-Type: text/x-diff;
 name="0003-Catalog-and-DDL-for-partitions-3.patch"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
 filename="0003-Catalog-and-DDL-for-partitions-3.patch"



^ permalink  raw  reply  [nested|flat] 110+ messages in thread

* [PATCH 2/9] psql and pg_dump support for partitioned tables.
@ 2016-07-12 08:20  amit <[email protected]>
  0 siblings, 0 replies; 110+ messages in thread

From: amit @ 2016-07-12 08:20 UTC (permalink / raw)

Takes care of both the partition key deparse stuff and the new relkind.
---
 src/backend/utils/adt/ruleutils.c          |  140 ++++++++++++++++++++++++++++
 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/pg_proc.h              |    2 +
 src/include/utils/builtins.h               |    1 +
 src/test/regress/expected/create_table.out |   26 +++++
 src/test/regress/sql/create_table.sql      |   13 +++
 9 files changed, 260 insertions(+), 26 deletions(-)

diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 8a81d7a..03be202 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_table.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
@@ -315,6 +316,7 @@ static char *pg_get_indexdef_worker(Oid indexrelid, int colno,
 					   const Oid *excludeOps,
 					   bool attrsOnly, bool showTblSpc,
 					   int prettyFlags, bool missing_ok);
+static char *pg_get_partkeydef_worker(Oid relid, int prettyFlags);
 static char *pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 							int prettyFlags, bool missing_ok);
 static text *pg_get_expr_worker(text *expr, Oid relid, const char *relname,
@@ -1389,6 +1391,144 @@ 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 [ opclass_name ] [, ...])
+ */
+Datum
+pg_get_partkeydef(PG_FUNCTION_ARGS)
+{
+	Oid			relid = PG_GETARG_OID(0);
+	int			prettyFlags;
+
+	prettyFlags = PRETTYFLAG_INDENT;
+	PG_RETURN_TEXT_P(string_to_text(pg_get_partkeydef_worker(relid,
+									prettyFlags)));
+}
+
+/*
+ * Internal workhorse to decompile a partition key definition.
+ */
+static char *
+pg_get_partkeydef_worker(Oid relid, int prettyFlags)
+{
+	Form_pg_partitioned_table	form;
+	HeapTuple	tuple;
+	oidvector  *partclass;
+	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_table) GETSTRUCT(tuple);
+
+	Assert(form->partrelid == relid);
+
+	/* Must get partclass, and partexprs the hard way */
+	datum = SysCacheGetAttr(PARTEDRELID, tuple,
+							Anum_pg_partitioned_table_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_table_partexprbin))
+	{
+		Datum		exprsDatum;
+		bool		isnull;
+		char	   *exprsString;
+
+		exprsDatum = SysCacheGetAttr(PARTEDRELID, tuple,
+									 Anum_pg_partitioned_table_partexprbin, &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);
+
+	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,
+											0, 0);
+
+			appendStringInfoString(&buf, 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/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index ba9c276..c805a84 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -1253,9 +1253,10 @@ expand_table_name_patterns(Archive *fout,
 						  "SELECT c.oid"
 						  "\nFROM pg_catalog.pg_class c"
 		"\n     LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace"
-					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c')\n",
+					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c')\n",
 						  RELKIND_RELATION, RELKIND_SEQUENCE, RELKIND_VIEW,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 		processSQLNamePattern(GetConnection(fout), query, cell->val, true,
 							  false, "n.nspname", "c.relname", NULL,
 							  "pg_catalog.pg_table_is_visible(c.oid)");
@@ -2125,6 +2126,9 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo, bool oids)
 	/* Skip FOREIGN TABLEs (no data to dump) */
 	if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 		return;
+	/* Skip partitioned tables (data in partitions) */
+	if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+		return;
 
 	/* Don't dump data in unlogged tables, if so requested */
 	if (tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED &&
@@ -5204,6 +5208,7 @@ getTables(Archive *fout, int *numTables)
 	int			i_reloftype;
 	int			i_relpages;
 	int			i_changed_acl;
+	int			i_partkeydef;
 
 	/* Make sure we are in proper schema */
 	selectSourceSchema(fout, "pg_catalog");
@@ -5289,7 +5294,8 @@ getTables(Archive *fout, int *numTables)
 						  "OR %s IS NOT NULL "
 						  "OR %s IS NOT NULL"
 						  "))"
-						  "AS changed_acl "
+						  "AS changed_acl, "
+						  "CASE WHEN c.relkind = 'P' THEN pg_catalog.pg_get_partkeydef(c.oid) ELSE NULL END AS partkeydef "
 						  "FROM pg_class c "
 						  "LEFT JOIN pg_depend d ON "
 						  "(c.relkind = '%c' AND "
@@ -5301,7 +5307,7 @@ getTables(Archive *fout, int *numTables)
 						  "(c.oid = pip.objoid "
 						  "AND pip.classoid = 'pg_class'::regclass "
 						  "AND pip.objsubid = 0) "
-				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c') "
+				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c', '%c') "
 						  "ORDER BY c.oid",
 						  acl_subquery->data,
 						  racl_subquery->data,
@@ -5315,7 +5321,8 @@ getTables(Archive *fout, int *numTables)
 						  RELKIND_SEQUENCE,
 						  RELKIND_RELATION, RELKIND_SEQUENCE,
 						  RELKIND_VIEW, RELKIND_COMPOSITE_TYPE,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 
 		destroyPQExpBuffer(acl_subquery);
 		destroyPQExpBuffer(racl_subquery);
@@ -5877,6 +5884,7 @@ getTables(Archive *fout, int *numTables)
 	i_toastreloptions = PQfnumber(res, "toast_reloptions");
 	i_reloftype = PQfnumber(res, "reloftype");
 	i_changed_acl = PQfnumber(res, "changed_acl");
+	i_partkeydef = PQfnumber(res, "partkeydef");
 
 	if (dopt->lockWaitTimeout && fout->remoteVersion >= 70300)
 	{
@@ -5947,6 +5955,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 */
 
@@ -5991,7 +6000,9 @@ getTables(Archive *fout, int *numTables)
 		 * We only need to lock the table for certain components; see
 		 * pg_dump.h
 		 */
-		if (tblinfo[i].dobj.dump && tblinfo[i].relkind == RELKIND_RELATION &&
+		if (tblinfo[i].dobj.dump &&
+			(tblinfo[i].relkind == RELKIND_RELATION ||
+			 tblinfo->relkind == RELKIND_PARTITIONED_TABLE) &&
 			(tblinfo[i].dobj.dump & DUMP_COMPONENTS_REQUIRING_LOCK))
 		{
 			resetPQExpBuffer(query);
@@ -6093,7 +6104,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);
 
@@ -15446,6 +15460,9 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 				appendPQExpBufferChar(q, ')');
 			}
 
+			if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+				appendPQExpBuffer(q, "\nPARTITION BY %s", tbinfo->partkeydef);
+
 			if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 				appendPQExpBuffer(q, "\nSERVER %s", fmtId(srvname));
 		}
@@ -15506,6 +15523,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 		 */
 		if (dopt->binary_upgrade &&
 			(tbinfo->relkind == RELKIND_RELATION ||
+			 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 			 tbinfo->relkind == RELKIND_FOREIGN_TABLE))
 		{
 			for (j = 0; j < tbinfo->numatts; j++)
@@ -15524,7 +15542,8 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 					appendStringLiteralAH(q, fmtId(tbinfo->dobj.name), fout);
 					appendPQExpBufferStr(q, "::pg_catalog.regclass;\n");
 
-					if (tbinfo->relkind == RELKIND_RELATION)
+					if (tbinfo->relkind == RELKIND_RELATION ||
+						tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
 						appendPQExpBuffer(q, "ALTER TABLE ONLY %s ",
 										  fmtId(tbinfo->dobj.name));
 					else
@@ -15741,6 +15760,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 	 * dump properties we only have ALTER TABLE syntax for
 	 */
 	if ((tbinfo->relkind == RELKIND_RELATION ||
+		 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 		 tbinfo->relkind == RELKIND_MATVIEW) &&
 		tbinfo->relreplident != REPLICA_IDENTITY_DEFAULT)
 	{
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 2bfa2d9..0292859 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -310,6 +310,7 @@ typedef struct _tableInfo
 	bool	   *inhNotNull;		/* true if NOT NULL is inherited */
 	struct _attrDefInfo **attrdefs;		/* DEFAULT expressions */
 	struct _constraintInfo *checkexprs; /* CHECK constraints */
+	char	   *partkeydef;		/* partition key definition */
 
 	/*
 	 * Stuff computed only for dumpable tables.
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 6275a68..10d924a 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -861,6 +861,7 @@ permissionsList(const char *pattern)
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'S' THEN '%s'"
@@ -870,6 +871,7 @@ permissionsList(const char *pattern)
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("sequence"),
@@ -920,7 +922,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
@@ -1567,8 +1569,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)");
 	}
 
@@ -1633,6 +1635,14 @@ describeOneTableDetails(const char *schemaname,
 			printfPQExpBuffer(&title, _("Foreign table \"%s.%s\""),
 							  schemaname, relationname);
 			break;
+		case 'P':
+			if (tableinfo.relpersistence == 'u')
+				printfPQExpBuffer(&title, _("Unlogged table \"%s.%s\""),
+								  schemaname, relationname);
+			else
+				printfPQExpBuffer(&title, _("Table \"%s.%s\""),
+								  schemaname, relationname);
+			break;
 		default:
 			/* untranslated unknown relkind */
 			printfPQExpBuffer(&title, "?%c? \"%s.%s\"",
@@ -1646,8 +1656,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");
@@ -1667,12 +1677,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");
 	}
 
@@ -1772,7 +1782,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);
@@ -1780,14 +1790,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 (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 */
@@ -1926,7 +1955,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;
@@ -2485,7 +2514,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;
@@ -2696,7 +2725,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
@@ -3024,6 +3053,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'i' THEN '%s'"
@@ -3035,6 +3065,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("index"),
@@ -3079,7 +3110,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 50a45eb..8284a9c 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/pg_proc.h b/src/include/catalog/pg_proc.h
index e2d08ba..b45688b 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -1980,6 +1980,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/utils/builtins.h b/src/include/utils/builtins.h
index 2ae212a..e800647 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -729,6 +729,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/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 0af1422..91ba1be 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -412,3 +412,29 @@ CREATE TABLE no_inh_con_parted (
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
 ERROR:  cannot add NO INHERIT constraint to partitioned table "no_inh_con_parted"
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+Table "public.describe_range_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition Key: RANGE ((a + b))
+
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+Table "public.describe_list_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition Key: LIST (a)
+
+DROP TABLE describe_range_key, describe_list_key;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 418ac55..b75542d 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -406,3 +406,16 @@ CREATE TABLE no_inh_con_parted (
 	a int,
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
+
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+DROP TABLE describe_range_key, describe_list_key;
-- 
1.7.1


--------------D008BA677674C4836D7B65AD
Content-Type: text/x-diff;
 name="0003-Catalog-and-DDL-for-partitions-5.patch"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
 filename="0003-Catalog-and-DDL-for-partitions-5.patch"



^ permalink  raw  reply  [nested|flat] 110+ messages in thread

* [PATCH 2/9] psql and pg_dump support for partitioned tables.
@ 2016-07-12 08:20  amit <[email protected]>
  0 siblings, 0 replies; 110+ messages in thread

From: amit @ 2016-07-12 08:20 UTC (permalink / raw)

Takes care of both the partition key deparse stuff and the new relkind.
---
 src/backend/utils/adt/ruleutils.c          |  140 ++++++++++++++++++++++++++++
 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/pg_proc.h              |    2 +
 src/include/utils/builtins.h               |    1 +
 src/test/regress/expected/create_table.out |   26 +++++
 src/test/regress/sql/create_table.sql      |   13 +++
 9 files changed, 260 insertions(+), 26 deletions(-)

diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 8a81d7a..03be202 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_table.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
@@ -315,6 +316,7 @@ static char *pg_get_indexdef_worker(Oid indexrelid, int colno,
 					   const Oid *excludeOps,
 					   bool attrsOnly, bool showTblSpc,
 					   int prettyFlags, bool missing_ok);
+static char *pg_get_partkeydef_worker(Oid relid, int prettyFlags);
 static char *pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 							int prettyFlags, bool missing_ok);
 static text *pg_get_expr_worker(text *expr, Oid relid, const char *relname,
@@ -1389,6 +1391,144 @@ 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 [ opclass_name ] [, ...])
+ */
+Datum
+pg_get_partkeydef(PG_FUNCTION_ARGS)
+{
+	Oid			relid = PG_GETARG_OID(0);
+	int			prettyFlags;
+
+	prettyFlags = PRETTYFLAG_INDENT;
+	PG_RETURN_TEXT_P(string_to_text(pg_get_partkeydef_worker(relid,
+									prettyFlags)));
+}
+
+/*
+ * Internal workhorse to decompile a partition key definition.
+ */
+static char *
+pg_get_partkeydef_worker(Oid relid, int prettyFlags)
+{
+	Form_pg_partitioned_table	form;
+	HeapTuple	tuple;
+	oidvector  *partclass;
+	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_table) GETSTRUCT(tuple);
+
+	Assert(form->partrelid == relid);
+
+	/* Must get partclass, and partexprs the hard way */
+	datum = SysCacheGetAttr(PARTEDRELID, tuple,
+							Anum_pg_partitioned_table_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_table_partexprbin))
+	{
+		Datum		exprsDatum;
+		bool		isnull;
+		char	   *exprsString;
+
+		exprsDatum = SysCacheGetAttr(PARTEDRELID, tuple,
+									 Anum_pg_partitioned_table_partexprbin, &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);
+
+	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,
+											0, 0);
+
+			appendStringInfoString(&buf, 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/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 51b8a1a..854dddc 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -1253,9 +1253,10 @@ expand_table_name_patterns(Archive *fout,
 						  "SELECT c.oid"
 						  "\nFROM pg_catalog.pg_class c"
 		"\n     LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace"
-					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c')\n",
+					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c')\n",
 						  RELKIND_RELATION, RELKIND_SEQUENCE, RELKIND_VIEW,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 		processSQLNamePattern(GetConnection(fout), query, cell->val, true,
 							  false, "n.nspname", "c.relname", NULL,
 							  "pg_catalog.pg_table_is_visible(c.oid)");
@@ -2125,6 +2126,9 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo, bool oids)
 	/* Skip FOREIGN TABLEs (no data to dump) */
 	if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 		return;
+	/* Skip partitioned tables (data in partitions) */
+	if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+		return;
 
 	/* Don't dump data in unlogged tables, if so requested */
 	if (tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED &&
@@ -5211,6 +5215,7 @@ getTables(Archive *fout, int *numTables)
 	int			i_reloftype;
 	int			i_relpages;
 	int			i_changed_acl;
+	int			i_partkeydef;
 
 	/* Make sure we are in proper schema */
 	selectSourceSchema(fout, "pg_catalog");
@@ -5296,7 +5301,8 @@ getTables(Archive *fout, int *numTables)
 						  "OR %s IS NOT NULL "
 						  "OR %s IS NOT NULL"
 						  "))"
-						  "AS changed_acl "
+						  "AS changed_acl, "
+						  "CASE WHEN c.relkind = 'P' THEN pg_catalog.pg_get_partkeydef(c.oid) ELSE NULL END AS partkeydef "
 						  "FROM pg_class c "
 						  "LEFT JOIN pg_depend d ON "
 						  "(c.relkind = '%c' AND "
@@ -5308,7 +5314,7 @@ getTables(Archive *fout, int *numTables)
 						  "(c.oid = pip.objoid "
 						  "AND pip.classoid = 'pg_class'::regclass "
 						  "AND pip.objsubid = 0) "
-				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c') "
+				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c', '%c') "
 						  "ORDER BY c.oid",
 						  acl_subquery->data,
 						  racl_subquery->data,
@@ -5322,7 +5328,8 @@ getTables(Archive *fout, int *numTables)
 						  RELKIND_SEQUENCE,
 						  RELKIND_RELATION, RELKIND_SEQUENCE,
 						  RELKIND_VIEW, RELKIND_COMPOSITE_TYPE,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 
 		destroyPQExpBuffer(acl_subquery);
 		destroyPQExpBuffer(racl_subquery);
@@ -5884,6 +5891,7 @@ getTables(Archive *fout, int *numTables)
 	i_toastreloptions = PQfnumber(res, "toast_reloptions");
 	i_reloftype = PQfnumber(res, "reloftype");
 	i_changed_acl = PQfnumber(res, "changed_acl");
+	i_partkeydef = PQfnumber(res, "partkeydef");
 
 	if (dopt->lockWaitTimeout && fout->remoteVersion >= 70300)
 	{
@@ -5954,6 +5962,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 */
 
@@ -5998,7 +6007,9 @@ getTables(Archive *fout, int *numTables)
 		 * We only need to lock the table for certain components; see
 		 * pg_dump.h
 		 */
-		if (tblinfo[i].dobj.dump && tblinfo[i].relkind == RELKIND_RELATION &&
+		if (tblinfo[i].dobj.dump &&
+			(tblinfo[i].relkind == RELKIND_RELATION ||
+			 tblinfo->relkind == RELKIND_PARTITIONED_TABLE) &&
 			(tblinfo[i].dobj.dump & DUMP_COMPONENTS_REQUIRING_LOCK))
 		{
 			resetPQExpBuffer(query);
@@ -6100,7 +6111,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);
 
@@ -15453,6 +15467,9 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 				appendPQExpBufferChar(q, ')');
 			}
 
+			if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+				appendPQExpBuffer(q, "\nPARTITION BY %s", tbinfo->partkeydef);
+
 			if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 				appendPQExpBuffer(q, "\nSERVER %s", fmtId(srvname));
 		}
@@ -15513,6 +15530,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 		 */
 		if (dopt->binary_upgrade &&
 			(tbinfo->relkind == RELKIND_RELATION ||
+			 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 			 tbinfo->relkind == RELKIND_FOREIGN_TABLE))
 		{
 			for (j = 0; j < tbinfo->numatts; j++)
@@ -15531,7 +15549,8 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 					appendStringLiteralAH(q, fmtId(tbinfo->dobj.name), fout);
 					appendPQExpBufferStr(q, "::pg_catalog.regclass;\n");
 
-					if (tbinfo->relkind == RELKIND_RELATION)
+					if (tbinfo->relkind == RELKIND_RELATION ||
+						tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
 						appendPQExpBuffer(q, "ALTER TABLE ONLY %s ",
 										  fmtId(tbinfo->dobj.name));
 					else
@@ -15748,6 +15767,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 	 * dump properties we only have ALTER TABLE syntax for
 	 */
 	if ((tbinfo->relkind == RELKIND_RELATION ||
+		 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 		 tbinfo->relkind == RELKIND_MATVIEW) &&
 		tbinfo->relreplident != REPLICA_IDENTITY_DEFAULT)
 	{
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 2bfa2d9..0292859 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -310,6 +310,7 @@ typedef struct _tableInfo
 	bool	   *inhNotNull;		/* true if NOT NULL is inherited */
 	struct _attrDefInfo **attrdefs;		/* DEFAULT expressions */
 	struct _constraintInfo *checkexprs; /* CHECK constraints */
+	char	   *partkeydef;		/* partition key definition */
 
 	/*
 	 * Stuff computed only for dumpable tables.
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 6275a68..db6dc5c 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -861,6 +861,7 @@ permissionsList(const char *pattern)
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'S' THEN '%s'"
@@ -870,6 +871,7 @@ permissionsList(const char *pattern)
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("sequence"),
@@ -920,7 +922,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
@@ -1567,8 +1569,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)");
 	}
 
@@ -1633,6 +1635,14 @@ describeOneTableDetails(const char *schemaname,
 			printfPQExpBuffer(&title, _("Foreign table \"%s.%s\""),
 							  schemaname, relationname);
 			break;
+		case 'P':
+			if (tableinfo.relpersistence == 'u')
+				printfPQExpBuffer(&title, _("Unlogged table \"%s.%s\""),
+								  schemaname, relationname);
+			else
+				printfPQExpBuffer(&title, _("Table \"%s.%s\""),
+								  schemaname, relationname);
+			break;
 		default:
 			/* untranslated unknown relkind */
 			printfPQExpBuffer(&title, "?%c? \"%s.%s\"",
@@ -1646,8 +1656,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");
@@ -1667,12 +1677,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");
 	}
 
@@ -1772,7 +1782,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);
@@ -1780,14 +1790,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 (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 */
@@ -1926,7 +1955,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;
@@ -2485,7 +2514,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;
@@ -2696,7 +2725,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
@@ -3024,6 +3053,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'i' THEN '%s'"
@@ -3035,6 +3065,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("index"),
@@ -3079,7 +3110,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 50a45eb..8284a9c 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/pg_proc.h b/src/include/catalog/pg_proc.h
index e2d08ba..b45688b 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -1980,6 +1980,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/utils/builtins.h b/src/include/utils/builtins.h
index 2ae212a..e800647 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -729,6 +729,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/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 1394362..260ec34 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -407,3 +407,29 @@ CREATE TABLE no_inh_con_parted (
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
 ERROR:  cannot add NO INHERIT constraint to partitioned table "no_inh_con_parted"
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+Table "public.describe_range_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition key: RANGE ((a + b))
+
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+Table "public.describe_list_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition key: LIST (a)
+
+DROP TABLE describe_range_key, describe_list_key;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index d383f58..7a1f8de 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -401,3 +401,16 @@ CREATE TABLE no_inh_con_parted (
 	a int,
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
+
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+DROP TABLE describe_range_key, describe_list_key;
-- 
1.7.1


--------------C3235EE96C69D8DAE4DB543E
Content-Type: text/x-diff;
 name="0003-Catalog-and-DDL-for-partitions-6.patch"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
 filename="0003-Catalog-and-DDL-for-partitions-6.patch"



^ permalink  raw  reply  [nested|flat] 110+ messages in thread

* [PATCH 2/8] psql and pg_dump support for partitioned tables.
@ 2016-07-12 08:20  amit <[email protected]>
  0 siblings, 0 replies; 110+ messages in thread

From: amit @ 2016-07-12 08:20 UTC (permalink / raw)

Takes care of both the partition key deparse stuff and the new relkind.
---
 src/backend/utils/adt/ruleutils.c          |  147 ++++++++++++++++++++++++++++
 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/pg_proc.h              |    2 +
 src/include/utils/builtins.h               |    1 +
 src/test/regress/expected/create_table.out |   26 +++++
 src/test/regress/sql/create_table.sql      |   13 +++
 9 files changed, 267 insertions(+), 26 deletions(-)

diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index ec966c7..4691b5b 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, bool missing_ok);
+static char *pg_get_partkeydef_worker(Oid relId, int prettyFlags);
 static char *pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 							int prettyFlags, bool missing_ok);
 static text *pg_get_expr_worker(text *expr, Oid relid, const char *relname,
@@ -1389,6 +1391,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 [ 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/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 4ee10fc..a127672 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -1253,9 +1253,10 @@ expand_table_name_patterns(Archive *fout,
 						  "SELECT c.oid"
 						  "\nFROM pg_catalog.pg_class c"
 		"\n     LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace"
-					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c')\n",
+					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c')\n",
 						  RELKIND_RELATION, RELKIND_SEQUENCE, RELKIND_VIEW,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_REL);
 		processSQLNamePattern(GetConnection(fout), query, cell->val, true,
 							  false, "n.nspname", "c.relname", NULL,
 							  "pg_catalog.pg_table_is_visible(c.oid)");
@@ -2117,6 +2118,9 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo, bool oids)
 	/* Skip FOREIGN TABLEs (no data to dump) */
 	if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 		return;
+	/* Skip partitioned tables (data in partitions) */
+	if (tbinfo->relkind == RELKIND_PARTITIONED_REL)
+		return;
 
 	/* Don't dump data in unlogged tables, if so requested */
 	if (tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED &&
@@ -5195,6 +5199,7 @@ getTables(Archive *fout, int *numTables)
 	int			i_reloftype;
 	int			i_relpages;
 	int			i_changed_acl;
+	int			i_partkeydef;
 
 	/* Make sure we are in proper schema */
 	selectSourceSchema(fout, "pg_catalog");
@@ -5280,7 +5285,8 @@ getTables(Archive *fout, int *numTables)
 						  "OR %s IS NOT NULL "
 						  "OR %s IS NOT NULL"
 						  "))"
-						  "AS changed_acl "
+						  "AS changed_acl, "
+						  "CASE WHEN c.relkind = 'P' THEN pg_catalog.pg_get_partkeydef(c.oid) ELSE NULL END AS partkeydef "
 						  "FROM pg_class c "
 						  "LEFT JOIN pg_depend d ON "
 						  "(c.relkind = '%c' AND "
@@ -5292,7 +5298,7 @@ getTables(Archive *fout, int *numTables)
 						  "(c.oid = pip.objoid "
 						  "AND pip.classoid = 'pg_class'::regclass "
 						  "AND pip.objsubid = 0) "
-				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c') "
+				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c', '%c') "
 						  "ORDER BY c.oid",
 						  acl_subquery->data,
 						  racl_subquery->data,
@@ -5306,7 +5312,8 @@ getTables(Archive *fout, int *numTables)
 						  RELKIND_SEQUENCE,
 						  RELKIND_RELATION, RELKIND_SEQUENCE,
 						  RELKIND_VIEW, RELKIND_COMPOSITE_TYPE,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_REL);
 
 		destroyPQExpBuffer(acl_subquery);
 		destroyPQExpBuffer(racl_subquery);
@@ -5868,6 +5875,7 @@ getTables(Archive *fout, int *numTables)
 	i_toastreloptions = PQfnumber(res, "toast_reloptions");
 	i_reloftype = PQfnumber(res, "reloftype");
 	i_changed_acl = PQfnumber(res, "changed_acl");
+	i_partkeydef = PQfnumber(res, "partkeydef");
 
 	if (dopt->lockWaitTimeout && fout->remoteVersion >= 70300)
 	{
@@ -5938,6 +5946,7 @@ getTables(Archive *fout, int *numTables)
 		else
 			tblinfo[i].checkoption = pg_strdup(PQgetvalue(res, i, i_checkoption));
 		tblinfo[i].toast_reloptions = pg_strdup(PQgetvalue(res, i, i_toastreloptions));
+		tblinfo[i].partkeydef = pg_strdup(PQgetvalue(res, i, i_partkeydef));
 
 		/* other fields were zeroed above */
 
@@ -5982,7 +5991,9 @@ getTables(Archive *fout, int *numTables)
 		 * We only need to lock the table for certain components; see
 		 * pg_dump.h
 		 */
-		if (tblinfo[i].dobj.dump && tblinfo[i].relkind == RELKIND_RELATION &&
+		if (tblinfo[i].dobj.dump &&
+			(tblinfo[i].relkind == RELKIND_RELATION ||
+			 tblinfo->relkind == RELKIND_PARTITIONED_REL) &&
 			(tblinfo[i].dobj.dump & DUMP_COMPONENTS_REQUIRING_LOCK))
 		{
 			resetPQExpBuffer(query);
@@ -6083,7 +6094,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);
 
@@ -15436,6 +15450,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));
 		}
@@ -15496,6 +15513,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++)
@@ -15514,7 +15532,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
@@ -15731,6 +15750,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 	 * dump properties we only have ALTER TABLE syntax for
 	 */
 	if ((tbinfo->relkind == RELKIND_RELATION ||
+		 tbinfo->relkind == RELKIND_PARTITIONED_REL ||
 		 tbinfo->relkind == RELKIND_MATVIEW) &&
 		tbinfo->relreplident != REPLICA_IDENTITY_DEFAULT)
 	{
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 2bfa2d9..0292859 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -310,6 +310,7 @@ typedef struct _tableInfo
 	bool	   *inhNotNull;		/* true if NOT NULL is inherited */
 	struct _attrDefInfo **attrdefs;		/* DEFAULT expressions */
 	struct _constraintInfo *checkexprs; /* CHECK constraints */
+	char	   *partkeydef;		/* partition key definition */
 
 	/*
 	 * Stuff computed only for dumpable tables.
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 27be102..3ba8a90 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -856,6 +856,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"),
@@ -865,6 +866,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");
@@ -911,7 +913,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
@@ -1555,8 +1557,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)");
 	}
 
@@ -1621,6 +1623,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\"",
@@ -1634,8 +1644,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");
@@ -1655,12 +1665,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");
 	}
 
@@ -1760,7 +1770,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);
@@ -1768,14 +1778,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 */
@@ -1914,7 +1943,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;
@@ -2473,7 +2502,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;
@@ -2684,7 +2713,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
@@ -3018,6 +3047,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"),
@@ -3029,6 +3059,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"));
 
@@ -3067,7 +3098,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 
 	appendPQExpBufferStr(&buf, "\nWHERE c.relkind IN (");
 	if (showTables)
-		appendPQExpBufferStr(&buf, "'r',");
+		appendPQExpBufferStr(&buf, "'r', 'P',");
 	if (showViews)
 		appendPQExpBufferStr(&buf, "'v',");
 	if (showMatViews)
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 8469d9f..cd4eb7c 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -427,7 +427,7 @@ static const SchemaQuery Query_for_list_of_tables = {
 	/* catname */
 	"pg_catalog.pg_class c",
 	/* selcondition */
-	"c.relkind IN ('r')",
+	"c.relkind IN ('r', 'P')",
 	/* viscondition */
 	"pg_catalog.pg_table_is_visible(c.oid)",
 	/* namespace */
@@ -458,7 +458,7 @@ static const SchemaQuery Query_for_list_of_updatables = {
 	/* catname */
 	"pg_catalog.pg_class c",
 	/* selcondition */
-	"c.relkind IN ('r', 'f', 'v')",
+	"c.relkind IN ('r', 'f', 'v', 'P')",
 	/* viscondition */
 	"pg_catalog.pg_table_is_visible(c.oid)",
 	/* namespace */
@@ -488,7 +488,7 @@ static const SchemaQuery Query_for_list_of_tsvmf = {
 	/* catname */
 	"pg_catalog.pg_class c",
 	/* selcondition */
-	"c.relkind IN ('r', 'S', 'v', 'm', 'f')",
+	"c.relkind IN ('r', 'S', 'v', 'm', 'f', 'P')",
 	/* viscondition */
 	"pg_catalog.pg_table_is_visible(c.oid)",
 	/* namespace */
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index 270dd21..5a444d9 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/utils/builtins.h b/src/include/utils/builtins.h
index 8cebc86..351bfd3 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -722,6 +722,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/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index fb7a1b3..4fc8c06 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -413,3 +413,29 @@ SELECT connoinherit FROM pg_constraint WHERE conrelid = 'no_inh_con_parted'::reg
 (1 row)
 
 DROP TABLE no_inh_con_parted;
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+Partitioned table "public.describe_range_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition Key: PARTITION BY RANGE (((a + b)))
+
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+Partitioned table "public.describe_list_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition Key: PARTITION BY LIST (a)
+
+DROP TABLE describe_range_key, describe_list_key;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index cb17517..7c7a18a 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -404,3 +404,16 @@ CREATE TABLE no_inh_con_parted (
 ) PARTITION BY RANGE (a);
 SELECT connoinherit FROM pg_constraint WHERE conrelid = 'no_inh_con_parted'::regclass;
 DROP TABLE no_inh_con_parted;
+
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+DROP TABLE describe_range_key, describe_list_key;
-- 
1.7.1


--------------8C4C48E640631A327D3D2826
Content-Type: text/x-diff;
 name="0003-Catalog-and-DDL-for-partitions.patch"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
 filename="0003-Catalog-and-DDL-for-partitions.patch"



^ permalink  raw  reply  [nested|flat] 110+ messages in thread

* [PATCH 2/9] psql and pg_dump support for partitioned tables.
@ 2016-07-12 08:20  amit <[email protected]>
  0 siblings, 0 replies; 110+ messages in thread

From: amit @ 2016-07-12 08:20 UTC (permalink / raw)

Takes care of both the partition key deparse stuff and the new relkind.
---
 src/backend/utils/adt/ruleutils.c          |  140 ++++++++++++++++++++++++++++
 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/pg_proc.h              |    2 +
 src/include/utils/builtins.h               |    1 +
 src/test/regress/expected/create_table.out |   26 +++++
 src/test/regress/sql/create_table.sql      |   13 +++
 9 files changed, 260 insertions(+), 26 deletions(-)

diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 8a81d7a..77ce807 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_table.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
@@ -315,6 +316,7 @@ static char *pg_get_indexdef_worker(Oid indexrelid, int colno,
 					   const Oid *excludeOps,
 					   bool attrsOnly, bool showTblSpc,
 					   int prettyFlags, bool missing_ok);
+static char *pg_get_partkeydef_worker(Oid relId, int prettyFlags);
 static char *pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 							int prettyFlags, bool missing_ok);
 static text *pg_get_expr_worker(text *expr, Oid relid, const char *relname,
@@ -1389,6 +1391,144 @@ 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 [ 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_table	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_table) GETSTRUCT(tuple);
+
+	Assert(form->partedrelid == relId);
+
+	/* Must get partclass, and partexprs the hard way */
+	datum = SysCacheGetAttr(PARTEDRELID, tuple,
+							Anum_pg_partitioned_table_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_table_partexprbin))
+	{
+		Datum		exprsDatum;
+		bool		isnull;
+		char	   *exprsString;
+
+		exprsDatum = SysCacheGetAttr(PARTEDRELID, tuple,
+									 Anum_pg_partitioned_table_partexprbin, &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);
+
+	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,
+											0, 0);
+
+			appendStringInfoString(&buf, 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/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index a5c2d09..c418ba7 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -1253,9 +1253,10 @@ expand_table_name_patterns(Archive *fout,
 						  "SELECT c.oid"
 						  "\nFROM pg_catalog.pg_class c"
 		"\n     LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace"
-					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c')\n",
+					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c')\n",
 						  RELKIND_RELATION, RELKIND_SEQUENCE, RELKIND_VIEW,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 		processSQLNamePattern(GetConnection(fout), query, cell->val, true,
 							  false, "n.nspname", "c.relname", NULL,
 							  "pg_catalog.pg_table_is_visible(c.oid)");
@@ -2117,6 +2118,9 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo, bool oids)
 	/* Skip FOREIGN TABLEs (no data to dump) */
 	if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 		return;
+	/* Skip partitioned tables (data in partitions) */
+	if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+		return;
 
 	/* Don't dump data in unlogged tables, if so requested */
 	if (tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED &&
@@ -5195,6 +5199,7 @@ getTables(Archive *fout, int *numTables)
 	int			i_reloftype;
 	int			i_relpages;
 	int			i_changed_acl;
+	int			i_partkeydef;
 
 	/* Make sure we are in proper schema */
 	selectSourceSchema(fout, "pg_catalog");
@@ -5280,7 +5285,8 @@ getTables(Archive *fout, int *numTables)
 						  "OR %s IS NOT NULL "
 						  "OR %s IS NOT NULL"
 						  "))"
-						  "AS changed_acl "
+						  "AS changed_acl, "
+						  "CASE WHEN c.relkind = 'P' THEN pg_catalog.pg_get_partkeydef(c.oid) ELSE NULL END AS partkeydef "
 						  "FROM pg_class c "
 						  "LEFT JOIN pg_depend d ON "
 						  "(c.relkind = '%c' AND "
@@ -5292,7 +5298,7 @@ getTables(Archive *fout, int *numTables)
 						  "(c.oid = pip.objoid "
 						  "AND pip.classoid = 'pg_class'::regclass "
 						  "AND pip.objsubid = 0) "
-				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c') "
+				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c', '%c') "
 						  "ORDER BY c.oid",
 						  acl_subquery->data,
 						  racl_subquery->data,
@@ -5306,7 +5312,8 @@ getTables(Archive *fout, int *numTables)
 						  RELKIND_SEQUENCE,
 						  RELKIND_RELATION, RELKIND_SEQUENCE,
 						  RELKIND_VIEW, RELKIND_COMPOSITE_TYPE,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 
 		destroyPQExpBuffer(acl_subquery);
 		destroyPQExpBuffer(racl_subquery);
@@ -5868,6 +5875,7 @@ getTables(Archive *fout, int *numTables)
 	i_toastreloptions = PQfnumber(res, "toast_reloptions");
 	i_reloftype = PQfnumber(res, "reloftype");
 	i_changed_acl = PQfnumber(res, "changed_acl");
+	i_partkeydef = PQfnumber(res, "partkeydef");
 
 	if (dopt->lockWaitTimeout && fout->remoteVersion >= 70300)
 	{
@@ -5938,6 +5946,7 @@ getTables(Archive *fout, int *numTables)
 		else
 			tblinfo[i].checkoption = pg_strdup(PQgetvalue(res, i, i_checkoption));
 		tblinfo[i].toast_reloptions = pg_strdup(PQgetvalue(res, i, i_toastreloptions));
+		tblinfo[i].partkeydef = pg_strdup(PQgetvalue(res, i, i_partkeydef));
 
 		/* other fields were zeroed above */
 
@@ -5982,7 +5991,9 @@ getTables(Archive *fout, int *numTables)
 		 * We only need to lock the table for certain components; see
 		 * pg_dump.h
 		 */
-		if (tblinfo[i].dobj.dump && tblinfo[i].relkind == RELKIND_RELATION &&
+		if (tblinfo[i].dobj.dump &&
+			(tblinfo[i].relkind == RELKIND_RELATION ||
+			 tblinfo->relkind == RELKIND_PARTITIONED_TABLE) &&
 			(tblinfo[i].dobj.dump & DUMP_COMPONENTS_REQUIRING_LOCK))
 		{
 			resetPQExpBuffer(query);
@@ -6084,7 +6095,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);
 
@@ -15437,6 +15451,9 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 				appendPQExpBufferChar(q, ')');
 			}
 
+			if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+				appendPQExpBuffer(q, "\nPARTITION BY %s", tbinfo->partkeydef);
+
 			if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 				appendPQExpBuffer(q, "\nSERVER %s", fmtId(srvname));
 		}
@@ -15497,6 +15514,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 		 */
 		if (dopt->binary_upgrade &&
 			(tbinfo->relkind == RELKIND_RELATION ||
+			 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 			 tbinfo->relkind == RELKIND_FOREIGN_TABLE))
 		{
 			for (j = 0; j < tbinfo->numatts; j++)
@@ -15515,7 +15533,8 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 					appendStringLiteralAH(q, fmtId(tbinfo->dobj.name), fout);
 					appendPQExpBufferStr(q, "::pg_catalog.regclass;\n");
 
-					if (tbinfo->relkind == RELKIND_RELATION)
+					if (tbinfo->relkind == RELKIND_RELATION ||
+						tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
 						appendPQExpBuffer(q, "ALTER TABLE ONLY %s ",
 										  fmtId(tbinfo->dobj.name));
 					else
@@ -15732,6 +15751,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 	 * dump properties we only have ALTER TABLE syntax for
 	 */
 	if ((tbinfo->relkind == RELKIND_RELATION ||
+		 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 		 tbinfo->relkind == RELKIND_MATVIEW) &&
 		tbinfo->relreplident != REPLICA_IDENTITY_DEFAULT)
 	{
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 2bfa2d9..0292859 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -310,6 +310,7 @@ typedef struct _tableInfo
 	bool	   *inhNotNull;		/* true if NOT NULL is inherited */
 	struct _attrDefInfo **attrdefs;		/* DEFAULT expressions */
 	struct _constraintInfo *checkexprs; /* CHECK constraints */
+	char	   *partkeydef;		/* partition key definition */
 
 	/*
 	 * Stuff computed only for dumpable tables.
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 6275a68..10d924a 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -861,6 +861,7 @@ permissionsList(const char *pattern)
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'S' THEN '%s'"
@@ -870,6 +871,7 @@ permissionsList(const char *pattern)
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("sequence"),
@@ -920,7 +922,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
@@ -1567,8 +1569,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)");
 	}
 
@@ -1633,6 +1635,14 @@ describeOneTableDetails(const char *schemaname,
 			printfPQExpBuffer(&title, _("Foreign table \"%s.%s\""),
 							  schemaname, relationname);
 			break;
+		case 'P':
+			if (tableinfo.relpersistence == 'u')
+				printfPQExpBuffer(&title, _("Unlogged table \"%s.%s\""),
+								  schemaname, relationname);
+			else
+				printfPQExpBuffer(&title, _("Table \"%s.%s\""),
+								  schemaname, relationname);
+			break;
 		default:
 			/* untranslated unknown relkind */
 			printfPQExpBuffer(&title, "?%c? \"%s.%s\"",
@@ -1646,8 +1656,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");
@@ -1667,12 +1677,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");
 	}
 
@@ -1772,7 +1782,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);
@@ -1780,14 +1790,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 (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 */
@@ -1926,7 +1955,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;
@@ -2485,7 +2514,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;
@@ -2696,7 +2725,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
@@ -3024,6 +3053,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'i' THEN '%s'"
@@ -3035,6 +3065,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("index"),
@@ -3079,7 +3110,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 1345e4e..4686cd1 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/pg_proc.h b/src/include/catalog/pg_proc.h
index e2d08ba..b45688b 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -1980,6 +1980,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/utils/builtins.h b/src/include/utils/builtins.h
index 2ae212a..e800647 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -729,6 +729,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/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 708232d..2fec847 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -408,3 +408,29 @@ CREATE TABLE no_inh_con_parted (
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
 ERROR:  cannot add NO INHERIT constraint to partitioned table "no_inh_con_parted"
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+Table "public.describe_range_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition Key: RANGE ((a + b))
+
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+Table "public.describe_list_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition Key: LIST (a)
+
+DROP TABLE describe_range_key, describe_list_key;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 6e4a8be..4dd6a0a 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -402,3 +402,16 @@ CREATE TABLE no_inh_con_parted (
 	a int,
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
+
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+DROP TABLE describe_range_key, describe_list_key;
-- 
1.7.1


--------------954458C17BBEB1BF6FC758E7
Content-Type: text/x-diff;
 name="0003-Catalog-and-DDL-for-partitions-2.patch"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
 filename="0003-Catalog-and-DDL-for-partitions-2.patch"



^ permalink  raw  reply  [nested|flat] 110+ messages in thread

* [PATCH 2/9] psql and pg_dump support for partitioned tables.
@ 2016-07-12 08:20  amit <[email protected]>
  0 siblings, 0 replies; 110+ messages in thread

From: amit @ 2016-07-12 08:20 UTC (permalink / raw)

Takes care of both the partition key deparse stuff and the new relkind.
---
 src/backend/utils/adt/ruleutils.c          |  140 ++++++++++++++++++++++++++++
 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/pg_proc.h              |    2 +
 src/include/utils/builtins.h               |    1 +
 src/test/regress/expected/create_table.out |   26 +++++
 src/test/regress/sql/create_table.sql      |   13 +++
 9 files changed, 260 insertions(+), 26 deletions(-)

diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 8a81d7a..03be202 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_table.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
@@ -315,6 +316,7 @@ static char *pg_get_indexdef_worker(Oid indexrelid, int colno,
 					   const Oid *excludeOps,
 					   bool attrsOnly, bool showTblSpc,
 					   int prettyFlags, bool missing_ok);
+static char *pg_get_partkeydef_worker(Oid relid, int prettyFlags);
 static char *pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 							int prettyFlags, bool missing_ok);
 static text *pg_get_expr_worker(text *expr, Oid relid, const char *relname,
@@ -1389,6 +1391,144 @@ 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 [ opclass_name ] [, ...])
+ */
+Datum
+pg_get_partkeydef(PG_FUNCTION_ARGS)
+{
+	Oid			relid = PG_GETARG_OID(0);
+	int			prettyFlags;
+
+	prettyFlags = PRETTYFLAG_INDENT;
+	PG_RETURN_TEXT_P(string_to_text(pg_get_partkeydef_worker(relid,
+									prettyFlags)));
+}
+
+/*
+ * Internal workhorse to decompile a partition key definition.
+ */
+static char *
+pg_get_partkeydef_worker(Oid relid, int prettyFlags)
+{
+	Form_pg_partitioned_table	form;
+	HeapTuple	tuple;
+	oidvector  *partclass;
+	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_table) GETSTRUCT(tuple);
+
+	Assert(form->partrelid == relid);
+
+	/* Must get partclass, and partexprs the hard way */
+	datum = SysCacheGetAttr(PARTEDRELID, tuple,
+							Anum_pg_partitioned_table_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_table_partexprbin))
+	{
+		Datum		exprsDatum;
+		bool		isnull;
+		char	   *exprsString;
+
+		exprsDatum = SysCacheGetAttr(PARTEDRELID, tuple,
+									 Anum_pg_partitioned_table_partexprbin, &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);
+
+	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,
+											0, 0);
+
+			appendStringInfoString(&buf, 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/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index ba9c276..c805a84 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -1253,9 +1253,10 @@ expand_table_name_patterns(Archive *fout,
 						  "SELECT c.oid"
 						  "\nFROM pg_catalog.pg_class c"
 		"\n     LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace"
-					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c')\n",
+					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c')\n",
 						  RELKIND_RELATION, RELKIND_SEQUENCE, RELKIND_VIEW,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 		processSQLNamePattern(GetConnection(fout), query, cell->val, true,
 							  false, "n.nspname", "c.relname", NULL,
 							  "pg_catalog.pg_table_is_visible(c.oid)");
@@ -2125,6 +2126,9 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo, bool oids)
 	/* Skip FOREIGN TABLEs (no data to dump) */
 	if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 		return;
+	/* Skip partitioned tables (data in partitions) */
+	if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+		return;
 
 	/* Don't dump data in unlogged tables, if so requested */
 	if (tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED &&
@@ -5204,6 +5208,7 @@ getTables(Archive *fout, int *numTables)
 	int			i_reloftype;
 	int			i_relpages;
 	int			i_changed_acl;
+	int			i_partkeydef;
 
 	/* Make sure we are in proper schema */
 	selectSourceSchema(fout, "pg_catalog");
@@ -5289,7 +5294,8 @@ getTables(Archive *fout, int *numTables)
 						  "OR %s IS NOT NULL "
 						  "OR %s IS NOT NULL"
 						  "))"
-						  "AS changed_acl "
+						  "AS changed_acl, "
+						  "CASE WHEN c.relkind = 'P' THEN pg_catalog.pg_get_partkeydef(c.oid) ELSE NULL END AS partkeydef "
 						  "FROM pg_class c "
 						  "LEFT JOIN pg_depend d ON "
 						  "(c.relkind = '%c' AND "
@@ -5301,7 +5307,7 @@ getTables(Archive *fout, int *numTables)
 						  "(c.oid = pip.objoid "
 						  "AND pip.classoid = 'pg_class'::regclass "
 						  "AND pip.objsubid = 0) "
-				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c') "
+				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c', '%c') "
 						  "ORDER BY c.oid",
 						  acl_subquery->data,
 						  racl_subquery->data,
@@ -5315,7 +5321,8 @@ getTables(Archive *fout, int *numTables)
 						  RELKIND_SEQUENCE,
 						  RELKIND_RELATION, RELKIND_SEQUENCE,
 						  RELKIND_VIEW, RELKIND_COMPOSITE_TYPE,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 
 		destroyPQExpBuffer(acl_subquery);
 		destroyPQExpBuffer(racl_subquery);
@@ -5877,6 +5884,7 @@ getTables(Archive *fout, int *numTables)
 	i_toastreloptions = PQfnumber(res, "toast_reloptions");
 	i_reloftype = PQfnumber(res, "reloftype");
 	i_changed_acl = PQfnumber(res, "changed_acl");
+	i_partkeydef = PQfnumber(res, "partkeydef");
 
 	if (dopt->lockWaitTimeout && fout->remoteVersion >= 70300)
 	{
@@ -5947,6 +5955,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 */
 
@@ -5991,7 +6000,9 @@ getTables(Archive *fout, int *numTables)
 		 * We only need to lock the table for certain components; see
 		 * pg_dump.h
 		 */
-		if (tblinfo[i].dobj.dump && tblinfo[i].relkind == RELKIND_RELATION &&
+		if (tblinfo[i].dobj.dump &&
+			(tblinfo[i].relkind == RELKIND_RELATION ||
+			 tblinfo->relkind == RELKIND_PARTITIONED_TABLE) &&
 			(tblinfo[i].dobj.dump & DUMP_COMPONENTS_REQUIRING_LOCK))
 		{
 			resetPQExpBuffer(query);
@@ -6093,7 +6104,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);
 
@@ -15446,6 +15460,9 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 				appendPQExpBufferChar(q, ')');
 			}
 
+			if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+				appendPQExpBuffer(q, "\nPARTITION BY %s", tbinfo->partkeydef);
+
 			if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 				appendPQExpBuffer(q, "\nSERVER %s", fmtId(srvname));
 		}
@@ -15506,6 +15523,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 		 */
 		if (dopt->binary_upgrade &&
 			(tbinfo->relkind == RELKIND_RELATION ||
+			 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 			 tbinfo->relkind == RELKIND_FOREIGN_TABLE))
 		{
 			for (j = 0; j < tbinfo->numatts; j++)
@@ -15524,7 +15542,8 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 					appendStringLiteralAH(q, fmtId(tbinfo->dobj.name), fout);
 					appendPQExpBufferStr(q, "::pg_catalog.regclass;\n");
 
-					if (tbinfo->relkind == RELKIND_RELATION)
+					if (tbinfo->relkind == RELKIND_RELATION ||
+						tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
 						appendPQExpBuffer(q, "ALTER TABLE ONLY %s ",
 										  fmtId(tbinfo->dobj.name));
 					else
@@ -15741,6 +15760,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 	 * dump properties we only have ALTER TABLE syntax for
 	 */
 	if ((tbinfo->relkind == RELKIND_RELATION ||
+		 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 		 tbinfo->relkind == RELKIND_MATVIEW) &&
 		tbinfo->relreplident != REPLICA_IDENTITY_DEFAULT)
 	{
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 2bfa2d9..0292859 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -310,6 +310,7 @@ typedef struct _tableInfo
 	bool	   *inhNotNull;		/* true if NOT NULL is inherited */
 	struct _attrDefInfo **attrdefs;		/* DEFAULT expressions */
 	struct _constraintInfo *checkexprs; /* CHECK constraints */
+	char	   *partkeydef;		/* partition key definition */
 
 	/*
 	 * Stuff computed only for dumpable tables.
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 6275a68..10d924a 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -861,6 +861,7 @@ permissionsList(const char *pattern)
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'S' THEN '%s'"
@@ -870,6 +871,7 @@ permissionsList(const char *pattern)
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("sequence"),
@@ -920,7 +922,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
@@ -1567,8 +1569,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)");
 	}
 
@@ -1633,6 +1635,14 @@ describeOneTableDetails(const char *schemaname,
 			printfPQExpBuffer(&title, _("Foreign table \"%s.%s\""),
 							  schemaname, relationname);
 			break;
+		case 'P':
+			if (tableinfo.relpersistence == 'u')
+				printfPQExpBuffer(&title, _("Unlogged table \"%s.%s\""),
+								  schemaname, relationname);
+			else
+				printfPQExpBuffer(&title, _("Table \"%s.%s\""),
+								  schemaname, relationname);
+			break;
 		default:
 			/* untranslated unknown relkind */
 			printfPQExpBuffer(&title, "?%c? \"%s.%s\"",
@@ -1646,8 +1656,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");
@@ -1667,12 +1677,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");
 	}
 
@@ -1772,7 +1782,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);
@@ -1780,14 +1790,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 (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 */
@@ -1926,7 +1955,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;
@@ -2485,7 +2514,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;
@@ -2696,7 +2725,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
@@ -3024,6 +3053,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'i' THEN '%s'"
@@ -3035,6 +3065,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("index"),
@@ -3079,7 +3110,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 019f75a..5e5c370 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/pg_proc.h b/src/include/catalog/pg_proc.h
index e2d08ba..b45688b 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -1980,6 +1980,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/utils/builtins.h b/src/include/utils/builtins.h
index 2ae212a..e800647 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -729,6 +729,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/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 708232d..2fec847 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -408,3 +408,29 @@ CREATE TABLE no_inh_con_parted (
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
 ERROR:  cannot add NO INHERIT constraint to partitioned table "no_inh_con_parted"
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+Table "public.describe_range_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition Key: RANGE ((a + b))
+
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+Table "public.describe_list_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition Key: LIST (a)
+
+DROP TABLE describe_range_key, describe_list_key;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 6e4a8be..4dd6a0a 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -402,3 +402,16 @@ CREATE TABLE no_inh_con_parted (
 	a int,
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
+
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+DROP TABLE describe_range_key, describe_list_key;
-- 
1.7.1


--------------4012470FC48539D6F6714CEA
Content-Type: text/x-diff;
 name="0003-Catalog-and-DDL-for-partitions-4.patch"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
 filename="0003-Catalog-and-DDL-for-partitions-4.patch"



^ permalink  raw  reply  [nested|flat] 110+ messages in thread

* [PATCH 2/8] psql and pg_dump support for partitioned tables.
@ 2016-07-12 08:20  amit <[email protected]>
  0 siblings, 0 replies; 110+ messages in thread

From: amit @ 2016-07-12 08:20 UTC (permalink / raw)

Takes care of both the partition key deparse stuff and the new relkind.
---
 src/backend/utils/adt/ruleutils.c          |  147 ++++++++++++++++++++++++++++
 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/pg_proc.h              |    2 +
 src/include/utils/builtins.h               |    1 +
 src/test/regress/expected/create_table.out |   26 +++++
 src/test/regress/sql/create_table.sql      |   13 +++
 9 files changed, 267 insertions(+), 26 deletions(-)

diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index ec966c7..4691b5b 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, bool missing_ok);
+static char *pg_get_partkeydef_worker(Oid relId, int prettyFlags);
 static char *pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 							int prettyFlags, bool missing_ok);
 static text *pg_get_expr_worker(text *expr, Oid relid, const char *relname,
@@ -1389,6 +1391,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 [ 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/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 4ee10fc..a127672 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -1253,9 +1253,10 @@ expand_table_name_patterns(Archive *fout,
 						  "SELECT c.oid"
 						  "\nFROM pg_catalog.pg_class c"
 		"\n     LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace"
-					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c')\n",
+					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c')\n",
 						  RELKIND_RELATION, RELKIND_SEQUENCE, RELKIND_VIEW,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_REL);
 		processSQLNamePattern(GetConnection(fout), query, cell->val, true,
 							  false, "n.nspname", "c.relname", NULL,
 							  "pg_catalog.pg_table_is_visible(c.oid)");
@@ -2117,6 +2118,9 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo, bool oids)
 	/* Skip FOREIGN TABLEs (no data to dump) */
 	if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 		return;
+	/* Skip partitioned tables (data in partitions) */
+	if (tbinfo->relkind == RELKIND_PARTITIONED_REL)
+		return;
 
 	/* Don't dump data in unlogged tables, if so requested */
 	if (tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED &&
@@ -5195,6 +5199,7 @@ getTables(Archive *fout, int *numTables)
 	int			i_reloftype;
 	int			i_relpages;
 	int			i_changed_acl;
+	int			i_partkeydef;
 
 	/* Make sure we are in proper schema */
 	selectSourceSchema(fout, "pg_catalog");
@@ -5280,7 +5285,8 @@ getTables(Archive *fout, int *numTables)
 						  "OR %s IS NOT NULL "
 						  "OR %s IS NOT NULL"
 						  "))"
-						  "AS changed_acl "
+						  "AS changed_acl, "
+						  "CASE WHEN c.relkind = 'P' THEN pg_catalog.pg_get_partkeydef(c.oid) ELSE NULL END AS partkeydef "
 						  "FROM pg_class c "
 						  "LEFT JOIN pg_depend d ON "
 						  "(c.relkind = '%c' AND "
@@ -5292,7 +5298,7 @@ getTables(Archive *fout, int *numTables)
 						  "(c.oid = pip.objoid "
 						  "AND pip.classoid = 'pg_class'::regclass "
 						  "AND pip.objsubid = 0) "
-				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c') "
+				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c', '%c') "
 						  "ORDER BY c.oid",
 						  acl_subquery->data,
 						  racl_subquery->data,
@@ -5306,7 +5312,8 @@ getTables(Archive *fout, int *numTables)
 						  RELKIND_SEQUENCE,
 						  RELKIND_RELATION, RELKIND_SEQUENCE,
 						  RELKIND_VIEW, RELKIND_COMPOSITE_TYPE,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_REL);
 
 		destroyPQExpBuffer(acl_subquery);
 		destroyPQExpBuffer(racl_subquery);
@@ -5868,6 +5875,7 @@ getTables(Archive *fout, int *numTables)
 	i_toastreloptions = PQfnumber(res, "toast_reloptions");
 	i_reloftype = PQfnumber(res, "reloftype");
 	i_changed_acl = PQfnumber(res, "changed_acl");
+	i_partkeydef = PQfnumber(res, "partkeydef");
 
 	if (dopt->lockWaitTimeout && fout->remoteVersion >= 70300)
 	{
@@ -5938,6 +5946,7 @@ getTables(Archive *fout, int *numTables)
 		else
 			tblinfo[i].checkoption = pg_strdup(PQgetvalue(res, i, i_checkoption));
 		tblinfo[i].toast_reloptions = pg_strdup(PQgetvalue(res, i, i_toastreloptions));
+		tblinfo[i].partkeydef = pg_strdup(PQgetvalue(res, i, i_partkeydef));
 
 		/* other fields were zeroed above */
 
@@ -5982,7 +5991,9 @@ getTables(Archive *fout, int *numTables)
 		 * We only need to lock the table for certain components; see
 		 * pg_dump.h
 		 */
-		if (tblinfo[i].dobj.dump && tblinfo[i].relkind == RELKIND_RELATION &&
+		if (tblinfo[i].dobj.dump &&
+			(tblinfo[i].relkind == RELKIND_RELATION ||
+			 tblinfo->relkind == RELKIND_PARTITIONED_REL) &&
 			(tblinfo[i].dobj.dump & DUMP_COMPONENTS_REQUIRING_LOCK))
 		{
 			resetPQExpBuffer(query);
@@ -6083,7 +6094,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);
 
@@ -15436,6 +15450,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));
 		}
@@ -15496,6 +15513,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++)
@@ -15514,7 +15532,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
@@ -15731,6 +15750,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 	 * dump properties we only have ALTER TABLE syntax for
 	 */
 	if ((tbinfo->relkind == RELKIND_RELATION ||
+		 tbinfo->relkind == RELKIND_PARTITIONED_REL ||
 		 tbinfo->relkind == RELKIND_MATVIEW) &&
 		tbinfo->relreplident != REPLICA_IDENTITY_DEFAULT)
 	{
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 2bfa2d9..0292859 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -310,6 +310,7 @@ typedef struct _tableInfo
 	bool	   *inhNotNull;		/* true if NOT NULL is inherited */
 	struct _attrDefInfo **attrdefs;		/* DEFAULT expressions */
 	struct _constraintInfo *checkexprs; /* CHECK constraints */
+	char	   *partkeydef;		/* partition key definition */
 
 	/*
 	 * Stuff computed only for dumpable tables.
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 27be102..3ba8a90 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -856,6 +856,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"),
@@ -865,6 +866,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");
@@ -911,7 +913,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
@@ -1555,8 +1557,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)");
 	}
 
@@ -1621,6 +1623,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\"",
@@ -1634,8 +1644,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");
@@ -1655,12 +1665,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");
 	}
 
@@ -1760,7 +1770,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);
@@ -1768,14 +1778,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 */
@@ -1914,7 +1943,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;
@@ -2473,7 +2502,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;
@@ -2684,7 +2713,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
@@ -3018,6 +3047,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"),
@@ -3029,6 +3059,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"));
 
@@ -3067,7 +3098,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 
 	appendPQExpBufferStr(&buf, "\nWHERE c.relkind IN (");
 	if (showTables)
-		appendPQExpBufferStr(&buf, "'r',");
+		appendPQExpBufferStr(&buf, "'r', 'P',");
 	if (showViews)
 		appendPQExpBufferStr(&buf, "'v',");
 	if (showMatViews)
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 8469d9f..cd4eb7c 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -427,7 +427,7 @@ static const SchemaQuery Query_for_list_of_tables = {
 	/* catname */
 	"pg_catalog.pg_class c",
 	/* selcondition */
-	"c.relkind IN ('r')",
+	"c.relkind IN ('r', 'P')",
 	/* viscondition */
 	"pg_catalog.pg_table_is_visible(c.oid)",
 	/* namespace */
@@ -458,7 +458,7 @@ static const SchemaQuery Query_for_list_of_updatables = {
 	/* catname */
 	"pg_catalog.pg_class c",
 	/* selcondition */
-	"c.relkind IN ('r', 'f', 'v')",
+	"c.relkind IN ('r', 'f', 'v', 'P')",
 	/* viscondition */
 	"pg_catalog.pg_table_is_visible(c.oid)",
 	/* namespace */
@@ -488,7 +488,7 @@ static const SchemaQuery Query_for_list_of_tsvmf = {
 	/* catname */
 	"pg_catalog.pg_class c",
 	/* selcondition */
-	"c.relkind IN ('r', 'S', 'v', 'm', 'f')",
+	"c.relkind IN ('r', 'S', 'v', 'm', 'f', 'P')",
 	/* viscondition */
 	"pg_catalog.pg_table_is_visible(c.oid)",
 	/* namespace */
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index 270dd21..5a444d9 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/utils/builtins.h b/src/include/utils/builtins.h
index 8cebc86..351bfd3 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -722,6 +722,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/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index fb7a1b3..4fc8c06 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -413,3 +413,29 @@ SELECT connoinherit FROM pg_constraint WHERE conrelid = 'no_inh_con_parted'::reg
 (1 row)
 
 DROP TABLE no_inh_con_parted;
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+Partitioned table "public.describe_range_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition Key: PARTITION BY RANGE (((a + b)))
+
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+Partitioned table "public.describe_list_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition Key: PARTITION BY LIST (a)
+
+DROP TABLE describe_range_key, describe_list_key;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index cb17517..7c7a18a 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -404,3 +404,16 @@ CREATE TABLE no_inh_con_parted (
 ) PARTITION BY RANGE (a);
 SELECT connoinherit FROM pg_constraint WHERE conrelid = 'no_inh_con_parted'::regclass;
 DROP TABLE no_inh_con_parted;
+
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+DROP TABLE describe_range_key, describe_list_key;
-- 
1.7.1


--------------8C4C48E640631A327D3D2826
Content-Type: text/x-diff;
 name="0003-Catalog-and-DDL-for-partitions.patch"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
 filename="0003-Catalog-and-DDL-for-partitions.patch"



^ permalink  raw  reply  [nested|flat] 110+ messages in thread

* [PATCH 2/9] psql and pg_dump support for partitioned tables.
@ 2016-07-12 08:20  amit <[email protected]>
  0 siblings, 0 replies; 110+ messages in thread

From: amit @ 2016-07-12 08:20 UTC (permalink / raw)

Takes care of both the partition key deparse stuff and the new relkind.
---
 src/backend/utils/adt/ruleutils.c          |  140 ++++++++++++++++++++++++++++
 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/pg_proc.h              |    2 +
 src/include/utils/builtins.h               |    1 +
 src/test/regress/expected/create_table.out |   26 +++++
 src/test/regress/sql/create_table.sql      |   13 +++
 9 files changed, 260 insertions(+), 26 deletions(-)

diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 8a81d7a..77ce807 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_table.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
@@ -315,6 +316,7 @@ static char *pg_get_indexdef_worker(Oid indexrelid, int colno,
 					   const Oid *excludeOps,
 					   bool attrsOnly, bool showTblSpc,
 					   int prettyFlags, bool missing_ok);
+static char *pg_get_partkeydef_worker(Oid relId, int prettyFlags);
 static char *pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 							int prettyFlags, bool missing_ok);
 static text *pg_get_expr_worker(text *expr, Oid relid, const char *relname,
@@ -1389,6 +1391,144 @@ 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 [ 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_table	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_table) GETSTRUCT(tuple);
+
+	Assert(form->partedrelid == relId);
+
+	/* Must get partclass, and partexprs the hard way */
+	datum = SysCacheGetAttr(PARTEDRELID, tuple,
+							Anum_pg_partitioned_table_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_table_partexprbin))
+	{
+		Datum		exprsDatum;
+		bool		isnull;
+		char	   *exprsString;
+
+		exprsDatum = SysCacheGetAttr(PARTEDRELID, tuple,
+									 Anum_pg_partitioned_table_partexprbin, &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);
+
+	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,
+											0, 0);
+
+			appendStringInfoString(&buf, 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/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index a5c2d09..c418ba7 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -1253,9 +1253,10 @@ expand_table_name_patterns(Archive *fout,
 						  "SELECT c.oid"
 						  "\nFROM pg_catalog.pg_class c"
 		"\n     LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace"
-					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c')\n",
+					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c')\n",
 						  RELKIND_RELATION, RELKIND_SEQUENCE, RELKIND_VIEW,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 		processSQLNamePattern(GetConnection(fout), query, cell->val, true,
 							  false, "n.nspname", "c.relname", NULL,
 							  "pg_catalog.pg_table_is_visible(c.oid)");
@@ -2117,6 +2118,9 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo, bool oids)
 	/* Skip FOREIGN TABLEs (no data to dump) */
 	if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 		return;
+	/* Skip partitioned tables (data in partitions) */
+	if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+		return;
 
 	/* Don't dump data in unlogged tables, if so requested */
 	if (tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED &&
@@ -5195,6 +5199,7 @@ getTables(Archive *fout, int *numTables)
 	int			i_reloftype;
 	int			i_relpages;
 	int			i_changed_acl;
+	int			i_partkeydef;
 
 	/* Make sure we are in proper schema */
 	selectSourceSchema(fout, "pg_catalog");
@@ -5280,7 +5285,8 @@ getTables(Archive *fout, int *numTables)
 						  "OR %s IS NOT NULL "
 						  "OR %s IS NOT NULL"
 						  "))"
-						  "AS changed_acl "
+						  "AS changed_acl, "
+						  "CASE WHEN c.relkind = 'P' THEN pg_catalog.pg_get_partkeydef(c.oid) ELSE NULL END AS partkeydef "
 						  "FROM pg_class c "
 						  "LEFT JOIN pg_depend d ON "
 						  "(c.relkind = '%c' AND "
@@ -5292,7 +5298,7 @@ getTables(Archive *fout, int *numTables)
 						  "(c.oid = pip.objoid "
 						  "AND pip.classoid = 'pg_class'::regclass "
 						  "AND pip.objsubid = 0) "
-				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c') "
+				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c', '%c') "
 						  "ORDER BY c.oid",
 						  acl_subquery->data,
 						  racl_subquery->data,
@@ -5306,7 +5312,8 @@ getTables(Archive *fout, int *numTables)
 						  RELKIND_SEQUENCE,
 						  RELKIND_RELATION, RELKIND_SEQUENCE,
 						  RELKIND_VIEW, RELKIND_COMPOSITE_TYPE,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 
 		destroyPQExpBuffer(acl_subquery);
 		destroyPQExpBuffer(racl_subquery);
@@ -5868,6 +5875,7 @@ getTables(Archive *fout, int *numTables)
 	i_toastreloptions = PQfnumber(res, "toast_reloptions");
 	i_reloftype = PQfnumber(res, "reloftype");
 	i_changed_acl = PQfnumber(res, "changed_acl");
+	i_partkeydef = PQfnumber(res, "partkeydef");
 
 	if (dopt->lockWaitTimeout && fout->remoteVersion >= 70300)
 	{
@@ -5938,6 +5946,7 @@ getTables(Archive *fout, int *numTables)
 		else
 			tblinfo[i].checkoption = pg_strdup(PQgetvalue(res, i, i_checkoption));
 		tblinfo[i].toast_reloptions = pg_strdup(PQgetvalue(res, i, i_toastreloptions));
+		tblinfo[i].partkeydef = pg_strdup(PQgetvalue(res, i, i_partkeydef));
 
 		/* other fields were zeroed above */
 
@@ -5982,7 +5991,9 @@ getTables(Archive *fout, int *numTables)
 		 * We only need to lock the table for certain components; see
 		 * pg_dump.h
 		 */
-		if (tblinfo[i].dobj.dump && tblinfo[i].relkind == RELKIND_RELATION &&
+		if (tblinfo[i].dobj.dump &&
+			(tblinfo[i].relkind == RELKIND_RELATION ||
+			 tblinfo->relkind == RELKIND_PARTITIONED_TABLE) &&
 			(tblinfo[i].dobj.dump & DUMP_COMPONENTS_REQUIRING_LOCK))
 		{
 			resetPQExpBuffer(query);
@@ -6084,7 +6095,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);
 
@@ -15437,6 +15451,9 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 				appendPQExpBufferChar(q, ')');
 			}
 
+			if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+				appendPQExpBuffer(q, "\nPARTITION BY %s", tbinfo->partkeydef);
+
 			if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 				appendPQExpBuffer(q, "\nSERVER %s", fmtId(srvname));
 		}
@@ -15497,6 +15514,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 		 */
 		if (dopt->binary_upgrade &&
 			(tbinfo->relkind == RELKIND_RELATION ||
+			 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 			 tbinfo->relkind == RELKIND_FOREIGN_TABLE))
 		{
 			for (j = 0; j < tbinfo->numatts; j++)
@@ -15515,7 +15533,8 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 					appendStringLiteralAH(q, fmtId(tbinfo->dobj.name), fout);
 					appendPQExpBufferStr(q, "::pg_catalog.regclass;\n");
 
-					if (tbinfo->relkind == RELKIND_RELATION)
+					if (tbinfo->relkind == RELKIND_RELATION ||
+						tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
 						appendPQExpBuffer(q, "ALTER TABLE ONLY %s ",
 										  fmtId(tbinfo->dobj.name));
 					else
@@ -15732,6 +15751,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 	 * dump properties we only have ALTER TABLE syntax for
 	 */
 	if ((tbinfo->relkind == RELKIND_RELATION ||
+		 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 		 tbinfo->relkind == RELKIND_MATVIEW) &&
 		tbinfo->relreplident != REPLICA_IDENTITY_DEFAULT)
 	{
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 2bfa2d9..0292859 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -310,6 +310,7 @@ typedef struct _tableInfo
 	bool	   *inhNotNull;		/* true if NOT NULL is inherited */
 	struct _attrDefInfo **attrdefs;		/* DEFAULT expressions */
 	struct _constraintInfo *checkexprs; /* CHECK constraints */
+	char	   *partkeydef;		/* partition key definition */
 
 	/*
 	 * Stuff computed only for dumpable tables.
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 6275a68..10d924a 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -861,6 +861,7 @@ permissionsList(const char *pattern)
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'S' THEN '%s'"
@@ -870,6 +871,7 @@ permissionsList(const char *pattern)
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("sequence"),
@@ -920,7 +922,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
@@ -1567,8 +1569,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)");
 	}
 
@@ -1633,6 +1635,14 @@ describeOneTableDetails(const char *schemaname,
 			printfPQExpBuffer(&title, _("Foreign table \"%s.%s\""),
 							  schemaname, relationname);
 			break;
+		case 'P':
+			if (tableinfo.relpersistence == 'u')
+				printfPQExpBuffer(&title, _("Unlogged table \"%s.%s\""),
+								  schemaname, relationname);
+			else
+				printfPQExpBuffer(&title, _("Table \"%s.%s\""),
+								  schemaname, relationname);
+			break;
 		default:
 			/* untranslated unknown relkind */
 			printfPQExpBuffer(&title, "?%c? \"%s.%s\"",
@@ -1646,8 +1656,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");
@@ -1667,12 +1677,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");
 	}
 
@@ -1772,7 +1782,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);
@@ -1780,14 +1790,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 (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 */
@@ -1926,7 +1955,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;
@@ -2485,7 +2514,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;
@@ -2696,7 +2725,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
@@ -3024,6 +3053,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'i' THEN '%s'"
@@ -3035,6 +3065,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("index"),
@@ -3079,7 +3110,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 1345e4e..4686cd1 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/pg_proc.h b/src/include/catalog/pg_proc.h
index e2d08ba..b45688b 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -1980,6 +1980,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/utils/builtins.h b/src/include/utils/builtins.h
index 2ae212a..e800647 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -729,6 +729,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/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 708232d..2fec847 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -408,3 +408,29 @@ CREATE TABLE no_inh_con_parted (
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
 ERROR:  cannot add NO INHERIT constraint to partitioned table "no_inh_con_parted"
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+Table "public.describe_range_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition Key: RANGE ((a + b))
+
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+Table "public.describe_list_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition Key: LIST (a)
+
+DROP TABLE describe_range_key, describe_list_key;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 6e4a8be..4dd6a0a 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -402,3 +402,16 @@ CREATE TABLE no_inh_con_parted (
 	a int,
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
+
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+DROP TABLE describe_range_key, describe_list_key;
-- 
1.7.1


--------------954458C17BBEB1BF6FC758E7
Content-Type: text/x-diff;
 name="0003-Catalog-and-DDL-for-partitions-2.patch"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
 filename="0003-Catalog-and-DDL-for-partitions-2.patch"



^ permalink  raw  reply  [nested|flat] 110+ messages in thread

* [PATCH 2/9] psql and pg_dump support for partitioned tables.
@ 2016-07-12 08:20  amit <[email protected]>
  0 siblings, 0 replies; 110+ messages in thread

From: amit @ 2016-07-12 08:20 UTC (permalink / raw)

Takes care of both the partition key deparse stuff and the new relkind.
---
 src/backend/utils/adt/ruleutils.c          |  140 ++++++++++++++++++++++++++++
 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/pg_proc.h              |    2 +
 src/include/utils/builtins.h               |    1 +
 src/test/regress/expected/create_table.out |   26 +++++
 src/test/regress/sql/create_table.sql      |   13 +++
 9 files changed, 260 insertions(+), 26 deletions(-)

diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 8a81d7a..77ce807 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_table.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
@@ -315,6 +316,7 @@ static char *pg_get_indexdef_worker(Oid indexrelid, int colno,
 					   const Oid *excludeOps,
 					   bool attrsOnly, bool showTblSpc,
 					   int prettyFlags, bool missing_ok);
+static char *pg_get_partkeydef_worker(Oid relId, int prettyFlags);
 static char *pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 							int prettyFlags, bool missing_ok);
 static text *pg_get_expr_worker(text *expr, Oid relid, const char *relname,
@@ -1389,6 +1391,144 @@ 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 [ 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_table	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_table) GETSTRUCT(tuple);
+
+	Assert(form->partedrelid == relId);
+
+	/* Must get partclass, and partexprs the hard way */
+	datum = SysCacheGetAttr(PARTEDRELID, tuple,
+							Anum_pg_partitioned_table_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_table_partexprbin))
+	{
+		Datum		exprsDatum;
+		bool		isnull;
+		char	   *exprsString;
+
+		exprsDatum = SysCacheGetAttr(PARTEDRELID, tuple,
+									 Anum_pg_partitioned_table_partexprbin, &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);
+
+	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,
+											0, 0);
+
+			appendStringInfoString(&buf, 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/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index a5c2d09..c418ba7 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -1253,9 +1253,10 @@ expand_table_name_patterns(Archive *fout,
 						  "SELECT c.oid"
 						  "\nFROM pg_catalog.pg_class c"
 		"\n     LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace"
-					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c')\n",
+					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c')\n",
 						  RELKIND_RELATION, RELKIND_SEQUENCE, RELKIND_VIEW,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 		processSQLNamePattern(GetConnection(fout), query, cell->val, true,
 							  false, "n.nspname", "c.relname", NULL,
 							  "pg_catalog.pg_table_is_visible(c.oid)");
@@ -2117,6 +2118,9 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo, bool oids)
 	/* Skip FOREIGN TABLEs (no data to dump) */
 	if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 		return;
+	/* Skip partitioned tables (data in partitions) */
+	if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+		return;
 
 	/* Don't dump data in unlogged tables, if so requested */
 	if (tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED &&
@@ -5195,6 +5199,7 @@ getTables(Archive *fout, int *numTables)
 	int			i_reloftype;
 	int			i_relpages;
 	int			i_changed_acl;
+	int			i_partkeydef;
 
 	/* Make sure we are in proper schema */
 	selectSourceSchema(fout, "pg_catalog");
@@ -5280,7 +5285,8 @@ getTables(Archive *fout, int *numTables)
 						  "OR %s IS NOT NULL "
 						  "OR %s IS NOT NULL"
 						  "))"
-						  "AS changed_acl "
+						  "AS changed_acl, "
+						  "CASE WHEN c.relkind = 'P' THEN pg_catalog.pg_get_partkeydef(c.oid) ELSE NULL END AS partkeydef "
 						  "FROM pg_class c "
 						  "LEFT JOIN pg_depend d ON "
 						  "(c.relkind = '%c' AND "
@@ -5292,7 +5298,7 @@ getTables(Archive *fout, int *numTables)
 						  "(c.oid = pip.objoid "
 						  "AND pip.classoid = 'pg_class'::regclass "
 						  "AND pip.objsubid = 0) "
-				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c') "
+				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c', '%c') "
 						  "ORDER BY c.oid",
 						  acl_subquery->data,
 						  racl_subquery->data,
@@ -5306,7 +5312,8 @@ getTables(Archive *fout, int *numTables)
 						  RELKIND_SEQUENCE,
 						  RELKIND_RELATION, RELKIND_SEQUENCE,
 						  RELKIND_VIEW, RELKIND_COMPOSITE_TYPE,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 
 		destroyPQExpBuffer(acl_subquery);
 		destroyPQExpBuffer(racl_subquery);
@@ -5868,6 +5875,7 @@ getTables(Archive *fout, int *numTables)
 	i_toastreloptions = PQfnumber(res, "toast_reloptions");
 	i_reloftype = PQfnumber(res, "reloftype");
 	i_changed_acl = PQfnumber(res, "changed_acl");
+	i_partkeydef = PQfnumber(res, "partkeydef");
 
 	if (dopt->lockWaitTimeout && fout->remoteVersion >= 70300)
 	{
@@ -5938,6 +5946,7 @@ getTables(Archive *fout, int *numTables)
 		else
 			tblinfo[i].checkoption = pg_strdup(PQgetvalue(res, i, i_checkoption));
 		tblinfo[i].toast_reloptions = pg_strdup(PQgetvalue(res, i, i_toastreloptions));
+		tblinfo[i].partkeydef = pg_strdup(PQgetvalue(res, i, i_partkeydef));
 
 		/* other fields were zeroed above */
 
@@ -5982,7 +5991,9 @@ getTables(Archive *fout, int *numTables)
 		 * We only need to lock the table for certain components; see
 		 * pg_dump.h
 		 */
-		if (tblinfo[i].dobj.dump && tblinfo[i].relkind == RELKIND_RELATION &&
+		if (tblinfo[i].dobj.dump &&
+			(tblinfo[i].relkind == RELKIND_RELATION ||
+			 tblinfo->relkind == RELKIND_PARTITIONED_TABLE) &&
 			(tblinfo[i].dobj.dump & DUMP_COMPONENTS_REQUIRING_LOCK))
 		{
 			resetPQExpBuffer(query);
@@ -6084,7 +6095,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);
 
@@ -15437,6 +15451,9 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 				appendPQExpBufferChar(q, ')');
 			}
 
+			if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+				appendPQExpBuffer(q, "\nPARTITION BY %s", tbinfo->partkeydef);
+
 			if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 				appendPQExpBuffer(q, "\nSERVER %s", fmtId(srvname));
 		}
@@ -15497,6 +15514,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 		 */
 		if (dopt->binary_upgrade &&
 			(tbinfo->relkind == RELKIND_RELATION ||
+			 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 			 tbinfo->relkind == RELKIND_FOREIGN_TABLE))
 		{
 			for (j = 0; j < tbinfo->numatts; j++)
@@ -15515,7 +15533,8 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 					appendStringLiteralAH(q, fmtId(tbinfo->dobj.name), fout);
 					appendPQExpBufferStr(q, "::pg_catalog.regclass;\n");
 
-					if (tbinfo->relkind == RELKIND_RELATION)
+					if (tbinfo->relkind == RELKIND_RELATION ||
+						tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
 						appendPQExpBuffer(q, "ALTER TABLE ONLY %s ",
 										  fmtId(tbinfo->dobj.name));
 					else
@@ -15732,6 +15751,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 	 * dump properties we only have ALTER TABLE syntax for
 	 */
 	if ((tbinfo->relkind == RELKIND_RELATION ||
+		 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 		 tbinfo->relkind == RELKIND_MATVIEW) &&
 		tbinfo->relreplident != REPLICA_IDENTITY_DEFAULT)
 	{
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 2bfa2d9..0292859 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -310,6 +310,7 @@ typedef struct _tableInfo
 	bool	   *inhNotNull;		/* true if NOT NULL is inherited */
 	struct _attrDefInfo **attrdefs;		/* DEFAULT expressions */
 	struct _constraintInfo *checkexprs; /* CHECK constraints */
+	char	   *partkeydef;		/* partition key definition */
 
 	/*
 	 * Stuff computed only for dumpable tables.
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 6275a68..10d924a 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -861,6 +861,7 @@ permissionsList(const char *pattern)
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'S' THEN '%s'"
@@ -870,6 +871,7 @@ permissionsList(const char *pattern)
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("sequence"),
@@ -920,7 +922,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
@@ -1567,8 +1569,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)");
 	}
 
@@ -1633,6 +1635,14 @@ describeOneTableDetails(const char *schemaname,
 			printfPQExpBuffer(&title, _("Foreign table \"%s.%s\""),
 							  schemaname, relationname);
 			break;
+		case 'P':
+			if (tableinfo.relpersistence == 'u')
+				printfPQExpBuffer(&title, _("Unlogged table \"%s.%s\""),
+								  schemaname, relationname);
+			else
+				printfPQExpBuffer(&title, _("Table \"%s.%s\""),
+								  schemaname, relationname);
+			break;
 		default:
 			/* untranslated unknown relkind */
 			printfPQExpBuffer(&title, "?%c? \"%s.%s\"",
@@ -1646,8 +1656,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");
@@ -1667,12 +1677,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");
 	}
 
@@ -1772,7 +1782,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);
@@ -1780,14 +1790,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 (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 */
@@ -1926,7 +1955,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;
@@ -2485,7 +2514,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;
@@ -2696,7 +2725,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
@@ -3024,6 +3053,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'i' THEN '%s'"
@@ -3035,6 +3065,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("index"),
@@ -3079,7 +3110,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 019f75a..5e5c370 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/pg_proc.h b/src/include/catalog/pg_proc.h
index e2d08ba..b45688b 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -1980,6 +1980,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/utils/builtins.h b/src/include/utils/builtins.h
index 2ae212a..e800647 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -729,6 +729,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/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 708232d..2fec847 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -408,3 +408,29 @@ CREATE TABLE no_inh_con_parted (
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
 ERROR:  cannot add NO INHERIT constraint to partitioned table "no_inh_con_parted"
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+Table "public.describe_range_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition Key: RANGE ((a + b))
+
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+Table "public.describe_list_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition Key: LIST (a)
+
+DROP TABLE describe_range_key, describe_list_key;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 6e4a8be..4dd6a0a 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -402,3 +402,16 @@ CREATE TABLE no_inh_con_parted (
 	a int,
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
+
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+DROP TABLE describe_range_key, describe_list_key;
-- 
1.7.1


--------------6EB5B318125CB7DB1A4FD229
Content-Type: text/x-diff;
 name="0003-Catalog-and-DDL-for-partitions-3.patch"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
 filename="0003-Catalog-and-DDL-for-partitions-3.patch"



^ permalink  raw  reply  [nested|flat] 110+ messages in thread

* [PATCH 2/9] psql and pg_dump support for partitioned tables.
@ 2016-07-12 08:20  amit <[email protected]>
  0 siblings, 0 replies; 110+ messages in thread

From: amit @ 2016-07-12 08:20 UTC (permalink / raw)

Takes care of both the partition key deparse stuff and the new relkind.
---
 src/backend/utils/adt/ruleutils.c          |  140 ++++++++++++++++++++++++++++
 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/pg_proc.h              |    2 +
 src/include/utils/builtins.h               |    1 +
 src/test/regress/expected/create_table.out |   26 +++++
 src/test/regress/sql/create_table.sql      |   13 +++
 9 files changed, 260 insertions(+), 26 deletions(-)

diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 8a81d7a..03be202 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_table.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
@@ -315,6 +316,7 @@ static char *pg_get_indexdef_worker(Oid indexrelid, int colno,
 					   const Oid *excludeOps,
 					   bool attrsOnly, bool showTblSpc,
 					   int prettyFlags, bool missing_ok);
+static char *pg_get_partkeydef_worker(Oid relid, int prettyFlags);
 static char *pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 							int prettyFlags, bool missing_ok);
 static text *pg_get_expr_worker(text *expr, Oid relid, const char *relname,
@@ -1389,6 +1391,144 @@ 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 [ opclass_name ] [, ...])
+ */
+Datum
+pg_get_partkeydef(PG_FUNCTION_ARGS)
+{
+	Oid			relid = PG_GETARG_OID(0);
+	int			prettyFlags;
+
+	prettyFlags = PRETTYFLAG_INDENT;
+	PG_RETURN_TEXT_P(string_to_text(pg_get_partkeydef_worker(relid,
+									prettyFlags)));
+}
+
+/*
+ * Internal workhorse to decompile a partition key definition.
+ */
+static char *
+pg_get_partkeydef_worker(Oid relid, int prettyFlags)
+{
+	Form_pg_partitioned_table	form;
+	HeapTuple	tuple;
+	oidvector  *partclass;
+	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_table) GETSTRUCT(tuple);
+
+	Assert(form->partrelid == relid);
+
+	/* Must get partclass, and partexprs the hard way */
+	datum = SysCacheGetAttr(PARTEDRELID, tuple,
+							Anum_pg_partitioned_table_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_table_partexprbin))
+	{
+		Datum		exprsDatum;
+		bool		isnull;
+		char	   *exprsString;
+
+		exprsDatum = SysCacheGetAttr(PARTEDRELID, tuple,
+									 Anum_pg_partitioned_table_partexprbin, &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);
+
+	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,
+											0, 0);
+
+			appendStringInfoString(&buf, 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/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index ba9c276..c805a84 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -1253,9 +1253,10 @@ expand_table_name_patterns(Archive *fout,
 						  "SELECT c.oid"
 						  "\nFROM pg_catalog.pg_class c"
 		"\n     LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace"
-					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c')\n",
+					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c')\n",
 						  RELKIND_RELATION, RELKIND_SEQUENCE, RELKIND_VIEW,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 		processSQLNamePattern(GetConnection(fout), query, cell->val, true,
 							  false, "n.nspname", "c.relname", NULL,
 							  "pg_catalog.pg_table_is_visible(c.oid)");
@@ -2125,6 +2126,9 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo, bool oids)
 	/* Skip FOREIGN TABLEs (no data to dump) */
 	if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 		return;
+	/* Skip partitioned tables (data in partitions) */
+	if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+		return;
 
 	/* Don't dump data in unlogged tables, if so requested */
 	if (tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED &&
@@ -5204,6 +5208,7 @@ getTables(Archive *fout, int *numTables)
 	int			i_reloftype;
 	int			i_relpages;
 	int			i_changed_acl;
+	int			i_partkeydef;
 
 	/* Make sure we are in proper schema */
 	selectSourceSchema(fout, "pg_catalog");
@@ -5289,7 +5294,8 @@ getTables(Archive *fout, int *numTables)
 						  "OR %s IS NOT NULL "
 						  "OR %s IS NOT NULL"
 						  "))"
-						  "AS changed_acl "
+						  "AS changed_acl, "
+						  "CASE WHEN c.relkind = 'P' THEN pg_catalog.pg_get_partkeydef(c.oid) ELSE NULL END AS partkeydef "
 						  "FROM pg_class c "
 						  "LEFT JOIN pg_depend d ON "
 						  "(c.relkind = '%c' AND "
@@ -5301,7 +5307,7 @@ getTables(Archive *fout, int *numTables)
 						  "(c.oid = pip.objoid "
 						  "AND pip.classoid = 'pg_class'::regclass "
 						  "AND pip.objsubid = 0) "
-				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c') "
+				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c', '%c') "
 						  "ORDER BY c.oid",
 						  acl_subquery->data,
 						  racl_subquery->data,
@@ -5315,7 +5321,8 @@ getTables(Archive *fout, int *numTables)
 						  RELKIND_SEQUENCE,
 						  RELKIND_RELATION, RELKIND_SEQUENCE,
 						  RELKIND_VIEW, RELKIND_COMPOSITE_TYPE,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 
 		destroyPQExpBuffer(acl_subquery);
 		destroyPQExpBuffer(racl_subquery);
@@ -5877,6 +5884,7 @@ getTables(Archive *fout, int *numTables)
 	i_toastreloptions = PQfnumber(res, "toast_reloptions");
 	i_reloftype = PQfnumber(res, "reloftype");
 	i_changed_acl = PQfnumber(res, "changed_acl");
+	i_partkeydef = PQfnumber(res, "partkeydef");
 
 	if (dopt->lockWaitTimeout && fout->remoteVersion >= 70300)
 	{
@@ -5947,6 +5955,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 */
 
@@ -5991,7 +6000,9 @@ getTables(Archive *fout, int *numTables)
 		 * We only need to lock the table for certain components; see
 		 * pg_dump.h
 		 */
-		if (tblinfo[i].dobj.dump && tblinfo[i].relkind == RELKIND_RELATION &&
+		if (tblinfo[i].dobj.dump &&
+			(tblinfo[i].relkind == RELKIND_RELATION ||
+			 tblinfo->relkind == RELKIND_PARTITIONED_TABLE) &&
 			(tblinfo[i].dobj.dump & DUMP_COMPONENTS_REQUIRING_LOCK))
 		{
 			resetPQExpBuffer(query);
@@ -6093,7 +6104,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);
 
@@ -15446,6 +15460,9 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 				appendPQExpBufferChar(q, ')');
 			}
 
+			if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+				appendPQExpBuffer(q, "\nPARTITION BY %s", tbinfo->partkeydef);
+
 			if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 				appendPQExpBuffer(q, "\nSERVER %s", fmtId(srvname));
 		}
@@ -15506,6 +15523,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 		 */
 		if (dopt->binary_upgrade &&
 			(tbinfo->relkind == RELKIND_RELATION ||
+			 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 			 tbinfo->relkind == RELKIND_FOREIGN_TABLE))
 		{
 			for (j = 0; j < tbinfo->numatts; j++)
@@ -15524,7 +15542,8 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 					appendStringLiteralAH(q, fmtId(tbinfo->dobj.name), fout);
 					appendPQExpBufferStr(q, "::pg_catalog.regclass;\n");
 
-					if (tbinfo->relkind == RELKIND_RELATION)
+					if (tbinfo->relkind == RELKIND_RELATION ||
+						tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
 						appendPQExpBuffer(q, "ALTER TABLE ONLY %s ",
 										  fmtId(tbinfo->dobj.name));
 					else
@@ -15741,6 +15760,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 	 * dump properties we only have ALTER TABLE syntax for
 	 */
 	if ((tbinfo->relkind == RELKIND_RELATION ||
+		 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 		 tbinfo->relkind == RELKIND_MATVIEW) &&
 		tbinfo->relreplident != REPLICA_IDENTITY_DEFAULT)
 	{
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 2bfa2d9..0292859 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -310,6 +310,7 @@ typedef struct _tableInfo
 	bool	   *inhNotNull;		/* true if NOT NULL is inherited */
 	struct _attrDefInfo **attrdefs;		/* DEFAULT expressions */
 	struct _constraintInfo *checkexprs; /* CHECK constraints */
+	char	   *partkeydef;		/* partition key definition */
 
 	/*
 	 * Stuff computed only for dumpable tables.
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 6275a68..10d924a 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -861,6 +861,7 @@ permissionsList(const char *pattern)
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'S' THEN '%s'"
@@ -870,6 +871,7 @@ permissionsList(const char *pattern)
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("sequence"),
@@ -920,7 +922,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
@@ -1567,8 +1569,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)");
 	}
 
@@ -1633,6 +1635,14 @@ describeOneTableDetails(const char *schemaname,
 			printfPQExpBuffer(&title, _("Foreign table \"%s.%s\""),
 							  schemaname, relationname);
 			break;
+		case 'P':
+			if (tableinfo.relpersistence == 'u')
+				printfPQExpBuffer(&title, _("Unlogged table \"%s.%s\""),
+								  schemaname, relationname);
+			else
+				printfPQExpBuffer(&title, _("Table \"%s.%s\""),
+								  schemaname, relationname);
+			break;
 		default:
 			/* untranslated unknown relkind */
 			printfPQExpBuffer(&title, "?%c? \"%s.%s\"",
@@ -1646,8 +1656,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");
@@ -1667,12 +1677,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");
 	}
 
@@ -1772,7 +1782,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);
@@ -1780,14 +1790,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 (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 */
@@ -1926,7 +1955,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;
@@ -2485,7 +2514,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;
@@ -2696,7 +2725,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
@@ -3024,6 +3053,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'i' THEN '%s'"
@@ -3035,6 +3065,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("index"),
@@ -3079,7 +3110,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 019f75a..5e5c370 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/pg_proc.h b/src/include/catalog/pg_proc.h
index e2d08ba..b45688b 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -1980,6 +1980,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/utils/builtins.h b/src/include/utils/builtins.h
index 2ae212a..e800647 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -729,6 +729,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/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 708232d..2fec847 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -408,3 +408,29 @@ CREATE TABLE no_inh_con_parted (
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
 ERROR:  cannot add NO INHERIT constraint to partitioned table "no_inh_con_parted"
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+Table "public.describe_range_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition Key: RANGE ((a + b))
+
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+Table "public.describe_list_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition Key: LIST (a)
+
+DROP TABLE describe_range_key, describe_list_key;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 6e4a8be..4dd6a0a 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -402,3 +402,16 @@ CREATE TABLE no_inh_con_parted (
 	a int,
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
+
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+DROP TABLE describe_range_key, describe_list_key;
-- 
1.7.1


--------------4012470FC48539D6F6714CEA
Content-Type: text/x-diff;
 name="0003-Catalog-and-DDL-for-partitions-4.patch"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
 filename="0003-Catalog-and-DDL-for-partitions-4.patch"



^ permalink  raw  reply  [nested|flat] 110+ messages in thread

* [PATCH 2/9] psql and pg_dump support for partitioned tables.
@ 2016-07-12 08:20  amit <[email protected]>
  0 siblings, 0 replies; 110+ messages in thread

From: amit @ 2016-07-12 08:20 UTC (permalink / raw)

Takes care of both the partition key deparse stuff and the new relkind.
---
 src/backend/utils/adt/ruleutils.c          |  140 ++++++++++++++++++++++++++++
 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/pg_proc.h              |    2 +
 src/include/utils/builtins.h               |    1 +
 src/test/regress/expected/create_table.out |   26 +++++
 src/test/regress/sql/create_table.sql      |   13 +++
 9 files changed, 260 insertions(+), 26 deletions(-)

diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 8a81d7a..03be202 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_table.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
@@ -315,6 +316,7 @@ static char *pg_get_indexdef_worker(Oid indexrelid, int colno,
 					   const Oid *excludeOps,
 					   bool attrsOnly, bool showTblSpc,
 					   int prettyFlags, bool missing_ok);
+static char *pg_get_partkeydef_worker(Oid relid, int prettyFlags);
 static char *pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 							int prettyFlags, bool missing_ok);
 static text *pg_get_expr_worker(text *expr, Oid relid, const char *relname,
@@ -1389,6 +1391,144 @@ 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 [ opclass_name ] [, ...])
+ */
+Datum
+pg_get_partkeydef(PG_FUNCTION_ARGS)
+{
+	Oid			relid = PG_GETARG_OID(0);
+	int			prettyFlags;
+
+	prettyFlags = PRETTYFLAG_INDENT;
+	PG_RETURN_TEXT_P(string_to_text(pg_get_partkeydef_worker(relid,
+									prettyFlags)));
+}
+
+/*
+ * Internal workhorse to decompile a partition key definition.
+ */
+static char *
+pg_get_partkeydef_worker(Oid relid, int prettyFlags)
+{
+	Form_pg_partitioned_table	form;
+	HeapTuple	tuple;
+	oidvector  *partclass;
+	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_table) GETSTRUCT(tuple);
+
+	Assert(form->partrelid == relid);
+
+	/* Must get partclass, and partexprs the hard way */
+	datum = SysCacheGetAttr(PARTEDRELID, tuple,
+							Anum_pg_partitioned_table_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_table_partexprbin))
+	{
+		Datum		exprsDatum;
+		bool		isnull;
+		char	   *exprsString;
+
+		exprsDatum = SysCacheGetAttr(PARTEDRELID, tuple,
+									 Anum_pg_partitioned_table_partexprbin, &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);
+
+	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,
+											0, 0);
+
+			appendStringInfoString(&buf, 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/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index ba9c276..c805a84 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -1253,9 +1253,10 @@ expand_table_name_patterns(Archive *fout,
 						  "SELECT c.oid"
 						  "\nFROM pg_catalog.pg_class c"
 		"\n     LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace"
-					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c')\n",
+					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c')\n",
 						  RELKIND_RELATION, RELKIND_SEQUENCE, RELKIND_VIEW,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 		processSQLNamePattern(GetConnection(fout), query, cell->val, true,
 							  false, "n.nspname", "c.relname", NULL,
 							  "pg_catalog.pg_table_is_visible(c.oid)");
@@ -2125,6 +2126,9 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo, bool oids)
 	/* Skip FOREIGN TABLEs (no data to dump) */
 	if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 		return;
+	/* Skip partitioned tables (data in partitions) */
+	if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+		return;
 
 	/* Don't dump data in unlogged tables, if so requested */
 	if (tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED &&
@@ -5204,6 +5208,7 @@ getTables(Archive *fout, int *numTables)
 	int			i_reloftype;
 	int			i_relpages;
 	int			i_changed_acl;
+	int			i_partkeydef;
 
 	/* Make sure we are in proper schema */
 	selectSourceSchema(fout, "pg_catalog");
@@ -5289,7 +5294,8 @@ getTables(Archive *fout, int *numTables)
 						  "OR %s IS NOT NULL "
 						  "OR %s IS NOT NULL"
 						  "))"
-						  "AS changed_acl "
+						  "AS changed_acl, "
+						  "CASE WHEN c.relkind = 'P' THEN pg_catalog.pg_get_partkeydef(c.oid) ELSE NULL END AS partkeydef "
 						  "FROM pg_class c "
 						  "LEFT JOIN pg_depend d ON "
 						  "(c.relkind = '%c' AND "
@@ -5301,7 +5307,7 @@ getTables(Archive *fout, int *numTables)
 						  "(c.oid = pip.objoid "
 						  "AND pip.classoid = 'pg_class'::regclass "
 						  "AND pip.objsubid = 0) "
-				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c') "
+				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c', '%c') "
 						  "ORDER BY c.oid",
 						  acl_subquery->data,
 						  racl_subquery->data,
@@ -5315,7 +5321,8 @@ getTables(Archive *fout, int *numTables)
 						  RELKIND_SEQUENCE,
 						  RELKIND_RELATION, RELKIND_SEQUENCE,
 						  RELKIND_VIEW, RELKIND_COMPOSITE_TYPE,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 
 		destroyPQExpBuffer(acl_subquery);
 		destroyPQExpBuffer(racl_subquery);
@@ -5877,6 +5884,7 @@ getTables(Archive *fout, int *numTables)
 	i_toastreloptions = PQfnumber(res, "toast_reloptions");
 	i_reloftype = PQfnumber(res, "reloftype");
 	i_changed_acl = PQfnumber(res, "changed_acl");
+	i_partkeydef = PQfnumber(res, "partkeydef");
 
 	if (dopt->lockWaitTimeout && fout->remoteVersion >= 70300)
 	{
@@ -5947,6 +5955,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 */
 
@@ -5991,7 +6000,9 @@ getTables(Archive *fout, int *numTables)
 		 * We only need to lock the table for certain components; see
 		 * pg_dump.h
 		 */
-		if (tblinfo[i].dobj.dump && tblinfo[i].relkind == RELKIND_RELATION &&
+		if (tblinfo[i].dobj.dump &&
+			(tblinfo[i].relkind == RELKIND_RELATION ||
+			 tblinfo->relkind == RELKIND_PARTITIONED_TABLE) &&
 			(tblinfo[i].dobj.dump & DUMP_COMPONENTS_REQUIRING_LOCK))
 		{
 			resetPQExpBuffer(query);
@@ -6093,7 +6104,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);
 
@@ -15446,6 +15460,9 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 				appendPQExpBufferChar(q, ')');
 			}
 
+			if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+				appendPQExpBuffer(q, "\nPARTITION BY %s", tbinfo->partkeydef);
+
 			if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 				appendPQExpBuffer(q, "\nSERVER %s", fmtId(srvname));
 		}
@@ -15506,6 +15523,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 		 */
 		if (dopt->binary_upgrade &&
 			(tbinfo->relkind == RELKIND_RELATION ||
+			 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 			 tbinfo->relkind == RELKIND_FOREIGN_TABLE))
 		{
 			for (j = 0; j < tbinfo->numatts; j++)
@@ -15524,7 +15542,8 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 					appendStringLiteralAH(q, fmtId(tbinfo->dobj.name), fout);
 					appendPQExpBufferStr(q, "::pg_catalog.regclass;\n");
 
-					if (tbinfo->relkind == RELKIND_RELATION)
+					if (tbinfo->relkind == RELKIND_RELATION ||
+						tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
 						appendPQExpBuffer(q, "ALTER TABLE ONLY %s ",
 										  fmtId(tbinfo->dobj.name));
 					else
@@ -15741,6 +15760,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 	 * dump properties we only have ALTER TABLE syntax for
 	 */
 	if ((tbinfo->relkind == RELKIND_RELATION ||
+		 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 		 tbinfo->relkind == RELKIND_MATVIEW) &&
 		tbinfo->relreplident != REPLICA_IDENTITY_DEFAULT)
 	{
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 2bfa2d9..0292859 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -310,6 +310,7 @@ typedef struct _tableInfo
 	bool	   *inhNotNull;		/* true if NOT NULL is inherited */
 	struct _attrDefInfo **attrdefs;		/* DEFAULT expressions */
 	struct _constraintInfo *checkexprs; /* CHECK constraints */
+	char	   *partkeydef;		/* partition key definition */
 
 	/*
 	 * Stuff computed only for dumpable tables.
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 6275a68..10d924a 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -861,6 +861,7 @@ permissionsList(const char *pattern)
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'S' THEN '%s'"
@@ -870,6 +871,7 @@ permissionsList(const char *pattern)
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("sequence"),
@@ -920,7 +922,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
@@ -1567,8 +1569,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)");
 	}
 
@@ -1633,6 +1635,14 @@ describeOneTableDetails(const char *schemaname,
 			printfPQExpBuffer(&title, _("Foreign table \"%s.%s\""),
 							  schemaname, relationname);
 			break;
+		case 'P':
+			if (tableinfo.relpersistence == 'u')
+				printfPQExpBuffer(&title, _("Unlogged table \"%s.%s\""),
+								  schemaname, relationname);
+			else
+				printfPQExpBuffer(&title, _("Table \"%s.%s\""),
+								  schemaname, relationname);
+			break;
 		default:
 			/* untranslated unknown relkind */
 			printfPQExpBuffer(&title, "?%c? \"%s.%s\"",
@@ -1646,8 +1656,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");
@@ -1667,12 +1677,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");
 	}
 
@@ -1772,7 +1782,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);
@@ -1780,14 +1790,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 (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 */
@@ -1926,7 +1955,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;
@@ -2485,7 +2514,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;
@@ -2696,7 +2725,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
@@ -3024,6 +3053,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'i' THEN '%s'"
@@ -3035,6 +3065,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("index"),
@@ -3079,7 +3110,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 50a45eb..8284a9c 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/pg_proc.h b/src/include/catalog/pg_proc.h
index e2d08ba..b45688b 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -1980,6 +1980,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/utils/builtins.h b/src/include/utils/builtins.h
index 2ae212a..e800647 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -729,6 +729,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/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 0af1422..91ba1be 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -412,3 +412,29 @@ CREATE TABLE no_inh_con_parted (
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
 ERROR:  cannot add NO INHERIT constraint to partitioned table "no_inh_con_parted"
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+Table "public.describe_range_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition Key: RANGE ((a + b))
+
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+Table "public.describe_list_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition Key: LIST (a)
+
+DROP TABLE describe_range_key, describe_list_key;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 418ac55..b75542d 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -406,3 +406,16 @@ CREATE TABLE no_inh_con_parted (
 	a int,
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
+
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+DROP TABLE describe_range_key, describe_list_key;
-- 
1.7.1


--------------D008BA677674C4836D7B65AD
Content-Type: text/x-diff;
 name="0003-Catalog-and-DDL-for-partitions-5.patch"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
 filename="0003-Catalog-and-DDL-for-partitions-5.patch"



^ permalink  raw  reply  [nested|flat] 110+ messages in thread

* [PATCH 2/9] psql and pg_dump support for partitioned tables.
@ 2016-07-12 08:20  amit <[email protected]>
  0 siblings, 0 replies; 110+ messages in thread

From: amit @ 2016-07-12 08:20 UTC (permalink / raw)

Takes care of both the partition key deparse stuff and the new relkind.
---
 src/backend/utils/adt/ruleutils.c          |  140 ++++++++++++++++++++++++++++
 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/pg_proc.h              |    2 +
 src/include/utils/builtins.h               |    1 +
 src/test/regress/expected/create_table.out |   26 +++++
 src/test/regress/sql/create_table.sql      |   13 +++
 9 files changed, 260 insertions(+), 26 deletions(-)

diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 8a81d7a..03be202 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_table.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
@@ -315,6 +316,7 @@ static char *pg_get_indexdef_worker(Oid indexrelid, int colno,
 					   const Oid *excludeOps,
 					   bool attrsOnly, bool showTblSpc,
 					   int prettyFlags, bool missing_ok);
+static char *pg_get_partkeydef_worker(Oid relid, int prettyFlags);
 static char *pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 							int prettyFlags, bool missing_ok);
 static text *pg_get_expr_worker(text *expr, Oid relid, const char *relname,
@@ -1389,6 +1391,144 @@ 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 [ opclass_name ] [, ...])
+ */
+Datum
+pg_get_partkeydef(PG_FUNCTION_ARGS)
+{
+	Oid			relid = PG_GETARG_OID(0);
+	int			prettyFlags;
+
+	prettyFlags = PRETTYFLAG_INDENT;
+	PG_RETURN_TEXT_P(string_to_text(pg_get_partkeydef_worker(relid,
+									prettyFlags)));
+}
+
+/*
+ * Internal workhorse to decompile a partition key definition.
+ */
+static char *
+pg_get_partkeydef_worker(Oid relid, int prettyFlags)
+{
+	Form_pg_partitioned_table	form;
+	HeapTuple	tuple;
+	oidvector  *partclass;
+	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_table) GETSTRUCT(tuple);
+
+	Assert(form->partrelid == relid);
+
+	/* Must get partclass, and partexprs the hard way */
+	datum = SysCacheGetAttr(PARTEDRELID, tuple,
+							Anum_pg_partitioned_table_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_table_partexprbin))
+	{
+		Datum		exprsDatum;
+		bool		isnull;
+		char	   *exprsString;
+
+		exprsDatum = SysCacheGetAttr(PARTEDRELID, tuple,
+									 Anum_pg_partitioned_table_partexprbin, &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);
+
+	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,
+											0, 0);
+
+			appendStringInfoString(&buf, 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/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 51b8a1a..854dddc 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -1253,9 +1253,10 @@ expand_table_name_patterns(Archive *fout,
 						  "SELECT c.oid"
 						  "\nFROM pg_catalog.pg_class c"
 		"\n     LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace"
-					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c')\n",
+					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c')\n",
 						  RELKIND_RELATION, RELKIND_SEQUENCE, RELKIND_VIEW,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 		processSQLNamePattern(GetConnection(fout), query, cell->val, true,
 							  false, "n.nspname", "c.relname", NULL,
 							  "pg_catalog.pg_table_is_visible(c.oid)");
@@ -2125,6 +2126,9 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo, bool oids)
 	/* Skip FOREIGN TABLEs (no data to dump) */
 	if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 		return;
+	/* Skip partitioned tables (data in partitions) */
+	if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+		return;
 
 	/* Don't dump data in unlogged tables, if so requested */
 	if (tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED &&
@@ -5211,6 +5215,7 @@ getTables(Archive *fout, int *numTables)
 	int			i_reloftype;
 	int			i_relpages;
 	int			i_changed_acl;
+	int			i_partkeydef;
 
 	/* Make sure we are in proper schema */
 	selectSourceSchema(fout, "pg_catalog");
@@ -5296,7 +5301,8 @@ getTables(Archive *fout, int *numTables)
 						  "OR %s IS NOT NULL "
 						  "OR %s IS NOT NULL"
 						  "))"
-						  "AS changed_acl "
+						  "AS changed_acl, "
+						  "CASE WHEN c.relkind = 'P' THEN pg_catalog.pg_get_partkeydef(c.oid) ELSE NULL END AS partkeydef "
 						  "FROM pg_class c "
 						  "LEFT JOIN pg_depend d ON "
 						  "(c.relkind = '%c' AND "
@@ -5308,7 +5314,7 @@ getTables(Archive *fout, int *numTables)
 						  "(c.oid = pip.objoid "
 						  "AND pip.classoid = 'pg_class'::regclass "
 						  "AND pip.objsubid = 0) "
-				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c') "
+				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c', '%c') "
 						  "ORDER BY c.oid",
 						  acl_subquery->data,
 						  racl_subquery->data,
@@ -5322,7 +5328,8 @@ getTables(Archive *fout, int *numTables)
 						  RELKIND_SEQUENCE,
 						  RELKIND_RELATION, RELKIND_SEQUENCE,
 						  RELKIND_VIEW, RELKIND_COMPOSITE_TYPE,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 
 		destroyPQExpBuffer(acl_subquery);
 		destroyPQExpBuffer(racl_subquery);
@@ -5884,6 +5891,7 @@ getTables(Archive *fout, int *numTables)
 	i_toastreloptions = PQfnumber(res, "toast_reloptions");
 	i_reloftype = PQfnumber(res, "reloftype");
 	i_changed_acl = PQfnumber(res, "changed_acl");
+	i_partkeydef = PQfnumber(res, "partkeydef");
 
 	if (dopt->lockWaitTimeout && fout->remoteVersion >= 70300)
 	{
@@ -5954,6 +5962,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 */
 
@@ -5998,7 +6007,9 @@ getTables(Archive *fout, int *numTables)
 		 * We only need to lock the table for certain components; see
 		 * pg_dump.h
 		 */
-		if (tblinfo[i].dobj.dump && tblinfo[i].relkind == RELKIND_RELATION &&
+		if (tblinfo[i].dobj.dump &&
+			(tblinfo[i].relkind == RELKIND_RELATION ||
+			 tblinfo->relkind == RELKIND_PARTITIONED_TABLE) &&
 			(tblinfo[i].dobj.dump & DUMP_COMPONENTS_REQUIRING_LOCK))
 		{
 			resetPQExpBuffer(query);
@@ -6100,7 +6111,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);
 
@@ -15453,6 +15467,9 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 				appendPQExpBufferChar(q, ')');
 			}
 
+			if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+				appendPQExpBuffer(q, "\nPARTITION BY %s", tbinfo->partkeydef);
+
 			if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 				appendPQExpBuffer(q, "\nSERVER %s", fmtId(srvname));
 		}
@@ -15513,6 +15530,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 		 */
 		if (dopt->binary_upgrade &&
 			(tbinfo->relkind == RELKIND_RELATION ||
+			 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 			 tbinfo->relkind == RELKIND_FOREIGN_TABLE))
 		{
 			for (j = 0; j < tbinfo->numatts; j++)
@@ -15531,7 +15549,8 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 					appendStringLiteralAH(q, fmtId(tbinfo->dobj.name), fout);
 					appendPQExpBufferStr(q, "::pg_catalog.regclass;\n");
 
-					if (tbinfo->relkind == RELKIND_RELATION)
+					if (tbinfo->relkind == RELKIND_RELATION ||
+						tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
 						appendPQExpBuffer(q, "ALTER TABLE ONLY %s ",
 										  fmtId(tbinfo->dobj.name));
 					else
@@ -15748,6 +15767,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 	 * dump properties we only have ALTER TABLE syntax for
 	 */
 	if ((tbinfo->relkind == RELKIND_RELATION ||
+		 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 		 tbinfo->relkind == RELKIND_MATVIEW) &&
 		tbinfo->relreplident != REPLICA_IDENTITY_DEFAULT)
 	{
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 2bfa2d9..0292859 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -310,6 +310,7 @@ typedef struct _tableInfo
 	bool	   *inhNotNull;		/* true if NOT NULL is inherited */
 	struct _attrDefInfo **attrdefs;		/* DEFAULT expressions */
 	struct _constraintInfo *checkexprs; /* CHECK constraints */
+	char	   *partkeydef;		/* partition key definition */
 
 	/*
 	 * Stuff computed only for dumpable tables.
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 6275a68..db6dc5c 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -861,6 +861,7 @@ permissionsList(const char *pattern)
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'S' THEN '%s'"
@@ -870,6 +871,7 @@ permissionsList(const char *pattern)
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("sequence"),
@@ -920,7 +922,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
@@ -1567,8 +1569,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)");
 	}
 
@@ -1633,6 +1635,14 @@ describeOneTableDetails(const char *schemaname,
 			printfPQExpBuffer(&title, _("Foreign table \"%s.%s\""),
 							  schemaname, relationname);
 			break;
+		case 'P':
+			if (tableinfo.relpersistence == 'u')
+				printfPQExpBuffer(&title, _("Unlogged table \"%s.%s\""),
+								  schemaname, relationname);
+			else
+				printfPQExpBuffer(&title, _("Table \"%s.%s\""),
+								  schemaname, relationname);
+			break;
 		default:
 			/* untranslated unknown relkind */
 			printfPQExpBuffer(&title, "?%c? \"%s.%s\"",
@@ -1646,8 +1656,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");
@@ -1667,12 +1677,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");
 	}
 
@@ -1772,7 +1782,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);
@@ -1780,14 +1790,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 (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 */
@@ -1926,7 +1955,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;
@@ -2485,7 +2514,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;
@@ -2696,7 +2725,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
@@ -3024,6 +3053,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'i' THEN '%s'"
@@ -3035,6 +3065,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("index"),
@@ -3079,7 +3110,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 50a45eb..8284a9c 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/pg_proc.h b/src/include/catalog/pg_proc.h
index e2d08ba..b45688b 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -1980,6 +1980,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/utils/builtins.h b/src/include/utils/builtins.h
index 2ae212a..e800647 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -729,6 +729,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/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 1394362..260ec34 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -407,3 +407,29 @@ CREATE TABLE no_inh_con_parted (
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
 ERROR:  cannot add NO INHERIT constraint to partitioned table "no_inh_con_parted"
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+Table "public.describe_range_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition key: RANGE ((a + b))
+
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+Table "public.describe_list_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition key: LIST (a)
+
+DROP TABLE describe_range_key, describe_list_key;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index d383f58..7a1f8de 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -401,3 +401,16 @@ CREATE TABLE no_inh_con_parted (
 	a int,
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
+
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+DROP TABLE describe_range_key, describe_list_key;
-- 
1.7.1


--------------C3235EE96C69D8DAE4DB543E
Content-Type: text/x-diff;
 name="0003-Catalog-and-DDL-for-partitions-6.patch"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
 filename="0003-Catalog-and-DDL-for-partitions-6.patch"



^ permalink  raw  reply  [nested|flat] 110+ messages in thread

* [PATCH 2/8] psql and pg_dump support for partitioned tables.
@ 2016-07-12 08:20  amit <[email protected]>
  0 siblings, 0 replies; 110+ messages in thread

From: amit @ 2016-07-12 08:20 UTC (permalink / raw)

Takes care of both the partition key deparse stuff and the new relkind.
---
 src/backend/utils/adt/ruleutils.c          |  147 ++++++++++++++++++++++++++++
 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/pg_proc.h              |    2 +
 src/include/utils/builtins.h               |    1 +
 src/test/regress/expected/create_table.out |   26 +++++
 src/test/regress/sql/create_table.sql      |   13 +++
 9 files changed, 267 insertions(+), 26 deletions(-)

diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index ec966c7..4691b5b 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, bool missing_ok);
+static char *pg_get_partkeydef_worker(Oid relId, int prettyFlags);
 static char *pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 							int prettyFlags, bool missing_ok);
 static text *pg_get_expr_worker(text *expr, Oid relid, const char *relname,
@@ -1389,6 +1391,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 [ 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/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 4ee10fc..a127672 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -1253,9 +1253,10 @@ expand_table_name_patterns(Archive *fout,
 						  "SELECT c.oid"
 						  "\nFROM pg_catalog.pg_class c"
 		"\n     LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace"
-					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c')\n",
+					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c')\n",
 						  RELKIND_RELATION, RELKIND_SEQUENCE, RELKIND_VIEW,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_REL);
 		processSQLNamePattern(GetConnection(fout), query, cell->val, true,
 							  false, "n.nspname", "c.relname", NULL,
 							  "pg_catalog.pg_table_is_visible(c.oid)");
@@ -2117,6 +2118,9 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo, bool oids)
 	/* Skip FOREIGN TABLEs (no data to dump) */
 	if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 		return;
+	/* Skip partitioned tables (data in partitions) */
+	if (tbinfo->relkind == RELKIND_PARTITIONED_REL)
+		return;
 
 	/* Don't dump data in unlogged tables, if so requested */
 	if (tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED &&
@@ -5195,6 +5199,7 @@ getTables(Archive *fout, int *numTables)
 	int			i_reloftype;
 	int			i_relpages;
 	int			i_changed_acl;
+	int			i_partkeydef;
 
 	/* Make sure we are in proper schema */
 	selectSourceSchema(fout, "pg_catalog");
@@ -5280,7 +5285,8 @@ getTables(Archive *fout, int *numTables)
 						  "OR %s IS NOT NULL "
 						  "OR %s IS NOT NULL"
 						  "))"
-						  "AS changed_acl "
+						  "AS changed_acl, "
+						  "CASE WHEN c.relkind = 'P' THEN pg_catalog.pg_get_partkeydef(c.oid) ELSE NULL END AS partkeydef "
 						  "FROM pg_class c "
 						  "LEFT JOIN pg_depend d ON "
 						  "(c.relkind = '%c' AND "
@@ -5292,7 +5298,7 @@ getTables(Archive *fout, int *numTables)
 						  "(c.oid = pip.objoid "
 						  "AND pip.classoid = 'pg_class'::regclass "
 						  "AND pip.objsubid = 0) "
-				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c') "
+				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c', '%c') "
 						  "ORDER BY c.oid",
 						  acl_subquery->data,
 						  racl_subquery->data,
@@ -5306,7 +5312,8 @@ getTables(Archive *fout, int *numTables)
 						  RELKIND_SEQUENCE,
 						  RELKIND_RELATION, RELKIND_SEQUENCE,
 						  RELKIND_VIEW, RELKIND_COMPOSITE_TYPE,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_REL);
 
 		destroyPQExpBuffer(acl_subquery);
 		destroyPQExpBuffer(racl_subquery);
@@ -5868,6 +5875,7 @@ getTables(Archive *fout, int *numTables)
 	i_toastreloptions = PQfnumber(res, "toast_reloptions");
 	i_reloftype = PQfnumber(res, "reloftype");
 	i_changed_acl = PQfnumber(res, "changed_acl");
+	i_partkeydef = PQfnumber(res, "partkeydef");
 
 	if (dopt->lockWaitTimeout && fout->remoteVersion >= 70300)
 	{
@@ -5938,6 +5946,7 @@ getTables(Archive *fout, int *numTables)
 		else
 			tblinfo[i].checkoption = pg_strdup(PQgetvalue(res, i, i_checkoption));
 		tblinfo[i].toast_reloptions = pg_strdup(PQgetvalue(res, i, i_toastreloptions));
+		tblinfo[i].partkeydef = pg_strdup(PQgetvalue(res, i, i_partkeydef));
 
 		/* other fields were zeroed above */
 
@@ -5982,7 +5991,9 @@ getTables(Archive *fout, int *numTables)
 		 * We only need to lock the table for certain components; see
 		 * pg_dump.h
 		 */
-		if (tblinfo[i].dobj.dump && tblinfo[i].relkind == RELKIND_RELATION &&
+		if (tblinfo[i].dobj.dump &&
+			(tblinfo[i].relkind == RELKIND_RELATION ||
+			 tblinfo->relkind == RELKIND_PARTITIONED_REL) &&
 			(tblinfo[i].dobj.dump & DUMP_COMPONENTS_REQUIRING_LOCK))
 		{
 			resetPQExpBuffer(query);
@@ -6083,7 +6094,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);
 
@@ -15436,6 +15450,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));
 		}
@@ -15496,6 +15513,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++)
@@ -15514,7 +15532,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
@@ -15731,6 +15750,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 	 * dump properties we only have ALTER TABLE syntax for
 	 */
 	if ((tbinfo->relkind == RELKIND_RELATION ||
+		 tbinfo->relkind == RELKIND_PARTITIONED_REL ||
 		 tbinfo->relkind == RELKIND_MATVIEW) &&
 		tbinfo->relreplident != REPLICA_IDENTITY_DEFAULT)
 	{
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 2bfa2d9..0292859 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -310,6 +310,7 @@ typedef struct _tableInfo
 	bool	   *inhNotNull;		/* true if NOT NULL is inherited */
 	struct _attrDefInfo **attrdefs;		/* DEFAULT expressions */
 	struct _constraintInfo *checkexprs; /* CHECK constraints */
+	char	   *partkeydef;		/* partition key definition */
 
 	/*
 	 * Stuff computed only for dumpable tables.
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 27be102..3ba8a90 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -856,6 +856,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"),
@@ -865,6 +866,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");
@@ -911,7 +913,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
@@ -1555,8 +1557,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)");
 	}
 
@@ -1621,6 +1623,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\"",
@@ -1634,8 +1644,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");
@@ -1655,12 +1665,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");
 	}
 
@@ -1760,7 +1770,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);
@@ -1768,14 +1778,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 */
@@ -1914,7 +1943,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;
@@ -2473,7 +2502,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;
@@ -2684,7 +2713,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
@@ -3018,6 +3047,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"),
@@ -3029,6 +3059,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"));
 
@@ -3067,7 +3098,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 
 	appendPQExpBufferStr(&buf, "\nWHERE c.relkind IN (");
 	if (showTables)
-		appendPQExpBufferStr(&buf, "'r',");
+		appendPQExpBufferStr(&buf, "'r', 'P',");
 	if (showViews)
 		appendPQExpBufferStr(&buf, "'v',");
 	if (showMatViews)
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 8469d9f..cd4eb7c 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -427,7 +427,7 @@ static const SchemaQuery Query_for_list_of_tables = {
 	/* catname */
 	"pg_catalog.pg_class c",
 	/* selcondition */
-	"c.relkind IN ('r')",
+	"c.relkind IN ('r', 'P')",
 	/* viscondition */
 	"pg_catalog.pg_table_is_visible(c.oid)",
 	/* namespace */
@@ -458,7 +458,7 @@ static const SchemaQuery Query_for_list_of_updatables = {
 	/* catname */
 	"pg_catalog.pg_class c",
 	/* selcondition */
-	"c.relkind IN ('r', 'f', 'v')",
+	"c.relkind IN ('r', 'f', 'v', 'P')",
 	/* viscondition */
 	"pg_catalog.pg_table_is_visible(c.oid)",
 	/* namespace */
@@ -488,7 +488,7 @@ static const SchemaQuery Query_for_list_of_tsvmf = {
 	/* catname */
 	"pg_catalog.pg_class c",
 	/* selcondition */
-	"c.relkind IN ('r', 'S', 'v', 'm', 'f')",
+	"c.relkind IN ('r', 'S', 'v', 'm', 'f', 'P')",
 	/* viscondition */
 	"pg_catalog.pg_table_is_visible(c.oid)",
 	/* namespace */
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index 270dd21..5a444d9 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/utils/builtins.h b/src/include/utils/builtins.h
index 8cebc86..351bfd3 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -722,6 +722,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/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index fb7a1b3..4fc8c06 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -413,3 +413,29 @@ SELECT connoinherit FROM pg_constraint WHERE conrelid = 'no_inh_con_parted'::reg
 (1 row)
 
 DROP TABLE no_inh_con_parted;
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+Partitioned table "public.describe_range_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition Key: PARTITION BY RANGE (((a + b)))
+
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+Partitioned table "public.describe_list_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition Key: PARTITION BY LIST (a)
+
+DROP TABLE describe_range_key, describe_list_key;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index cb17517..7c7a18a 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -404,3 +404,16 @@ CREATE TABLE no_inh_con_parted (
 ) PARTITION BY RANGE (a);
 SELECT connoinherit FROM pg_constraint WHERE conrelid = 'no_inh_con_parted'::regclass;
 DROP TABLE no_inh_con_parted;
+
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+DROP TABLE describe_range_key, describe_list_key;
-- 
1.7.1


--------------8C4C48E640631A327D3D2826
Content-Type: text/x-diff;
 name="0003-Catalog-and-DDL-for-partitions.patch"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
 filename="0003-Catalog-and-DDL-for-partitions.patch"



^ permalink  raw  reply  [nested|flat] 110+ messages in thread

* [PATCH 2/9] psql and pg_dump support for partitioned tables.
@ 2016-07-12 08:20  amit <[email protected]>
  0 siblings, 0 replies; 110+ messages in thread

From: amit @ 2016-07-12 08:20 UTC (permalink / raw)

Takes care of both the partition key deparse stuff and the new relkind.
---
 src/backend/utils/adt/ruleutils.c          |  140 ++++++++++++++++++++++++++++
 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/pg_proc.h              |    2 +
 src/include/utils/builtins.h               |    1 +
 src/test/regress/expected/create_table.out |   26 +++++
 src/test/regress/sql/create_table.sql      |   13 +++
 9 files changed, 260 insertions(+), 26 deletions(-)

diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 8a81d7a..77ce807 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_table.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
@@ -315,6 +316,7 @@ static char *pg_get_indexdef_worker(Oid indexrelid, int colno,
 					   const Oid *excludeOps,
 					   bool attrsOnly, bool showTblSpc,
 					   int prettyFlags, bool missing_ok);
+static char *pg_get_partkeydef_worker(Oid relId, int prettyFlags);
 static char *pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 							int prettyFlags, bool missing_ok);
 static text *pg_get_expr_worker(text *expr, Oid relid, const char *relname,
@@ -1389,6 +1391,144 @@ 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 [ 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_table	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_table) GETSTRUCT(tuple);
+
+	Assert(form->partedrelid == relId);
+
+	/* Must get partclass, and partexprs the hard way */
+	datum = SysCacheGetAttr(PARTEDRELID, tuple,
+							Anum_pg_partitioned_table_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_table_partexprbin))
+	{
+		Datum		exprsDatum;
+		bool		isnull;
+		char	   *exprsString;
+
+		exprsDatum = SysCacheGetAttr(PARTEDRELID, tuple,
+									 Anum_pg_partitioned_table_partexprbin, &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);
+
+	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,
+											0, 0);
+
+			appendStringInfoString(&buf, 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/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index a5c2d09..c418ba7 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -1253,9 +1253,10 @@ expand_table_name_patterns(Archive *fout,
 						  "SELECT c.oid"
 						  "\nFROM pg_catalog.pg_class c"
 		"\n     LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace"
-					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c')\n",
+					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c')\n",
 						  RELKIND_RELATION, RELKIND_SEQUENCE, RELKIND_VIEW,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 		processSQLNamePattern(GetConnection(fout), query, cell->val, true,
 							  false, "n.nspname", "c.relname", NULL,
 							  "pg_catalog.pg_table_is_visible(c.oid)");
@@ -2117,6 +2118,9 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo, bool oids)
 	/* Skip FOREIGN TABLEs (no data to dump) */
 	if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 		return;
+	/* Skip partitioned tables (data in partitions) */
+	if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+		return;
 
 	/* Don't dump data in unlogged tables, if so requested */
 	if (tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED &&
@@ -5195,6 +5199,7 @@ getTables(Archive *fout, int *numTables)
 	int			i_reloftype;
 	int			i_relpages;
 	int			i_changed_acl;
+	int			i_partkeydef;
 
 	/* Make sure we are in proper schema */
 	selectSourceSchema(fout, "pg_catalog");
@@ -5280,7 +5285,8 @@ getTables(Archive *fout, int *numTables)
 						  "OR %s IS NOT NULL "
 						  "OR %s IS NOT NULL"
 						  "))"
-						  "AS changed_acl "
+						  "AS changed_acl, "
+						  "CASE WHEN c.relkind = 'P' THEN pg_catalog.pg_get_partkeydef(c.oid) ELSE NULL END AS partkeydef "
 						  "FROM pg_class c "
 						  "LEFT JOIN pg_depend d ON "
 						  "(c.relkind = '%c' AND "
@@ -5292,7 +5298,7 @@ getTables(Archive *fout, int *numTables)
 						  "(c.oid = pip.objoid "
 						  "AND pip.classoid = 'pg_class'::regclass "
 						  "AND pip.objsubid = 0) "
-				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c') "
+				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c', '%c') "
 						  "ORDER BY c.oid",
 						  acl_subquery->data,
 						  racl_subquery->data,
@@ -5306,7 +5312,8 @@ getTables(Archive *fout, int *numTables)
 						  RELKIND_SEQUENCE,
 						  RELKIND_RELATION, RELKIND_SEQUENCE,
 						  RELKIND_VIEW, RELKIND_COMPOSITE_TYPE,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 
 		destroyPQExpBuffer(acl_subquery);
 		destroyPQExpBuffer(racl_subquery);
@@ -5868,6 +5875,7 @@ getTables(Archive *fout, int *numTables)
 	i_toastreloptions = PQfnumber(res, "toast_reloptions");
 	i_reloftype = PQfnumber(res, "reloftype");
 	i_changed_acl = PQfnumber(res, "changed_acl");
+	i_partkeydef = PQfnumber(res, "partkeydef");
 
 	if (dopt->lockWaitTimeout && fout->remoteVersion >= 70300)
 	{
@@ -5938,6 +5946,7 @@ getTables(Archive *fout, int *numTables)
 		else
 			tblinfo[i].checkoption = pg_strdup(PQgetvalue(res, i, i_checkoption));
 		tblinfo[i].toast_reloptions = pg_strdup(PQgetvalue(res, i, i_toastreloptions));
+		tblinfo[i].partkeydef = pg_strdup(PQgetvalue(res, i, i_partkeydef));
 
 		/* other fields were zeroed above */
 
@@ -5982,7 +5991,9 @@ getTables(Archive *fout, int *numTables)
 		 * We only need to lock the table for certain components; see
 		 * pg_dump.h
 		 */
-		if (tblinfo[i].dobj.dump && tblinfo[i].relkind == RELKIND_RELATION &&
+		if (tblinfo[i].dobj.dump &&
+			(tblinfo[i].relkind == RELKIND_RELATION ||
+			 tblinfo->relkind == RELKIND_PARTITIONED_TABLE) &&
 			(tblinfo[i].dobj.dump & DUMP_COMPONENTS_REQUIRING_LOCK))
 		{
 			resetPQExpBuffer(query);
@@ -6084,7 +6095,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);
 
@@ -15437,6 +15451,9 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 				appendPQExpBufferChar(q, ')');
 			}
 
+			if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+				appendPQExpBuffer(q, "\nPARTITION BY %s", tbinfo->partkeydef);
+
 			if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 				appendPQExpBuffer(q, "\nSERVER %s", fmtId(srvname));
 		}
@@ -15497,6 +15514,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 		 */
 		if (dopt->binary_upgrade &&
 			(tbinfo->relkind == RELKIND_RELATION ||
+			 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 			 tbinfo->relkind == RELKIND_FOREIGN_TABLE))
 		{
 			for (j = 0; j < tbinfo->numatts; j++)
@@ -15515,7 +15533,8 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 					appendStringLiteralAH(q, fmtId(tbinfo->dobj.name), fout);
 					appendPQExpBufferStr(q, "::pg_catalog.regclass;\n");
 
-					if (tbinfo->relkind == RELKIND_RELATION)
+					if (tbinfo->relkind == RELKIND_RELATION ||
+						tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
 						appendPQExpBuffer(q, "ALTER TABLE ONLY %s ",
 										  fmtId(tbinfo->dobj.name));
 					else
@@ -15732,6 +15751,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 	 * dump properties we only have ALTER TABLE syntax for
 	 */
 	if ((tbinfo->relkind == RELKIND_RELATION ||
+		 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 		 tbinfo->relkind == RELKIND_MATVIEW) &&
 		tbinfo->relreplident != REPLICA_IDENTITY_DEFAULT)
 	{
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 2bfa2d9..0292859 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -310,6 +310,7 @@ typedef struct _tableInfo
 	bool	   *inhNotNull;		/* true if NOT NULL is inherited */
 	struct _attrDefInfo **attrdefs;		/* DEFAULT expressions */
 	struct _constraintInfo *checkexprs; /* CHECK constraints */
+	char	   *partkeydef;		/* partition key definition */
 
 	/*
 	 * Stuff computed only for dumpable tables.
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 6275a68..10d924a 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -861,6 +861,7 @@ permissionsList(const char *pattern)
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'S' THEN '%s'"
@@ -870,6 +871,7 @@ permissionsList(const char *pattern)
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("sequence"),
@@ -920,7 +922,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
@@ -1567,8 +1569,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)");
 	}
 
@@ -1633,6 +1635,14 @@ describeOneTableDetails(const char *schemaname,
 			printfPQExpBuffer(&title, _("Foreign table \"%s.%s\""),
 							  schemaname, relationname);
 			break;
+		case 'P':
+			if (tableinfo.relpersistence == 'u')
+				printfPQExpBuffer(&title, _("Unlogged table \"%s.%s\""),
+								  schemaname, relationname);
+			else
+				printfPQExpBuffer(&title, _("Table \"%s.%s\""),
+								  schemaname, relationname);
+			break;
 		default:
 			/* untranslated unknown relkind */
 			printfPQExpBuffer(&title, "?%c? \"%s.%s\"",
@@ -1646,8 +1656,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");
@@ -1667,12 +1677,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");
 	}
 
@@ -1772,7 +1782,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);
@@ -1780,14 +1790,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 (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 */
@@ -1926,7 +1955,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;
@@ -2485,7 +2514,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;
@@ -2696,7 +2725,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
@@ -3024,6 +3053,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'i' THEN '%s'"
@@ -3035,6 +3065,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("index"),
@@ -3079,7 +3110,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 1345e4e..4686cd1 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/pg_proc.h b/src/include/catalog/pg_proc.h
index e2d08ba..b45688b 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -1980,6 +1980,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/utils/builtins.h b/src/include/utils/builtins.h
index 2ae212a..e800647 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -729,6 +729,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/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 708232d..2fec847 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -408,3 +408,29 @@ CREATE TABLE no_inh_con_parted (
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
 ERROR:  cannot add NO INHERIT constraint to partitioned table "no_inh_con_parted"
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+Table "public.describe_range_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition Key: RANGE ((a + b))
+
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+Table "public.describe_list_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition Key: LIST (a)
+
+DROP TABLE describe_range_key, describe_list_key;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 6e4a8be..4dd6a0a 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -402,3 +402,16 @@ CREATE TABLE no_inh_con_parted (
 	a int,
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
+
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+DROP TABLE describe_range_key, describe_list_key;
-- 
1.7.1


--------------954458C17BBEB1BF6FC758E7
Content-Type: text/x-diff;
 name="0003-Catalog-and-DDL-for-partitions-2.patch"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
 filename="0003-Catalog-and-DDL-for-partitions-2.patch"



^ permalink  raw  reply  [nested|flat] 110+ messages in thread

* [PATCH 2/9] psql and pg_dump support for partitioned tables.
@ 2016-07-12 08:20  amit <[email protected]>
  0 siblings, 0 replies; 110+ messages in thread

From: amit @ 2016-07-12 08:20 UTC (permalink / raw)

Takes care of both the partition key deparse stuff and the new relkind.
---
 src/backend/utils/adt/ruleutils.c          |  140 ++++++++++++++++++++++++++++
 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/pg_proc.h              |    2 +
 src/include/utils/builtins.h               |    1 +
 src/test/regress/expected/create_table.out |   26 +++++
 src/test/regress/sql/create_table.sql      |   13 +++
 9 files changed, 260 insertions(+), 26 deletions(-)

diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 8a81d7a..77ce807 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_table.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
@@ -315,6 +316,7 @@ static char *pg_get_indexdef_worker(Oid indexrelid, int colno,
 					   const Oid *excludeOps,
 					   bool attrsOnly, bool showTblSpc,
 					   int prettyFlags, bool missing_ok);
+static char *pg_get_partkeydef_worker(Oid relId, int prettyFlags);
 static char *pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 							int prettyFlags, bool missing_ok);
 static text *pg_get_expr_worker(text *expr, Oid relid, const char *relname,
@@ -1389,6 +1391,144 @@ 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 [ 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_table	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_table) GETSTRUCT(tuple);
+
+	Assert(form->partedrelid == relId);
+
+	/* Must get partclass, and partexprs the hard way */
+	datum = SysCacheGetAttr(PARTEDRELID, tuple,
+							Anum_pg_partitioned_table_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_table_partexprbin))
+	{
+		Datum		exprsDatum;
+		bool		isnull;
+		char	   *exprsString;
+
+		exprsDatum = SysCacheGetAttr(PARTEDRELID, tuple,
+									 Anum_pg_partitioned_table_partexprbin, &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);
+
+	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,
+											0, 0);
+
+			appendStringInfoString(&buf, 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/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index a5c2d09..c418ba7 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -1253,9 +1253,10 @@ expand_table_name_patterns(Archive *fout,
 						  "SELECT c.oid"
 						  "\nFROM pg_catalog.pg_class c"
 		"\n     LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace"
-					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c')\n",
+					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c')\n",
 						  RELKIND_RELATION, RELKIND_SEQUENCE, RELKIND_VIEW,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 		processSQLNamePattern(GetConnection(fout), query, cell->val, true,
 							  false, "n.nspname", "c.relname", NULL,
 							  "pg_catalog.pg_table_is_visible(c.oid)");
@@ -2117,6 +2118,9 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo, bool oids)
 	/* Skip FOREIGN TABLEs (no data to dump) */
 	if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 		return;
+	/* Skip partitioned tables (data in partitions) */
+	if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+		return;
 
 	/* Don't dump data in unlogged tables, if so requested */
 	if (tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED &&
@@ -5195,6 +5199,7 @@ getTables(Archive *fout, int *numTables)
 	int			i_reloftype;
 	int			i_relpages;
 	int			i_changed_acl;
+	int			i_partkeydef;
 
 	/* Make sure we are in proper schema */
 	selectSourceSchema(fout, "pg_catalog");
@@ -5280,7 +5285,8 @@ getTables(Archive *fout, int *numTables)
 						  "OR %s IS NOT NULL "
 						  "OR %s IS NOT NULL"
 						  "))"
-						  "AS changed_acl "
+						  "AS changed_acl, "
+						  "CASE WHEN c.relkind = 'P' THEN pg_catalog.pg_get_partkeydef(c.oid) ELSE NULL END AS partkeydef "
 						  "FROM pg_class c "
 						  "LEFT JOIN pg_depend d ON "
 						  "(c.relkind = '%c' AND "
@@ -5292,7 +5298,7 @@ getTables(Archive *fout, int *numTables)
 						  "(c.oid = pip.objoid "
 						  "AND pip.classoid = 'pg_class'::regclass "
 						  "AND pip.objsubid = 0) "
-				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c') "
+				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c', '%c') "
 						  "ORDER BY c.oid",
 						  acl_subquery->data,
 						  racl_subquery->data,
@@ -5306,7 +5312,8 @@ getTables(Archive *fout, int *numTables)
 						  RELKIND_SEQUENCE,
 						  RELKIND_RELATION, RELKIND_SEQUENCE,
 						  RELKIND_VIEW, RELKIND_COMPOSITE_TYPE,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 
 		destroyPQExpBuffer(acl_subquery);
 		destroyPQExpBuffer(racl_subquery);
@@ -5868,6 +5875,7 @@ getTables(Archive *fout, int *numTables)
 	i_toastreloptions = PQfnumber(res, "toast_reloptions");
 	i_reloftype = PQfnumber(res, "reloftype");
 	i_changed_acl = PQfnumber(res, "changed_acl");
+	i_partkeydef = PQfnumber(res, "partkeydef");
 
 	if (dopt->lockWaitTimeout && fout->remoteVersion >= 70300)
 	{
@@ -5938,6 +5946,7 @@ getTables(Archive *fout, int *numTables)
 		else
 			tblinfo[i].checkoption = pg_strdup(PQgetvalue(res, i, i_checkoption));
 		tblinfo[i].toast_reloptions = pg_strdup(PQgetvalue(res, i, i_toastreloptions));
+		tblinfo[i].partkeydef = pg_strdup(PQgetvalue(res, i, i_partkeydef));
 
 		/* other fields were zeroed above */
 
@@ -5982,7 +5991,9 @@ getTables(Archive *fout, int *numTables)
 		 * We only need to lock the table for certain components; see
 		 * pg_dump.h
 		 */
-		if (tblinfo[i].dobj.dump && tblinfo[i].relkind == RELKIND_RELATION &&
+		if (tblinfo[i].dobj.dump &&
+			(tblinfo[i].relkind == RELKIND_RELATION ||
+			 tblinfo->relkind == RELKIND_PARTITIONED_TABLE) &&
 			(tblinfo[i].dobj.dump & DUMP_COMPONENTS_REQUIRING_LOCK))
 		{
 			resetPQExpBuffer(query);
@@ -6084,7 +6095,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);
 
@@ -15437,6 +15451,9 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 				appendPQExpBufferChar(q, ')');
 			}
 
+			if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+				appendPQExpBuffer(q, "\nPARTITION BY %s", tbinfo->partkeydef);
+
 			if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 				appendPQExpBuffer(q, "\nSERVER %s", fmtId(srvname));
 		}
@@ -15497,6 +15514,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 		 */
 		if (dopt->binary_upgrade &&
 			(tbinfo->relkind == RELKIND_RELATION ||
+			 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 			 tbinfo->relkind == RELKIND_FOREIGN_TABLE))
 		{
 			for (j = 0; j < tbinfo->numatts; j++)
@@ -15515,7 +15533,8 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 					appendStringLiteralAH(q, fmtId(tbinfo->dobj.name), fout);
 					appendPQExpBufferStr(q, "::pg_catalog.regclass;\n");
 
-					if (tbinfo->relkind == RELKIND_RELATION)
+					if (tbinfo->relkind == RELKIND_RELATION ||
+						tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
 						appendPQExpBuffer(q, "ALTER TABLE ONLY %s ",
 										  fmtId(tbinfo->dobj.name));
 					else
@@ -15732,6 +15751,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 	 * dump properties we only have ALTER TABLE syntax for
 	 */
 	if ((tbinfo->relkind == RELKIND_RELATION ||
+		 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 		 tbinfo->relkind == RELKIND_MATVIEW) &&
 		tbinfo->relreplident != REPLICA_IDENTITY_DEFAULT)
 	{
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 2bfa2d9..0292859 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -310,6 +310,7 @@ typedef struct _tableInfo
 	bool	   *inhNotNull;		/* true if NOT NULL is inherited */
 	struct _attrDefInfo **attrdefs;		/* DEFAULT expressions */
 	struct _constraintInfo *checkexprs; /* CHECK constraints */
+	char	   *partkeydef;		/* partition key definition */
 
 	/*
 	 * Stuff computed only for dumpable tables.
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 6275a68..10d924a 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -861,6 +861,7 @@ permissionsList(const char *pattern)
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'S' THEN '%s'"
@@ -870,6 +871,7 @@ permissionsList(const char *pattern)
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("sequence"),
@@ -920,7 +922,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
@@ -1567,8 +1569,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)");
 	}
 
@@ -1633,6 +1635,14 @@ describeOneTableDetails(const char *schemaname,
 			printfPQExpBuffer(&title, _("Foreign table \"%s.%s\""),
 							  schemaname, relationname);
 			break;
+		case 'P':
+			if (tableinfo.relpersistence == 'u')
+				printfPQExpBuffer(&title, _("Unlogged table \"%s.%s\""),
+								  schemaname, relationname);
+			else
+				printfPQExpBuffer(&title, _("Table \"%s.%s\""),
+								  schemaname, relationname);
+			break;
 		default:
 			/* untranslated unknown relkind */
 			printfPQExpBuffer(&title, "?%c? \"%s.%s\"",
@@ -1646,8 +1656,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");
@@ -1667,12 +1677,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");
 	}
 
@@ -1772,7 +1782,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);
@@ -1780,14 +1790,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 (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 */
@@ -1926,7 +1955,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;
@@ -2485,7 +2514,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;
@@ -2696,7 +2725,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
@@ -3024,6 +3053,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'i' THEN '%s'"
@@ -3035,6 +3065,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("index"),
@@ -3079,7 +3110,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 019f75a..5e5c370 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/pg_proc.h b/src/include/catalog/pg_proc.h
index e2d08ba..b45688b 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -1980,6 +1980,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/utils/builtins.h b/src/include/utils/builtins.h
index 2ae212a..e800647 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -729,6 +729,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/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 708232d..2fec847 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -408,3 +408,29 @@ CREATE TABLE no_inh_con_parted (
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
 ERROR:  cannot add NO INHERIT constraint to partitioned table "no_inh_con_parted"
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+Table "public.describe_range_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition Key: RANGE ((a + b))
+
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+Table "public.describe_list_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition Key: LIST (a)
+
+DROP TABLE describe_range_key, describe_list_key;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 6e4a8be..4dd6a0a 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -402,3 +402,16 @@ CREATE TABLE no_inh_con_parted (
 	a int,
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
+
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+DROP TABLE describe_range_key, describe_list_key;
-- 
1.7.1


--------------6EB5B318125CB7DB1A4FD229
Content-Type: text/x-diff;
 name="0003-Catalog-and-DDL-for-partitions-3.patch"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
 filename="0003-Catalog-and-DDL-for-partitions-3.patch"



^ permalink  raw  reply  [nested|flat] 110+ messages in thread

* [PATCH 2/9] psql and pg_dump support for partitioned tables.
@ 2016-07-12 08:20  amit <[email protected]>
  0 siblings, 0 replies; 110+ messages in thread

From: amit @ 2016-07-12 08:20 UTC (permalink / raw)

Takes care of both the partition key deparse stuff and the new relkind.
---
 src/backend/utils/adt/ruleutils.c          |  140 ++++++++++++++++++++++++++++
 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/pg_proc.h              |    2 +
 src/include/utils/builtins.h               |    1 +
 src/test/regress/expected/create_table.out |   26 +++++
 src/test/regress/sql/create_table.sql      |   13 +++
 9 files changed, 260 insertions(+), 26 deletions(-)

diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 8a81d7a..03be202 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_table.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
@@ -315,6 +316,7 @@ static char *pg_get_indexdef_worker(Oid indexrelid, int colno,
 					   const Oid *excludeOps,
 					   bool attrsOnly, bool showTblSpc,
 					   int prettyFlags, bool missing_ok);
+static char *pg_get_partkeydef_worker(Oid relid, int prettyFlags);
 static char *pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 							int prettyFlags, bool missing_ok);
 static text *pg_get_expr_worker(text *expr, Oid relid, const char *relname,
@@ -1389,6 +1391,144 @@ 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 [ opclass_name ] [, ...])
+ */
+Datum
+pg_get_partkeydef(PG_FUNCTION_ARGS)
+{
+	Oid			relid = PG_GETARG_OID(0);
+	int			prettyFlags;
+
+	prettyFlags = PRETTYFLAG_INDENT;
+	PG_RETURN_TEXT_P(string_to_text(pg_get_partkeydef_worker(relid,
+									prettyFlags)));
+}
+
+/*
+ * Internal workhorse to decompile a partition key definition.
+ */
+static char *
+pg_get_partkeydef_worker(Oid relid, int prettyFlags)
+{
+	Form_pg_partitioned_table	form;
+	HeapTuple	tuple;
+	oidvector  *partclass;
+	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_table) GETSTRUCT(tuple);
+
+	Assert(form->partrelid == relid);
+
+	/* Must get partclass, and partexprs the hard way */
+	datum = SysCacheGetAttr(PARTEDRELID, tuple,
+							Anum_pg_partitioned_table_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_table_partexprbin))
+	{
+		Datum		exprsDatum;
+		bool		isnull;
+		char	   *exprsString;
+
+		exprsDatum = SysCacheGetAttr(PARTEDRELID, tuple,
+									 Anum_pg_partitioned_table_partexprbin, &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);
+
+	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,
+											0, 0);
+
+			appendStringInfoString(&buf, 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/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index ba9c276..c805a84 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -1253,9 +1253,10 @@ expand_table_name_patterns(Archive *fout,
 						  "SELECT c.oid"
 						  "\nFROM pg_catalog.pg_class c"
 		"\n     LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace"
-					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c')\n",
+					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c')\n",
 						  RELKIND_RELATION, RELKIND_SEQUENCE, RELKIND_VIEW,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 		processSQLNamePattern(GetConnection(fout), query, cell->val, true,
 							  false, "n.nspname", "c.relname", NULL,
 							  "pg_catalog.pg_table_is_visible(c.oid)");
@@ -2125,6 +2126,9 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo, bool oids)
 	/* Skip FOREIGN TABLEs (no data to dump) */
 	if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 		return;
+	/* Skip partitioned tables (data in partitions) */
+	if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+		return;
 
 	/* Don't dump data in unlogged tables, if so requested */
 	if (tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED &&
@@ -5204,6 +5208,7 @@ getTables(Archive *fout, int *numTables)
 	int			i_reloftype;
 	int			i_relpages;
 	int			i_changed_acl;
+	int			i_partkeydef;
 
 	/* Make sure we are in proper schema */
 	selectSourceSchema(fout, "pg_catalog");
@@ -5289,7 +5294,8 @@ getTables(Archive *fout, int *numTables)
 						  "OR %s IS NOT NULL "
 						  "OR %s IS NOT NULL"
 						  "))"
-						  "AS changed_acl "
+						  "AS changed_acl, "
+						  "CASE WHEN c.relkind = 'P' THEN pg_catalog.pg_get_partkeydef(c.oid) ELSE NULL END AS partkeydef "
 						  "FROM pg_class c "
 						  "LEFT JOIN pg_depend d ON "
 						  "(c.relkind = '%c' AND "
@@ -5301,7 +5307,7 @@ getTables(Archive *fout, int *numTables)
 						  "(c.oid = pip.objoid "
 						  "AND pip.classoid = 'pg_class'::regclass "
 						  "AND pip.objsubid = 0) "
-				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c') "
+				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c', '%c') "
 						  "ORDER BY c.oid",
 						  acl_subquery->data,
 						  racl_subquery->data,
@@ -5315,7 +5321,8 @@ getTables(Archive *fout, int *numTables)
 						  RELKIND_SEQUENCE,
 						  RELKIND_RELATION, RELKIND_SEQUENCE,
 						  RELKIND_VIEW, RELKIND_COMPOSITE_TYPE,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 
 		destroyPQExpBuffer(acl_subquery);
 		destroyPQExpBuffer(racl_subquery);
@@ -5877,6 +5884,7 @@ getTables(Archive *fout, int *numTables)
 	i_toastreloptions = PQfnumber(res, "toast_reloptions");
 	i_reloftype = PQfnumber(res, "reloftype");
 	i_changed_acl = PQfnumber(res, "changed_acl");
+	i_partkeydef = PQfnumber(res, "partkeydef");
 
 	if (dopt->lockWaitTimeout && fout->remoteVersion >= 70300)
 	{
@@ -5947,6 +5955,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 */
 
@@ -5991,7 +6000,9 @@ getTables(Archive *fout, int *numTables)
 		 * We only need to lock the table for certain components; see
 		 * pg_dump.h
 		 */
-		if (tblinfo[i].dobj.dump && tblinfo[i].relkind == RELKIND_RELATION &&
+		if (tblinfo[i].dobj.dump &&
+			(tblinfo[i].relkind == RELKIND_RELATION ||
+			 tblinfo->relkind == RELKIND_PARTITIONED_TABLE) &&
 			(tblinfo[i].dobj.dump & DUMP_COMPONENTS_REQUIRING_LOCK))
 		{
 			resetPQExpBuffer(query);
@@ -6093,7 +6104,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);
 
@@ -15446,6 +15460,9 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 				appendPQExpBufferChar(q, ')');
 			}
 
+			if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+				appendPQExpBuffer(q, "\nPARTITION BY %s", tbinfo->partkeydef);
+
 			if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 				appendPQExpBuffer(q, "\nSERVER %s", fmtId(srvname));
 		}
@@ -15506,6 +15523,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 		 */
 		if (dopt->binary_upgrade &&
 			(tbinfo->relkind == RELKIND_RELATION ||
+			 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 			 tbinfo->relkind == RELKIND_FOREIGN_TABLE))
 		{
 			for (j = 0; j < tbinfo->numatts; j++)
@@ -15524,7 +15542,8 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 					appendStringLiteralAH(q, fmtId(tbinfo->dobj.name), fout);
 					appendPQExpBufferStr(q, "::pg_catalog.regclass;\n");
 
-					if (tbinfo->relkind == RELKIND_RELATION)
+					if (tbinfo->relkind == RELKIND_RELATION ||
+						tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
 						appendPQExpBuffer(q, "ALTER TABLE ONLY %s ",
 										  fmtId(tbinfo->dobj.name));
 					else
@@ -15741,6 +15760,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 	 * dump properties we only have ALTER TABLE syntax for
 	 */
 	if ((tbinfo->relkind == RELKIND_RELATION ||
+		 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 		 tbinfo->relkind == RELKIND_MATVIEW) &&
 		tbinfo->relreplident != REPLICA_IDENTITY_DEFAULT)
 	{
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 2bfa2d9..0292859 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -310,6 +310,7 @@ typedef struct _tableInfo
 	bool	   *inhNotNull;		/* true if NOT NULL is inherited */
 	struct _attrDefInfo **attrdefs;		/* DEFAULT expressions */
 	struct _constraintInfo *checkexprs; /* CHECK constraints */
+	char	   *partkeydef;		/* partition key definition */
 
 	/*
 	 * Stuff computed only for dumpable tables.
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 6275a68..10d924a 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -861,6 +861,7 @@ permissionsList(const char *pattern)
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'S' THEN '%s'"
@@ -870,6 +871,7 @@ permissionsList(const char *pattern)
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("sequence"),
@@ -920,7 +922,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
@@ -1567,8 +1569,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)");
 	}
 
@@ -1633,6 +1635,14 @@ describeOneTableDetails(const char *schemaname,
 			printfPQExpBuffer(&title, _("Foreign table \"%s.%s\""),
 							  schemaname, relationname);
 			break;
+		case 'P':
+			if (tableinfo.relpersistence == 'u')
+				printfPQExpBuffer(&title, _("Unlogged table \"%s.%s\""),
+								  schemaname, relationname);
+			else
+				printfPQExpBuffer(&title, _("Table \"%s.%s\""),
+								  schemaname, relationname);
+			break;
 		default:
 			/* untranslated unknown relkind */
 			printfPQExpBuffer(&title, "?%c? \"%s.%s\"",
@@ -1646,8 +1656,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");
@@ -1667,12 +1677,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");
 	}
 
@@ -1772,7 +1782,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);
@@ -1780,14 +1790,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 (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 */
@@ -1926,7 +1955,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;
@@ -2485,7 +2514,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;
@@ -2696,7 +2725,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
@@ -3024,6 +3053,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'i' THEN '%s'"
@@ -3035,6 +3065,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("index"),
@@ -3079,7 +3110,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 019f75a..5e5c370 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/pg_proc.h b/src/include/catalog/pg_proc.h
index e2d08ba..b45688b 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -1980,6 +1980,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/utils/builtins.h b/src/include/utils/builtins.h
index 2ae212a..e800647 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -729,6 +729,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/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 708232d..2fec847 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -408,3 +408,29 @@ CREATE TABLE no_inh_con_parted (
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
 ERROR:  cannot add NO INHERIT constraint to partitioned table "no_inh_con_parted"
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+Table "public.describe_range_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition Key: RANGE ((a + b))
+
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+Table "public.describe_list_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition Key: LIST (a)
+
+DROP TABLE describe_range_key, describe_list_key;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 6e4a8be..4dd6a0a 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -402,3 +402,16 @@ CREATE TABLE no_inh_con_parted (
 	a int,
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
+
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+DROP TABLE describe_range_key, describe_list_key;
-- 
1.7.1


--------------4012470FC48539D6F6714CEA
Content-Type: text/x-diff;
 name="0003-Catalog-and-DDL-for-partitions-4.patch"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
 filename="0003-Catalog-and-DDL-for-partitions-4.patch"



^ permalink  raw  reply  [nested|flat] 110+ messages in thread

* [PATCH 2/9] psql and pg_dump support for partitioned tables.
@ 2016-07-12 08:20  amit <[email protected]>
  0 siblings, 0 replies; 110+ messages in thread

From: amit @ 2016-07-12 08:20 UTC (permalink / raw)

Takes care of both the partition key deparse stuff and the new relkind.
---
 src/backend/utils/adt/ruleutils.c          |  140 ++++++++++++++++++++++++++++
 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/pg_proc.h              |    2 +
 src/include/utils/builtins.h               |    1 +
 src/test/regress/expected/create_table.out |   26 +++++
 src/test/regress/sql/create_table.sql      |   13 +++
 9 files changed, 260 insertions(+), 26 deletions(-)

diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 8a81d7a..03be202 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_table.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
@@ -315,6 +316,7 @@ static char *pg_get_indexdef_worker(Oid indexrelid, int colno,
 					   const Oid *excludeOps,
 					   bool attrsOnly, bool showTblSpc,
 					   int prettyFlags, bool missing_ok);
+static char *pg_get_partkeydef_worker(Oid relid, int prettyFlags);
 static char *pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 							int prettyFlags, bool missing_ok);
 static text *pg_get_expr_worker(text *expr, Oid relid, const char *relname,
@@ -1389,6 +1391,144 @@ 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 [ opclass_name ] [, ...])
+ */
+Datum
+pg_get_partkeydef(PG_FUNCTION_ARGS)
+{
+	Oid			relid = PG_GETARG_OID(0);
+	int			prettyFlags;
+
+	prettyFlags = PRETTYFLAG_INDENT;
+	PG_RETURN_TEXT_P(string_to_text(pg_get_partkeydef_worker(relid,
+									prettyFlags)));
+}
+
+/*
+ * Internal workhorse to decompile a partition key definition.
+ */
+static char *
+pg_get_partkeydef_worker(Oid relid, int prettyFlags)
+{
+	Form_pg_partitioned_table	form;
+	HeapTuple	tuple;
+	oidvector  *partclass;
+	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_table) GETSTRUCT(tuple);
+
+	Assert(form->partrelid == relid);
+
+	/* Must get partclass, and partexprs the hard way */
+	datum = SysCacheGetAttr(PARTEDRELID, tuple,
+							Anum_pg_partitioned_table_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_table_partexprbin))
+	{
+		Datum		exprsDatum;
+		bool		isnull;
+		char	   *exprsString;
+
+		exprsDatum = SysCacheGetAttr(PARTEDRELID, tuple,
+									 Anum_pg_partitioned_table_partexprbin, &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);
+
+	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,
+											0, 0);
+
+			appendStringInfoString(&buf, 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/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 51b8a1a..854dddc 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -1253,9 +1253,10 @@ expand_table_name_patterns(Archive *fout,
 						  "SELECT c.oid"
 						  "\nFROM pg_catalog.pg_class c"
 		"\n     LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace"
-					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c')\n",
+					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c')\n",
 						  RELKIND_RELATION, RELKIND_SEQUENCE, RELKIND_VIEW,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 		processSQLNamePattern(GetConnection(fout), query, cell->val, true,
 							  false, "n.nspname", "c.relname", NULL,
 							  "pg_catalog.pg_table_is_visible(c.oid)");
@@ -2125,6 +2126,9 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo, bool oids)
 	/* Skip FOREIGN TABLEs (no data to dump) */
 	if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 		return;
+	/* Skip partitioned tables (data in partitions) */
+	if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+		return;
 
 	/* Don't dump data in unlogged tables, if so requested */
 	if (tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED &&
@@ -5211,6 +5215,7 @@ getTables(Archive *fout, int *numTables)
 	int			i_reloftype;
 	int			i_relpages;
 	int			i_changed_acl;
+	int			i_partkeydef;
 
 	/* Make sure we are in proper schema */
 	selectSourceSchema(fout, "pg_catalog");
@@ -5296,7 +5301,8 @@ getTables(Archive *fout, int *numTables)
 						  "OR %s IS NOT NULL "
 						  "OR %s IS NOT NULL"
 						  "))"
-						  "AS changed_acl "
+						  "AS changed_acl, "
+						  "CASE WHEN c.relkind = 'P' THEN pg_catalog.pg_get_partkeydef(c.oid) ELSE NULL END AS partkeydef "
 						  "FROM pg_class c "
 						  "LEFT JOIN pg_depend d ON "
 						  "(c.relkind = '%c' AND "
@@ -5308,7 +5314,7 @@ getTables(Archive *fout, int *numTables)
 						  "(c.oid = pip.objoid "
 						  "AND pip.classoid = 'pg_class'::regclass "
 						  "AND pip.objsubid = 0) "
-				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c') "
+				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c', '%c') "
 						  "ORDER BY c.oid",
 						  acl_subquery->data,
 						  racl_subquery->data,
@@ -5322,7 +5328,8 @@ getTables(Archive *fout, int *numTables)
 						  RELKIND_SEQUENCE,
 						  RELKIND_RELATION, RELKIND_SEQUENCE,
 						  RELKIND_VIEW, RELKIND_COMPOSITE_TYPE,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 
 		destroyPQExpBuffer(acl_subquery);
 		destroyPQExpBuffer(racl_subquery);
@@ -5884,6 +5891,7 @@ getTables(Archive *fout, int *numTables)
 	i_toastreloptions = PQfnumber(res, "toast_reloptions");
 	i_reloftype = PQfnumber(res, "reloftype");
 	i_changed_acl = PQfnumber(res, "changed_acl");
+	i_partkeydef = PQfnumber(res, "partkeydef");
 
 	if (dopt->lockWaitTimeout && fout->remoteVersion >= 70300)
 	{
@@ -5954,6 +5962,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 */
 
@@ -5998,7 +6007,9 @@ getTables(Archive *fout, int *numTables)
 		 * We only need to lock the table for certain components; see
 		 * pg_dump.h
 		 */
-		if (tblinfo[i].dobj.dump && tblinfo[i].relkind == RELKIND_RELATION &&
+		if (tblinfo[i].dobj.dump &&
+			(tblinfo[i].relkind == RELKIND_RELATION ||
+			 tblinfo->relkind == RELKIND_PARTITIONED_TABLE) &&
 			(tblinfo[i].dobj.dump & DUMP_COMPONENTS_REQUIRING_LOCK))
 		{
 			resetPQExpBuffer(query);
@@ -6100,7 +6111,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);
 
@@ -15453,6 +15467,9 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 				appendPQExpBufferChar(q, ')');
 			}
 
+			if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+				appendPQExpBuffer(q, "\nPARTITION BY %s", tbinfo->partkeydef);
+
 			if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 				appendPQExpBuffer(q, "\nSERVER %s", fmtId(srvname));
 		}
@@ -15513,6 +15530,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 		 */
 		if (dopt->binary_upgrade &&
 			(tbinfo->relkind == RELKIND_RELATION ||
+			 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 			 tbinfo->relkind == RELKIND_FOREIGN_TABLE))
 		{
 			for (j = 0; j < tbinfo->numatts; j++)
@@ -15531,7 +15549,8 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 					appendStringLiteralAH(q, fmtId(tbinfo->dobj.name), fout);
 					appendPQExpBufferStr(q, "::pg_catalog.regclass;\n");
 
-					if (tbinfo->relkind == RELKIND_RELATION)
+					if (tbinfo->relkind == RELKIND_RELATION ||
+						tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
 						appendPQExpBuffer(q, "ALTER TABLE ONLY %s ",
 										  fmtId(tbinfo->dobj.name));
 					else
@@ -15748,6 +15767,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 	 * dump properties we only have ALTER TABLE syntax for
 	 */
 	if ((tbinfo->relkind == RELKIND_RELATION ||
+		 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 		 tbinfo->relkind == RELKIND_MATVIEW) &&
 		tbinfo->relreplident != REPLICA_IDENTITY_DEFAULT)
 	{
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 2bfa2d9..0292859 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -310,6 +310,7 @@ typedef struct _tableInfo
 	bool	   *inhNotNull;		/* true if NOT NULL is inherited */
 	struct _attrDefInfo **attrdefs;		/* DEFAULT expressions */
 	struct _constraintInfo *checkexprs; /* CHECK constraints */
+	char	   *partkeydef;		/* partition key definition */
 
 	/*
 	 * Stuff computed only for dumpable tables.
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 6275a68..db6dc5c 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -861,6 +861,7 @@ permissionsList(const char *pattern)
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'S' THEN '%s'"
@@ -870,6 +871,7 @@ permissionsList(const char *pattern)
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("sequence"),
@@ -920,7 +922,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
@@ -1567,8 +1569,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)");
 	}
 
@@ -1633,6 +1635,14 @@ describeOneTableDetails(const char *schemaname,
 			printfPQExpBuffer(&title, _("Foreign table \"%s.%s\""),
 							  schemaname, relationname);
 			break;
+		case 'P':
+			if (tableinfo.relpersistence == 'u')
+				printfPQExpBuffer(&title, _("Unlogged table \"%s.%s\""),
+								  schemaname, relationname);
+			else
+				printfPQExpBuffer(&title, _("Table \"%s.%s\""),
+								  schemaname, relationname);
+			break;
 		default:
 			/* untranslated unknown relkind */
 			printfPQExpBuffer(&title, "?%c? \"%s.%s\"",
@@ -1646,8 +1656,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");
@@ -1667,12 +1677,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");
 	}
 
@@ -1772,7 +1782,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);
@@ -1780,14 +1790,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 (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 */
@@ -1926,7 +1955,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;
@@ -2485,7 +2514,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;
@@ -2696,7 +2725,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
@@ -3024,6 +3053,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'i' THEN '%s'"
@@ -3035,6 +3065,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("index"),
@@ -3079,7 +3110,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 50a45eb..8284a9c 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/pg_proc.h b/src/include/catalog/pg_proc.h
index e2d08ba..b45688b 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -1980,6 +1980,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/utils/builtins.h b/src/include/utils/builtins.h
index 2ae212a..e800647 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -729,6 +729,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/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 1394362..260ec34 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -407,3 +407,29 @@ CREATE TABLE no_inh_con_parted (
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
 ERROR:  cannot add NO INHERIT constraint to partitioned table "no_inh_con_parted"
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+Table "public.describe_range_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition key: RANGE ((a + b))
+
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+Table "public.describe_list_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition key: LIST (a)
+
+DROP TABLE describe_range_key, describe_list_key;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index d383f58..7a1f8de 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -401,3 +401,16 @@ CREATE TABLE no_inh_con_parted (
 	a int,
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
+
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+DROP TABLE describe_range_key, describe_list_key;
-- 
1.7.1


--------------C3235EE96C69D8DAE4DB543E
Content-Type: text/x-diff;
 name="0003-Catalog-and-DDL-for-partitions-6.patch"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
 filename="0003-Catalog-and-DDL-for-partitions-6.patch"



^ permalink  raw  reply  [nested|flat] 110+ messages in thread

* [PATCH 2/8] psql and pg_dump support for partitioned tables.
@ 2016-07-12 08:20  amit <[email protected]>
  0 siblings, 0 replies; 110+ messages in thread

From: amit @ 2016-07-12 08:20 UTC (permalink / raw)

Takes care of both the partition key deparse stuff and the new relkind.
---
 src/backend/utils/adt/ruleutils.c          |  147 ++++++++++++++++++++++++++++
 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/pg_proc.h              |    2 +
 src/include/utils/builtins.h               |    1 +
 src/test/regress/expected/create_table.out |   26 +++++
 src/test/regress/sql/create_table.sql      |   13 +++
 9 files changed, 267 insertions(+), 26 deletions(-)

diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index ec966c7..4691b5b 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, bool missing_ok);
+static char *pg_get_partkeydef_worker(Oid relId, int prettyFlags);
 static char *pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 							int prettyFlags, bool missing_ok);
 static text *pg_get_expr_worker(text *expr, Oid relid, const char *relname,
@@ -1389,6 +1391,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 [ 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/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 4ee10fc..a127672 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -1253,9 +1253,10 @@ expand_table_name_patterns(Archive *fout,
 						  "SELECT c.oid"
 						  "\nFROM pg_catalog.pg_class c"
 		"\n     LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace"
-					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c')\n",
+					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c')\n",
 						  RELKIND_RELATION, RELKIND_SEQUENCE, RELKIND_VIEW,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_REL);
 		processSQLNamePattern(GetConnection(fout), query, cell->val, true,
 							  false, "n.nspname", "c.relname", NULL,
 							  "pg_catalog.pg_table_is_visible(c.oid)");
@@ -2117,6 +2118,9 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo, bool oids)
 	/* Skip FOREIGN TABLEs (no data to dump) */
 	if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 		return;
+	/* Skip partitioned tables (data in partitions) */
+	if (tbinfo->relkind == RELKIND_PARTITIONED_REL)
+		return;
 
 	/* Don't dump data in unlogged tables, if so requested */
 	if (tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED &&
@@ -5195,6 +5199,7 @@ getTables(Archive *fout, int *numTables)
 	int			i_reloftype;
 	int			i_relpages;
 	int			i_changed_acl;
+	int			i_partkeydef;
 
 	/* Make sure we are in proper schema */
 	selectSourceSchema(fout, "pg_catalog");
@@ -5280,7 +5285,8 @@ getTables(Archive *fout, int *numTables)
 						  "OR %s IS NOT NULL "
 						  "OR %s IS NOT NULL"
 						  "))"
-						  "AS changed_acl "
+						  "AS changed_acl, "
+						  "CASE WHEN c.relkind = 'P' THEN pg_catalog.pg_get_partkeydef(c.oid) ELSE NULL END AS partkeydef "
 						  "FROM pg_class c "
 						  "LEFT JOIN pg_depend d ON "
 						  "(c.relkind = '%c' AND "
@@ -5292,7 +5298,7 @@ getTables(Archive *fout, int *numTables)
 						  "(c.oid = pip.objoid "
 						  "AND pip.classoid = 'pg_class'::regclass "
 						  "AND pip.objsubid = 0) "
-				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c') "
+				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c', '%c') "
 						  "ORDER BY c.oid",
 						  acl_subquery->data,
 						  racl_subquery->data,
@@ -5306,7 +5312,8 @@ getTables(Archive *fout, int *numTables)
 						  RELKIND_SEQUENCE,
 						  RELKIND_RELATION, RELKIND_SEQUENCE,
 						  RELKIND_VIEW, RELKIND_COMPOSITE_TYPE,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_REL);
 
 		destroyPQExpBuffer(acl_subquery);
 		destroyPQExpBuffer(racl_subquery);
@@ -5868,6 +5875,7 @@ getTables(Archive *fout, int *numTables)
 	i_toastreloptions = PQfnumber(res, "toast_reloptions");
 	i_reloftype = PQfnumber(res, "reloftype");
 	i_changed_acl = PQfnumber(res, "changed_acl");
+	i_partkeydef = PQfnumber(res, "partkeydef");
 
 	if (dopt->lockWaitTimeout && fout->remoteVersion >= 70300)
 	{
@@ -5938,6 +5946,7 @@ getTables(Archive *fout, int *numTables)
 		else
 			tblinfo[i].checkoption = pg_strdup(PQgetvalue(res, i, i_checkoption));
 		tblinfo[i].toast_reloptions = pg_strdup(PQgetvalue(res, i, i_toastreloptions));
+		tblinfo[i].partkeydef = pg_strdup(PQgetvalue(res, i, i_partkeydef));
 
 		/* other fields were zeroed above */
 
@@ -5982,7 +5991,9 @@ getTables(Archive *fout, int *numTables)
 		 * We only need to lock the table for certain components; see
 		 * pg_dump.h
 		 */
-		if (tblinfo[i].dobj.dump && tblinfo[i].relkind == RELKIND_RELATION &&
+		if (tblinfo[i].dobj.dump &&
+			(tblinfo[i].relkind == RELKIND_RELATION ||
+			 tblinfo->relkind == RELKIND_PARTITIONED_REL) &&
 			(tblinfo[i].dobj.dump & DUMP_COMPONENTS_REQUIRING_LOCK))
 		{
 			resetPQExpBuffer(query);
@@ -6083,7 +6094,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);
 
@@ -15436,6 +15450,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));
 		}
@@ -15496,6 +15513,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++)
@@ -15514,7 +15532,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
@@ -15731,6 +15750,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 	 * dump properties we only have ALTER TABLE syntax for
 	 */
 	if ((tbinfo->relkind == RELKIND_RELATION ||
+		 tbinfo->relkind == RELKIND_PARTITIONED_REL ||
 		 tbinfo->relkind == RELKIND_MATVIEW) &&
 		tbinfo->relreplident != REPLICA_IDENTITY_DEFAULT)
 	{
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 2bfa2d9..0292859 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -310,6 +310,7 @@ typedef struct _tableInfo
 	bool	   *inhNotNull;		/* true if NOT NULL is inherited */
 	struct _attrDefInfo **attrdefs;		/* DEFAULT expressions */
 	struct _constraintInfo *checkexprs; /* CHECK constraints */
+	char	   *partkeydef;		/* partition key definition */
 
 	/*
 	 * Stuff computed only for dumpable tables.
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 27be102..3ba8a90 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -856,6 +856,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"),
@@ -865,6 +866,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");
@@ -911,7 +913,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
@@ -1555,8 +1557,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)");
 	}
 
@@ -1621,6 +1623,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\"",
@@ -1634,8 +1644,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");
@@ -1655,12 +1665,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");
 	}
 
@@ -1760,7 +1770,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);
@@ -1768,14 +1778,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 */
@@ -1914,7 +1943,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;
@@ -2473,7 +2502,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;
@@ -2684,7 +2713,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
@@ -3018,6 +3047,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"),
@@ -3029,6 +3059,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"));
 
@@ -3067,7 +3098,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 
 	appendPQExpBufferStr(&buf, "\nWHERE c.relkind IN (");
 	if (showTables)
-		appendPQExpBufferStr(&buf, "'r',");
+		appendPQExpBufferStr(&buf, "'r', 'P',");
 	if (showViews)
 		appendPQExpBufferStr(&buf, "'v',");
 	if (showMatViews)
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 8469d9f..cd4eb7c 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -427,7 +427,7 @@ static const SchemaQuery Query_for_list_of_tables = {
 	/* catname */
 	"pg_catalog.pg_class c",
 	/* selcondition */
-	"c.relkind IN ('r')",
+	"c.relkind IN ('r', 'P')",
 	/* viscondition */
 	"pg_catalog.pg_table_is_visible(c.oid)",
 	/* namespace */
@@ -458,7 +458,7 @@ static const SchemaQuery Query_for_list_of_updatables = {
 	/* catname */
 	"pg_catalog.pg_class c",
 	/* selcondition */
-	"c.relkind IN ('r', 'f', 'v')",
+	"c.relkind IN ('r', 'f', 'v', 'P')",
 	/* viscondition */
 	"pg_catalog.pg_table_is_visible(c.oid)",
 	/* namespace */
@@ -488,7 +488,7 @@ static const SchemaQuery Query_for_list_of_tsvmf = {
 	/* catname */
 	"pg_catalog.pg_class c",
 	/* selcondition */
-	"c.relkind IN ('r', 'S', 'v', 'm', 'f')",
+	"c.relkind IN ('r', 'S', 'v', 'm', 'f', 'P')",
 	/* viscondition */
 	"pg_catalog.pg_table_is_visible(c.oid)",
 	/* namespace */
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index 270dd21..5a444d9 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/utils/builtins.h b/src/include/utils/builtins.h
index 8cebc86..351bfd3 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -722,6 +722,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/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index fb7a1b3..4fc8c06 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -413,3 +413,29 @@ SELECT connoinherit FROM pg_constraint WHERE conrelid = 'no_inh_con_parted'::reg
 (1 row)
 
 DROP TABLE no_inh_con_parted;
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+Partitioned table "public.describe_range_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition Key: PARTITION BY RANGE (((a + b)))
+
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+Partitioned table "public.describe_list_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition Key: PARTITION BY LIST (a)
+
+DROP TABLE describe_range_key, describe_list_key;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index cb17517..7c7a18a 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -404,3 +404,16 @@ CREATE TABLE no_inh_con_parted (
 ) PARTITION BY RANGE (a);
 SELECT connoinherit FROM pg_constraint WHERE conrelid = 'no_inh_con_parted'::regclass;
 DROP TABLE no_inh_con_parted;
+
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+DROP TABLE describe_range_key, describe_list_key;
-- 
1.7.1


--------------8C4C48E640631A327D3D2826
Content-Type: text/x-diff;
 name="0003-Catalog-and-DDL-for-partitions.patch"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
 filename="0003-Catalog-and-DDL-for-partitions.patch"



^ permalink  raw  reply  [nested|flat] 110+ messages in thread

* [PATCH 2/9] psql and pg_dump support for partitioned tables.
@ 2016-07-12 08:20  amit <[email protected]>
  0 siblings, 0 replies; 110+ messages in thread

From: amit @ 2016-07-12 08:20 UTC (permalink / raw)

Takes care of both the partition key deparse stuff and the new relkind.
---
 src/backend/utils/adt/ruleutils.c          |  140 ++++++++++++++++++++++++++++
 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/pg_proc.h              |    2 +
 src/include/utils/builtins.h               |    1 +
 src/test/regress/expected/create_table.out |   26 +++++
 src/test/regress/sql/create_table.sql      |   13 +++
 9 files changed, 260 insertions(+), 26 deletions(-)

diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 8a81d7a..77ce807 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_table.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
@@ -315,6 +316,7 @@ static char *pg_get_indexdef_worker(Oid indexrelid, int colno,
 					   const Oid *excludeOps,
 					   bool attrsOnly, bool showTblSpc,
 					   int prettyFlags, bool missing_ok);
+static char *pg_get_partkeydef_worker(Oid relId, int prettyFlags);
 static char *pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 							int prettyFlags, bool missing_ok);
 static text *pg_get_expr_worker(text *expr, Oid relid, const char *relname,
@@ -1389,6 +1391,144 @@ 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 [ 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_table	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_table) GETSTRUCT(tuple);
+
+	Assert(form->partedrelid == relId);
+
+	/* Must get partclass, and partexprs the hard way */
+	datum = SysCacheGetAttr(PARTEDRELID, tuple,
+							Anum_pg_partitioned_table_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_table_partexprbin))
+	{
+		Datum		exprsDatum;
+		bool		isnull;
+		char	   *exprsString;
+
+		exprsDatum = SysCacheGetAttr(PARTEDRELID, tuple,
+									 Anum_pg_partitioned_table_partexprbin, &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);
+
+	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,
+											0, 0);
+
+			appendStringInfoString(&buf, 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/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index a5c2d09..c418ba7 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -1253,9 +1253,10 @@ expand_table_name_patterns(Archive *fout,
 						  "SELECT c.oid"
 						  "\nFROM pg_catalog.pg_class c"
 		"\n     LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace"
-					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c')\n",
+					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c')\n",
 						  RELKIND_RELATION, RELKIND_SEQUENCE, RELKIND_VIEW,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 		processSQLNamePattern(GetConnection(fout), query, cell->val, true,
 							  false, "n.nspname", "c.relname", NULL,
 							  "pg_catalog.pg_table_is_visible(c.oid)");
@@ -2117,6 +2118,9 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo, bool oids)
 	/* Skip FOREIGN TABLEs (no data to dump) */
 	if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 		return;
+	/* Skip partitioned tables (data in partitions) */
+	if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+		return;
 
 	/* Don't dump data in unlogged tables, if so requested */
 	if (tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED &&
@@ -5195,6 +5199,7 @@ getTables(Archive *fout, int *numTables)
 	int			i_reloftype;
 	int			i_relpages;
 	int			i_changed_acl;
+	int			i_partkeydef;
 
 	/* Make sure we are in proper schema */
 	selectSourceSchema(fout, "pg_catalog");
@@ -5280,7 +5285,8 @@ getTables(Archive *fout, int *numTables)
 						  "OR %s IS NOT NULL "
 						  "OR %s IS NOT NULL"
 						  "))"
-						  "AS changed_acl "
+						  "AS changed_acl, "
+						  "CASE WHEN c.relkind = 'P' THEN pg_catalog.pg_get_partkeydef(c.oid) ELSE NULL END AS partkeydef "
 						  "FROM pg_class c "
 						  "LEFT JOIN pg_depend d ON "
 						  "(c.relkind = '%c' AND "
@@ -5292,7 +5298,7 @@ getTables(Archive *fout, int *numTables)
 						  "(c.oid = pip.objoid "
 						  "AND pip.classoid = 'pg_class'::regclass "
 						  "AND pip.objsubid = 0) "
-				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c') "
+				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c', '%c') "
 						  "ORDER BY c.oid",
 						  acl_subquery->data,
 						  racl_subquery->data,
@@ -5306,7 +5312,8 @@ getTables(Archive *fout, int *numTables)
 						  RELKIND_SEQUENCE,
 						  RELKIND_RELATION, RELKIND_SEQUENCE,
 						  RELKIND_VIEW, RELKIND_COMPOSITE_TYPE,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 
 		destroyPQExpBuffer(acl_subquery);
 		destroyPQExpBuffer(racl_subquery);
@@ -5868,6 +5875,7 @@ getTables(Archive *fout, int *numTables)
 	i_toastreloptions = PQfnumber(res, "toast_reloptions");
 	i_reloftype = PQfnumber(res, "reloftype");
 	i_changed_acl = PQfnumber(res, "changed_acl");
+	i_partkeydef = PQfnumber(res, "partkeydef");
 
 	if (dopt->lockWaitTimeout && fout->remoteVersion >= 70300)
 	{
@@ -5938,6 +5946,7 @@ getTables(Archive *fout, int *numTables)
 		else
 			tblinfo[i].checkoption = pg_strdup(PQgetvalue(res, i, i_checkoption));
 		tblinfo[i].toast_reloptions = pg_strdup(PQgetvalue(res, i, i_toastreloptions));
+		tblinfo[i].partkeydef = pg_strdup(PQgetvalue(res, i, i_partkeydef));
 
 		/* other fields were zeroed above */
 
@@ -5982,7 +5991,9 @@ getTables(Archive *fout, int *numTables)
 		 * We only need to lock the table for certain components; see
 		 * pg_dump.h
 		 */
-		if (tblinfo[i].dobj.dump && tblinfo[i].relkind == RELKIND_RELATION &&
+		if (tblinfo[i].dobj.dump &&
+			(tblinfo[i].relkind == RELKIND_RELATION ||
+			 tblinfo->relkind == RELKIND_PARTITIONED_TABLE) &&
 			(tblinfo[i].dobj.dump & DUMP_COMPONENTS_REQUIRING_LOCK))
 		{
 			resetPQExpBuffer(query);
@@ -6084,7 +6095,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);
 
@@ -15437,6 +15451,9 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 				appendPQExpBufferChar(q, ')');
 			}
 
+			if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+				appendPQExpBuffer(q, "\nPARTITION BY %s", tbinfo->partkeydef);
+
 			if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 				appendPQExpBuffer(q, "\nSERVER %s", fmtId(srvname));
 		}
@@ -15497,6 +15514,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 		 */
 		if (dopt->binary_upgrade &&
 			(tbinfo->relkind == RELKIND_RELATION ||
+			 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 			 tbinfo->relkind == RELKIND_FOREIGN_TABLE))
 		{
 			for (j = 0; j < tbinfo->numatts; j++)
@@ -15515,7 +15533,8 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 					appendStringLiteralAH(q, fmtId(tbinfo->dobj.name), fout);
 					appendPQExpBufferStr(q, "::pg_catalog.regclass;\n");
 
-					if (tbinfo->relkind == RELKIND_RELATION)
+					if (tbinfo->relkind == RELKIND_RELATION ||
+						tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
 						appendPQExpBuffer(q, "ALTER TABLE ONLY %s ",
 										  fmtId(tbinfo->dobj.name));
 					else
@@ -15732,6 +15751,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 	 * dump properties we only have ALTER TABLE syntax for
 	 */
 	if ((tbinfo->relkind == RELKIND_RELATION ||
+		 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 		 tbinfo->relkind == RELKIND_MATVIEW) &&
 		tbinfo->relreplident != REPLICA_IDENTITY_DEFAULT)
 	{
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 2bfa2d9..0292859 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -310,6 +310,7 @@ typedef struct _tableInfo
 	bool	   *inhNotNull;		/* true if NOT NULL is inherited */
 	struct _attrDefInfo **attrdefs;		/* DEFAULT expressions */
 	struct _constraintInfo *checkexprs; /* CHECK constraints */
+	char	   *partkeydef;		/* partition key definition */
 
 	/*
 	 * Stuff computed only for dumpable tables.
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 6275a68..10d924a 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -861,6 +861,7 @@ permissionsList(const char *pattern)
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'S' THEN '%s'"
@@ -870,6 +871,7 @@ permissionsList(const char *pattern)
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("sequence"),
@@ -920,7 +922,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
@@ -1567,8 +1569,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)");
 	}
 
@@ -1633,6 +1635,14 @@ describeOneTableDetails(const char *schemaname,
 			printfPQExpBuffer(&title, _("Foreign table \"%s.%s\""),
 							  schemaname, relationname);
 			break;
+		case 'P':
+			if (tableinfo.relpersistence == 'u')
+				printfPQExpBuffer(&title, _("Unlogged table \"%s.%s\""),
+								  schemaname, relationname);
+			else
+				printfPQExpBuffer(&title, _("Table \"%s.%s\""),
+								  schemaname, relationname);
+			break;
 		default:
 			/* untranslated unknown relkind */
 			printfPQExpBuffer(&title, "?%c? \"%s.%s\"",
@@ -1646,8 +1656,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");
@@ -1667,12 +1677,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");
 	}
 
@@ -1772,7 +1782,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);
@@ -1780,14 +1790,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 (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 */
@@ -1926,7 +1955,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;
@@ -2485,7 +2514,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;
@@ -2696,7 +2725,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
@@ -3024,6 +3053,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'i' THEN '%s'"
@@ -3035,6 +3065,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("index"),
@@ -3079,7 +3110,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 019f75a..5e5c370 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/pg_proc.h b/src/include/catalog/pg_proc.h
index e2d08ba..b45688b 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -1980,6 +1980,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/utils/builtins.h b/src/include/utils/builtins.h
index 2ae212a..e800647 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -729,6 +729,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/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 708232d..2fec847 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -408,3 +408,29 @@ CREATE TABLE no_inh_con_parted (
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
 ERROR:  cannot add NO INHERIT constraint to partitioned table "no_inh_con_parted"
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+Table "public.describe_range_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition Key: RANGE ((a + b))
+
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+Table "public.describe_list_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition Key: LIST (a)
+
+DROP TABLE describe_range_key, describe_list_key;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 6e4a8be..4dd6a0a 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -402,3 +402,16 @@ CREATE TABLE no_inh_con_parted (
 	a int,
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
+
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+DROP TABLE describe_range_key, describe_list_key;
-- 
1.7.1


--------------6EB5B318125CB7DB1A4FD229
Content-Type: text/x-diff;
 name="0003-Catalog-and-DDL-for-partitions-3.patch"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
 filename="0003-Catalog-and-DDL-for-partitions-3.patch"



^ permalink  raw  reply  [nested|flat] 110+ messages in thread

* [PATCH 2/9] psql and pg_dump support for partitioned tables.
@ 2016-07-12 08:20  amit <[email protected]>
  0 siblings, 0 replies; 110+ messages in thread

From: amit @ 2016-07-12 08:20 UTC (permalink / raw)

Takes care of both the partition key deparse stuff and the new relkind.
---
 src/backend/utils/adt/ruleutils.c          |  140 ++++++++++++++++++++++++++++
 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/pg_proc.h              |    2 +
 src/include/utils/builtins.h               |    1 +
 src/test/regress/expected/create_table.out |   26 +++++
 src/test/regress/sql/create_table.sql      |   13 +++
 9 files changed, 260 insertions(+), 26 deletions(-)

diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 8a81d7a..03be202 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_table.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
@@ -315,6 +316,7 @@ static char *pg_get_indexdef_worker(Oid indexrelid, int colno,
 					   const Oid *excludeOps,
 					   bool attrsOnly, bool showTblSpc,
 					   int prettyFlags, bool missing_ok);
+static char *pg_get_partkeydef_worker(Oid relid, int prettyFlags);
 static char *pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 							int prettyFlags, bool missing_ok);
 static text *pg_get_expr_worker(text *expr, Oid relid, const char *relname,
@@ -1389,6 +1391,144 @@ 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 [ opclass_name ] [, ...])
+ */
+Datum
+pg_get_partkeydef(PG_FUNCTION_ARGS)
+{
+	Oid			relid = PG_GETARG_OID(0);
+	int			prettyFlags;
+
+	prettyFlags = PRETTYFLAG_INDENT;
+	PG_RETURN_TEXT_P(string_to_text(pg_get_partkeydef_worker(relid,
+									prettyFlags)));
+}
+
+/*
+ * Internal workhorse to decompile a partition key definition.
+ */
+static char *
+pg_get_partkeydef_worker(Oid relid, int prettyFlags)
+{
+	Form_pg_partitioned_table	form;
+	HeapTuple	tuple;
+	oidvector  *partclass;
+	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_table) GETSTRUCT(tuple);
+
+	Assert(form->partrelid == relid);
+
+	/* Must get partclass, and partexprs the hard way */
+	datum = SysCacheGetAttr(PARTEDRELID, tuple,
+							Anum_pg_partitioned_table_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_table_partexprbin))
+	{
+		Datum		exprsDatum;
+		bool		isnull;
+		char	   *exprsString;
+
+		exprsDatum = SysCacheGetAttr(PARTEDRELID, tuple,
+									 Anum_pg_partitioned_table_partexprbin, &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);
+
+	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,
+											0, 0);
+
+			appendStringInfoString(&buf, 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/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index ba9c276..c805a84 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -1253,9 +1253,10 @@ expand_table_name_patterns(Archive *fout,
 						  "SELECT c.oid"
 						  "\nFROM pg_catalog.pg_class c"
 		"\n     LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace"
-					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c')\n",
+					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c')\n",
 						  RELKIND_RELATION, RELKIND_SEQUENCE, RELKIND_VIEW,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 		processSQLNamePattern(GetConnection(fout), query, cell->val, true,
 							  false, "n.nspname", "c.relname", NULL,
 							  "pg_catalog.pg_table_is_visible(c.oid)");
@@ -2125,6 +2126,9 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo, bool oids)
 	/* Skip FOREIGN TABLEs (no data to dump) */
 	if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 		return;
+	/* Skip partitioned tables (data in partitions) */
+	if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+		return;
 
 	/* Don't dump data in unlogged tables, if so requested */
 	if (tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED &&
@@ -5204,6 +5208,7 @@ getTables(Archive *fout, int *numTables)
 	int			i_reloftype;
 	int			i_relpages;
 	int			i_changed_acl;
+	int			i_partkeydef;
 
 	/* Make sure we are in proper schema */
 	selectSourceSchema(fout, "pg_catalog");
@@ -5289,7 +5294,8 @@ getTables(Archive *fout, int *numTables)
 						  "OR %s IS NOT NULL "
 						  "OR %s IS NOT NULL"
 						  "))"
-						  "AS changed_acl "
+						  "AS changed_acl, "
+						  "CASE WHEN c.relkind = 'P' THEN pg_catalog.pg_get_partkeydef(c.oid) ELSE NULL END AS partkeydef "
 						  "FROM pg_class c "
 						  "LEFT JOIN pg_depend d ON "
 						  "(c.relkind = '%c' AND "
@@ -5301,7 +5307,7 @@ getTables(Archive *fout, int *numTables)
 						  "(c.oid = pip.objoid "
 						  "AND pip.classoid = 'pg_class'::regclass "
 						  "AND pip.objsubid = 0) "
-				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c') "
+				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c', '%c') "
 						  "ORDER BY c.oid",
 						  acl_subquery->data,
 						  racl_subquery->data,
@@ -5315,7 +5321,8 @@ getTables(Archive *fout, int *numTables)
 						  RELKIND_SEQUENCE,
 						  RELKIND_RELATION, RELKIND_SEQUENCE,
 						  RELKIND_VIEW, RELKIND_COMPOSITE_TYPE,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 
 		destroyPQExpBuffer(acl_subquery);
 		destroyPQExpBuffer(racl_subquery);
@@ -5877,6 +5884,7 @@ getTables(Archive *fout, int *numTables)
 	i_toastreloptions = PQfnumber(res, "toast_reloptions");
 	i_reloftype = PQfnumber(res, "reloftype");
 	i_changed_acl = PQfnumber(res, "changed_acl");
+	i_partkeydef = PQfnumber(res, "partkeydef");
 
 	if (dopt->lockWaitTimeout && fout->remoteVersion >= 70300)
 	{
@@ -5947,6 +5955,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 */
 
@@ -5991,7 +6000,9 @@ getTables(Archive *fout, int *numTables)
 		 * We only need to lock the table for certain components; see
 		 * pg_dump.h
 		 */
-		if (tblinfo[i].dobj.dump && tblinfo[i].relkind == RELKIND_RELATION &&
+		if (tblinfo[i].dobj.dump &&
+			(tblinfo[i].relkind == RELKIND_RELATION ||
+			 tblinfo->relkind == RELKIND_PARTITIONED_TABLE) &&
 			(tblinfo[i].dobj.dump & DUMP_COMPONENTS_REQUIRING_LOCK))
 		{
 			resetPQExpBuffer(query);
@@ -6093,7 +6104,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);
 
@@ -15446,6 +15460,9 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 				appendPQExpBufferChar(q, ')');
 			}
 
+			if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+				appendPQExpBuffer(q, "\nPARTITION BY %s", tbinfo->partkeydef);
+
 			if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 				appendPQExpBuffer(q, "\nSERVER %s", fmtId(srvname));
 		}
@@ -15506,6 +15523,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 		 */
 		if (dopt->binary_upgrade &&
 			(tbinfo->relkind == RELKIND_RELATION ||
+			 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 			 tbinfo->relkind == RELKIND_FOREIGN_TABLE))
 		{
 			for (j = 0; j < tbinfo->numatts; j++)
@@ -15524,7 +15542,8 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 					appendStringLiteralAH(q, fmtId(tbinfo->dobj.name), fout);
 					appendPQExpBufferStr(q, "::pg_catalog.regclass;\n");
 
-					if (tbinfo->relkind == RELKIND_RELATION)
+					if (tbinfo->relkind == RELKIND_RELATION ||
+						tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
 						appendPQExpBuffer(q, "ALTER TABLE ONLY %s ",
 										  fmtId(tbinfo->dobj.name));
 					else
@@ -15741,6 +15760,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 	 * dump properties we only have ALTER TABLE syntax for
 	 */
 	if ((tbinfo->relkind == RELKIND_RELATION ||
+		 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 		 tbinfo->relkind == RELKIND_MATVIEW) &&
 		tbinfo->relreplident != REPLICA_IDENTITY_DEFAULT)
 	{
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 2bfa2d9..0292859 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -310,6 +310,7 @@ typedef struct _tableInfo
 	bool	   *inhNotNull;		/* true if NOT NULL is inherited */
 	struct _attrDefInfo **attrdefs;		/* DEFAULT expressions */
 	struct _constraintInfo *checkexprs; /* CHECK constraints */
+	char	   *partkeydef;		/* partition key definition */
 
 	/*
 	 * Stuff computed only for dumpable tables.
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 6275a68..10d924a 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -861,6 +861,7 @@ permissionsList(const char *pattern)
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'S' THEN '%s'"
@@ -870,6 +871,7 @@ permissionsList(const char *pattern)
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("sequence"),
@@ -920,7 +922,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
@@ -1567,8 +1569,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)");
 	}
 
@@ -1633,6 +1635,14 @@ describeOneTableDetails(const char *schemaname,
 			printfPQExpBuffer(&title, _("Foreign table \"%s.%s\""),
 							  schemaname, relationname);
 			break;
+		case 'P':
+			if (tableinfo.relpersistence == 'u')
+				printfPQExpBuffer(&title, _("Unlogged table \"%s.%s\""),
+								  schemaname, relationname);
+			else
+				printfPQExpBuffer(&title, _("Table \"%s.%s\""),
+								  schemaname, relationname);
+			break;
 		default:
 			/* untranslated unknown relkind */
 			printfPQExpBuffer(&title, "?%c? \"%s.%s\"",
@@ -1646,8 +1656,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");
@@ -1667,12 +1677,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");
 	}
 
@@ -1772,7 +1782,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);
@@ -1780,14 +1790,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 (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 */
@@ -1926,7 +1955,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;
@@ -2485,7 +2514,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;
@@ -2696,7 +2725,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
@@ -3024,6 +3053,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'i' THEN '%s'"
@@ -3035,6 +3065,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("index"),
@@ -3079,7 +3110,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 019f75a..5e5c370 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/pg_proc.h b/src/include/catalog/pg_proc.h
index e2d08ba..b45688b 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -1980,6 +1980,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/utils/builtins.h b/src/include/utils/builtins.h
index 2ae212a..e800647 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -729,6 +729,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/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 708232d..2fec847 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -408,3 +408,29 @@ CREATE TABLE no_inh_con_parted (
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
 ERROR:  cannot add NO INHERIT constraint to partitioned table "no_inh_con_parted"
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+Table "public.describe_range_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition Key: RANGE ((a + b))
+
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+Table "public.describe_list_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition Key: LIST (a)
+
+DROP TABLE describe_range_key, describe_list_key;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 6e4a8be..4dd6a0a 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -402,3 +402,16 @@ CREATE TABLE no_inh_con_parted (
 	a int,
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
+
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+DROP TABLE describe_range_key, describe_list_key;
-- 
1.7.1


--------------4012470FC48539D6F6714CEA
Content-Type: text/x-diff;
 name="0003-Catalog-and-DDL-for-partitions-4.patch"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
 filename="0003-Catalog-and-DDL-for-partitions-4.patch"



^ permalink  raw  reply  [nested|flat] 110+ messages in thread

* [PATCH 2/9] psql and pg_dump support for partitioned tables.
@ 2016-07-12 08:20  amit <[email protected]>
  0 siblings, 0 replies; 110+ messages in thread

From: amit @ 2016-07-12 08:20 UTC (permalink / raw)

Takes care of both the partition key deparse stuff and the new relkind.
---
 src/backend/utils/adt/ruleutils.c          |  140 ++++++++++++++++++++++++++++
 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/pg_proc.h              |    2 +
 src/include/utils/builtins.h               |    1 +
 src/test/regress/expected/create_table.out |   26 +++++
 src/test/regress/sql/create_table.sql      |   13 +++
 9 files changed, 260 insertions(+), 26 deletions(-)

diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 8a81d7a..03be202 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_table.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
@@ -315,6 +316,7 @@ static char *pg_get_indexdef_worker(Oid indexrelid, int colno,
 					   const Oid *excludeOps,
 					   bool attrsOnly, bool showTblSpc,
 					   int prettyFlags, bool missing_ok);
+static char *pg_get_partkeydef_worker(Oid relid, int prettyFlags);
 static char *pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 							int prettyFlags, bool missing_ok);
 static text *pg_get_expr_worker(text *expr, Oid relid, const char *relname,
@@ -1389,6 +1391,144 @@ 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 [ opclass_name ] [, ...])
+ */
+Datum
+pg_get_partkeydef(PG_FUNCTION_ARGS)
+{
+	Oid			relid = PG_GETARG_OID(0);
+	int			prettyFlags;
+
+	prettyFlags = PRETTYFLAG_INDENT;
+	PG_RETURN_TEXT_P(string_to_text(pg_get_partkeydef_worker(relid,
+									prettyFlags)));
+}
+
+/*
+ * Internal workhorse to decompile a partition key definition.
+ */
+static char *
+pg_get_partkeydef_worker(Oid relid, int prettyFlags)
+{
+	Form_pg_partitioned_table	form;
+	HeapTuple	tuple;
+	oidvector  *partclass;
+	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_table) GETSTRUCT(tuple);
+
+	Assert(form->partrelid == relid);
+
+	/* Must get partclass, and partexprs the hard way */
+	datum = SysCacheGetAttr(PARTEDRELID, tuple,
+							Anum_pg_partitioned_table_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_table_partexprbin))
+	{
+		Datum		exprsDatum;
+		bool		isnull;
+		char	   *exprsString;
+
+		exprsDatum = SysCacheGetAttr(PARTEDRELID, tuple,
+									 Anum_pg_partitioned_table_partexprbin, &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);
+
+	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,
+											0, 0);
+
+			appendStringInfoString(&buf, 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/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index ba9c276..c805a84 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -1253,9 +1253,10 @@ expand_table_name_patterns(Archive *fout,
 						  "SELECT c.oid"
 						  "\nFROM pg_catalog.pg_class c"
 		"\n     LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace"
-					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c')\n",
+					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c')\n",
 						  RELKIND_RELATION, RELKIND_SEQUENCE, RELKIND_VIEW,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 		processSQLNamePattern(GetConnection(fout), query, cell->val, true,
 							  false, "n.nspname", "c.relname", NULL,
 							  "pg_catalog.pg_table_is_visible(c.oid)");
@@ -2125,6 +2126,9 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo, bool oids)
 	/* Skip FOREIGN TABLEs (no data to dump) */
 	if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 		return;
+	/* Skip partitioned tables (data in partitions) */
+	if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+		return;
 
 	/* Don't dump data in unlogged tables, if so requested */
 	if (tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED &&
@@ -5204,6 +5208,7 @@ getTables(Archive *fout, int *numTables)
 	int			i_reloftype;
 	int			i_relpages;
 	int			i_changed_acl;
+	int			i_partkeydef;
 
 	/* Make sure we are in proper schema */
 	selectSourceSchema(fout, "pg_catalog");
@@ -5289,7 +5294,8 @@ getTables(Archive *fout, int *numTables)
 						  "OR %s IS NOT NULL "
 						  "OR %s IS NOT NULL"
 						  "))"
-						  "AS changed_acl "
+						  "AS changed_acl, "
+						  "CASE WHEN c.relkind = 'P' THEN pg_catalog.pg_get_partkeydef(c.oid) ELSE NULL END AS partkeydef "
 						  "FROM pg_class c "
 						  "LEFT JOIN pg_depend d ON "
 						  "(c.relkind = '%c' AND "
@@ -5301,7 +5307,7 @@ getTables(Archive *fout, int *numTables)
 						  "(c.oid = pip.objoid "
 						  "AND pip.classoid = 'pg_class'::regclass "
 						  "AND pip.objsubid = 0) "
-				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c') "
+				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c', '%c') "
 						  "ORDER BY c.oid",
 						  acl_subquery->data,
 						  racl_subquery->data,
@@ -5315,7 +5321,8 @@ getTables(Archive *fout, int *numTables)
 						  RELKIND_SEQUENCE,
 						  RELKIND_RELATION, RELKIND_SEQUENCE,
 						  RELKIND_VIEW, RELKIND_COMPOSITE_TYPE,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 
 		destroyPQExpBuffer(acl_subquery);
 		destroyPQExpBuffer(racl_subquery);
@@ -5877,6 +5884,7 @@ getTables(Archive *fout, int *numTables)
 	i_toastreloptions = PQfnumber(res, "toast_reloptions");
 	i_reloftype = PQfnumber(res, "reloftype");
 	i_changed_acl = PQfnumber(res, "changed_acl");
+	i_partkeydef = PQfnumber(res, "partkeydef");
 
 	if (dopt->lockWaitTimeout && fout->remoteVersion >= 70300)
 	{
@@ -5947,6 +5955,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 */
 
@@ -5991,7 +6000,9 @@ getTables(Archive *fout, int *numTables)
 		 * We only need to lock the table for certain components; see
 		 * pg_dump.h
 		 */
-		if (tblinfo[i].dobj.dump && tblinfo[i].relkind == RELKIND_RELATION &&
+		if (tblinfo[i].dobj.dump &&
+			(tblinfo[i].relkind == RELKIND_RELATION ||
+			 tblinfo->relkind == RELKIND_PARTITIONED_TABLE) &&
 			(tblinfo[i].dobj.dump & DUMP_COMPONENTS_REQUIRING_LOCK))
 		{
 			resetPQExpBuffer(query);
@@ -6093,7 +6104,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);
 
@@ -15446,6 +15460,9 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 				appendPQExpBufferChar(q, ')');
 			}
 
+			if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+				appendPQExpBuffer(q, "\nPARTITION BY %s", tbinfo->partkeydef);
+
 			if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 				appendPQExpBuffer(q, "\nSERVER %s", fmtId(srvname));
 		}
@@ -15506,6 +15523,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 		 */
 		if (dopt->binary_upgrade &&
 			(tbinfo->relkind == RELKIND_RELATION ||
+			 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 			 tbinfo->relkind == RELKIND_FOREIGN_TABLE))
 		{
 			for (j = 0; j < tbinfo->numatts; j++)
@@ -15524,7 +15542,8 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 					appendStringLiteralAH(q, fmtId(tbinfo->dobj.name), fout);
 					appendPQExpBufferStr(q, "::pg_catalog.regclass;\n");
 
-					if (tbinfo->relkind == RELKIND_RELATION)
+					if (tbinfo->relkind == RELKIND_RELATION ||
+						tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
 						appendPQExpBuffer(q, "ALTER TABLE ONLY %s ",
 										  fmtId(tbinfo->dobj.name));
 					else
@@ -15741,6 +15760,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 	 * dump properties we only have ALTER TABLE syntax for
 	 */
 	if ((tbinfo->relkind == RELKIND_RELATION ||
+		 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 		 tbinfo->relkind == RELKIND_MATVIEW) &&
 		tbinfo->relreplident != REPLICA_IDENTITY_DEFAULT)
 	{
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 2bfa2d9..0292859 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -310,6 +310,7 @@ typedef struct _tableInfo
 	bool	   *inhNotNull;		/* true if NOT NULL is inherited */
 	struct _attrDefInfo **attrdefs;		/* DEFAULT expressions */
 	struct _constraintInfo *checkexprs; /* CHECK constraints */
+	char	   *partkeydef;		/* partition key definition */
 
 	/*
 	 * Stuff computed only for dumpable tables.
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 6275a68..10d924a 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -861,6 +861,7 @@ permissionsList(const char *pattern)
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'S' THEN '%s'"
@@ -870,6 +871,7 @@ permissionsList(const char *pattern)
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("sequence"),
@@ -920,7 +922,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
@@ -1567,8 +1569,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)");
 	}
 
@@ -1633,6 +1635,14 @@ describeOneTableDetails(const char *schemaname,
 			printfPQExpBuffer(&title, _("Foreign table \"%s.%s\""),
 							  schemaname, relationname);
 			break;
+		case 'P':
+			if (tableinfo.relpersistence == 'u')
+				printfPQExpBuffer(&title, _("Unlogged table \"%s.%s\""),
+								  schemaname, relationname);
+			else
+				printfPQExpBuffer(&title, _("Table \"%s.%s\""),
+								  schemaname, relationname);
+			break;
 		default:
 			/* untranslated unknown relkind */
 			printfPQExpBuffer(&title, "?%c? \"%s.%s\"",
@@ -1646,8 +1656,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");
@@ -1667,12 +1677,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");
 	}
 
@@ -1772,7 +1782,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);
@@ -1780,14 +1790,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 (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 */
@@ -1926,7 +1955,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;
@@ -2485,7 +2514,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;
@@ -2696,7 +2725,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
@@ -3024,6 +3053,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'i' THEN '%s'"
@@ -3035,6 +3065,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("index"),
@@ -3079,7 +3110,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 50a45eb..8284a9c 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/pg_proc.h b/src/include/catalog/pg_proc.h
index e2d08ba..b45688b 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -1980,6 +1980,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/utils/builtins.h b/src/include/utils/builtins.h
index 2ae212a..e800647 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -729,6 +729,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/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 0af1422..91ba1be 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -412,3 +412,29 @@ CREATE TABLE no_inh_con_parted (
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
 ERROR:  cannot add NO INHERIT constraint to partitioned table "no_inh_con_parted"
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+Table "public.describe_range_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition Key: RANGE ((a + b))
+
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+Table "public.describe_list_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition Key: LIST (a)
+
+DROP TABLE describe_range_key, describe_list_key;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 418ac55..b75542d 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -406,3 +406,16 @@ CREATE TABLE no_inh_con_parted (
 	a int,
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
+
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+DROP TABLE describe_range_key, describe_list_key;
-- 
1.7.1


--------------D008BA677674C4836D7B65AD
Content-Type: text/x-diff;
 name="0003-Catalog-and-DDL-for-partitions-5.patch"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
 filename="0003-Catalog-and-DDL-for-partitions-5.patch"



^ permalink  raw  reply  [nested|flat] 110+ messages in thread

* [PATCH 2/9] psql and pg_dump support for partitioned tables.
@ 2016-07-12 08:20  amit <[email protected]>
  0 siblings, 0 replies; 110+ messages in thread

From: amit @ 2016-07-12 08:20 UTC (permalink / raw)

Takes care of both the partition key deparse stuff and the new relkind.
---
 src/backend/utils/adt/ruleutils.c          |  140 ++++++++++++++++++++++++++++
 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/pg_proc.h              |    2 +
 src/include/utils/builtins.h               |    1 +
 src/test/regress/expected/create_table.out |   26 +++++
 src/test/regress/sql/create_table.sql      |   13 +++
 9 files changed, 260 insertions(+), 26 deletions(-)

diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 8a81d7a..03be202 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_table.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
@@ -315,6 +316,7 @@ static char *pg_get_indexdef_worker(Oid indexrelid, int colno,
 					   const Oid *excludeOps,
 					   bool attrsOnly, bool showTblSpc,
 					   int prettyFlags, bool missing_ok);
+static char *pg_get_partkeydef_worker(Oid relid, int prettyFlags);
 static char *pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 							int prettyFlags, bool missing_ok);
 static text *pg_get_expr_worker(text *expr, Oid relid, const char *relname,
@@ -1389,6 +1391,144 @@ 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 [ opclass_name ] [, ...])
+ */
+Datum
+pg_get_partkeydef(PG_FUNCTION_ARGS)
+{
+	Oid			relid = PG_GETARG_OID(0);
+	int			prettyFlags;
+
+	prettyFlags = PRETTYFLAG_INDENT;
+	PG_RETURN_TEXT_P(string_to_text(pg_get_partkeydef_worker(relid,
+									prettyFlags)));
+}
+
+/*
+ * Internal workhorse to decompile a partition key definition.
+ */
+static char *
+pg_get_partkeydef_worker(Oid relid, int prettyFlags)
+{
+	Form_pg_partitioned_table	form;
+	HeapTuple	tuple;
+	oidvector  *partclass;
+	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_table) GETSTRUCT(tuple);
+
+	Assert(form->partrelid == relid);
+
+	/* Must get partclass, and partexprs the hard way */
+	datum = SysCacheGetAttr(PARTEDRELID, tuple,
+							Anum_pg_partitioned_table_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_table_partexprbin))
+	{
+		Datum		exprsDatum;
+		bool		isnull;
+		char	   *exprsString;
+
+		exprsDatum = SysCacheGetAttr(PARTEDRELID, tuple,
+									 Anum_pg_partitioned_table_partexprbin, &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);
+
+	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,
+											0, 0);
+
+			appendStringInfoString(&buf, 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/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 51b8a1a..854dddc 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -1253,9 +1253,10 @@ expand_table_name_patterns(Archive *fout,
 						  "SELECT c.oid"
 						  "\nFROM pg_catalog.pg_class c"
 		"\n     LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace"
-					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c')\n",
+					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c')\n",
 						  RELKIND_RELATION, RELKIND_SEQUENCE, RELKIND_VIEW,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 		processSQLNamePattern(GetConnection(fout), query, cell->val, true,
 							  false, "n.nspname", "c.relname", NULL,
 							  "pg_catalog.pg_table_is_visible(c.oid)");
@@ -2125,6 +2126,9 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo, bool oids)
 	/* Skip FOREIGN TABLEs (no data to dump) */
 	if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 		return;
+	/* Skip partitioned tables (data in partitions) */
+	if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+		return;
 
 	/* Don't dump data in unlogged tables, if so requested */
 	if (tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED &&
@@ -5211,6 +5215,7 @@ getTables(Archive *fout, int *numTables)
 	int			i_reloftype;
 	int			i_relpages;
 	int			i_changed_acl;
+	int			i_partkeydef;
 
 	/* Make sure we are in proper schema */
 	selectSourceSchema(fout, "pg_catalog");
@@ -5296,7 +5301,8 @@ getTables(Archive *fout, int *numTables)
 						  "OR %s IS NOT NULL "
 						  "OR %s IS NOT NULL"
 						  "))"
-						  "AS changed_acl "
+						  "AS changed_acl, "
+						  "CASE WHEN c.relkind = 'P' THEN pg_catalog.pg_get_partkeydef(c.oid) ELSE NULL END AS partkeydef "
 						  "FROM pg_class c "
 						  "LEFT JOIN pg_depend d ON "
 						  "(c.relkind = '%c' AND "
@@ -5308,7 +5314,7 @@ getTables(Archive *fout, int *numTables)
 						  "(c.oid = pip.objoid "
 						  "AND pip.classoid = 'pg_class'::regclass "
 						  "AND pip.objsubid = 0) "
-				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c') "
+				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c', '%c') "
 						  "ORDER BY c.oid",
 						  acl_subquery->data,
 						  racl_subquery->data,
@@ -5322,7 +5328,8 @@ getTables(Archive *fout, int *numTables)
 						  RELKIND_SEQUENCE,
 						  RELKIND_RELATION, RELKIND_SEQUENCE,
 						  RELKIND_VIEW, RELKIND_COMPOSITE_TYPE,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 
 		destroyPQExpBuffer(acl_subquery);
 		destroyPQExpBuffer(racl_subquery);
@@ -5884,6 +5891,7 @@ getTables(Archive *fout, int *numTables)
 	i_toastreloptions = PQfnumber(res, "toast_reloptions");
 	i_reloftype = PQfnumber(res, "reloftype");
 	i_changed_acl = PQfnumber(res, "changed_acl");
+	i_partkeydef = PQfnumber(res, "partkeydef");
 
 	if (dopt->lockWaitTimeout && fout->remoteVersion >= 70300)
 	{
@@ -5954,6 +5962,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 */
 
@@ -5998,7 +6007,9 @@ getTables(Archive *fout, int *numTables)
 		 * We only need to lock the table for certain components; see
 		 * pg_dump.h
 		 */
-		if (tblinfo[i].dobj.dump && tblinfo[i].relkind == RELKIND_RELATION &&
+		if (tblinfo[i].dobj.dump &&
+			(tblinfo[i].relkind == RELKIND_RELATION ||
+			 tblinfo->relkind == RELKIND_PARTITIONED_TABLE) &&
 			(tblinfo[i].dobj.dump & DUMP_COMPONENTS_REQUIRING_LOCK))
 		{
 			resetPQExpBuffer(query);
@@ -6100,7 +6111,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);
 
@@ -15453,6 +15467,9 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 				appendPQExpBufferChar(q, ')');
 			}
 
+			if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+				appendPQExpBuffer(q, "\nPARTITION BY %s", tbinfo->partkeydef);
+
 			if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 				appendPQExpBuffer(q, "\nSERVER %s", fmtId(srvname));
 		}
@@ -15513,6 +15530,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 		 */
 		if (dopt->binary_upgrade &&
 			(tbinfo->relkind == RELKIND_RELATION ||
+			 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 			 tbinfo->relkind == RELKIND_FOREIGN_TABLE))
 		{
 			for (j = 0; j < tbinfo->numatts; j++)
@@ -15531,7 +15549,8 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 					appendStringLiteralAH(q, fmtId(tbinfo->dobj.name), fout);
 					appendPQExpBufferStr(q, "::pg_catalog.regclass;\n");
 
-					if (tbinfo->relkind == RELKIND_RELATION)
+					if (tbinfo->relkind == RELKIND_RELATION ||
+						tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
 						appendPQExpBuffer(q, "ALTER TABLE ONLY %s ",
 										  fmtId(tbinfo->dobj.name));
 					else
@@ -15748,6 +15767,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 	 * dump properties we only have ALTER TABLE syntax for
 	 */
 	if ((tbinfo->relkind == RELKIND_RELATION ||
+		 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 		 tbinfo->relkind == RELKIND_MATVIEW) &&
 		tbinfo->relreplident != REPLICA_IDENTITY_DEFAULT)
 	{
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 2bfa2d9..0292859 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -310,6 +310,7 @@ typedef struct _tableInfo
 	bool	   *inhNotNull;		/* true if NOT NULL is inherited */
 	struct _attrDefInfo **attrdefs;		/* DEFAULT expressions */
 	struct _constraintInfo *checkexprs; /* CHECK constraints */
+	char	   *partkeydef;		/* partition key definition */
 
 	/*
 	 * Stuff computed only for dumpable tables.
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 6275a68..db6dc5c 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -861,6 +861,7 @@ permissionsList(const char *pattern)
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'S' THEN '%s'"
@@ -870,6 +871,7 @@ permissionsList(const char *pattern)
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("sequence"),
@@ -920,7 +922,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
@@ -1567,8 +1569,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)");
 	}
 
@@ -1633,6 +1635,14 @@ describeOneTableDetails(const char *schemaname,
 			printfPQExpBuffer(&title, _("Foreign table \"%s.%s\""),
 							  schemaname, relationname);
 			break;
+		case 'P':
+			if (tableinfo.relpersistence == 'u')
+				printfPQExpBuffer(&title, _("Unlogged table \"%s.%s\""),
+								  schemaname, relationname);
+			else
+				printfPQExpBuffer(&title, _("Table \"%s.%s\""),
+								  schemaname, relationname);
+			break;
 		default:
 			/* untranslated unknown relkind */
 			printfPQExpBuffer(&title, "?%c? \"%s.%s\"",
@@ -1646,8 +1656,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");
@@ -1667,12 +1677,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");
 	}
 
@@ -1772,7 +1782,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);
@@ -1780,14 +1790,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 (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 */
@@ -1926,7 +1955,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;
@@ -2485,7 +2514,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;
@@ -2696,7 +2725,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
@@ -3024,6 +3053,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'i' THEN '%s'"
@@ -3035,6 +3065,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("index"),
@@ -3079,7 +3110,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 50a45eb..8284a9c 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/pg_proc.h b/src/include/catalog/pg_proc.h
index e2d08ba..b45688b 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -1980,6 +1980,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/utils/builtins.h b/src/include/utils/builtins.h
index 2ae212a..e800647 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -729,6 +729,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/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 1394362..260ec34 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -407,3 +407,29 @@ CREATE TABLE no_inh_con_parted (
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
 ERROR:  cannot add NO INHERIT constraint to partitioned table "no_inh_con_parted"
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+Table "public.describe_range_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition key: RANGE ((a + b))
+
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+Table "public.describe_list_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition key: LIST (a)
+
+DROP TABLE describe_range_key, describe_list_key;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index d383f58..7a1f8de 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -401,3 +401,16 @@ CREATE TABLE no_inh_con_parted (
 	a int,
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
+
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+DROP TABLE describe_range_key, describe_list_key;
-- 
1.7.1


--------------C3235EE96C69D8DAE4DB543E
Content-Type: text/x-diff;
 name="0003-Catalog-and-DDL-for-partitions-6.patch"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
 filename="0003-Catalog-and-DDL-for-partitions-6.patch"



^ permalink  raw  reply  [nested|flat] 110+ messages in thread

* [PATCH 2/8] psql and pg_dump support for partitioned tables.
@ 2016-07-12 08:20  amit <[email protected]>
  0 siblings, 0 replies; 110+ messages in thread

From: amit @ 2016-07-12 08:20 UTC (permalink / raw)

Takes care of both the partition key deparse stuff and the new relkind.
---
 src/backend/utils/adt/ruleutils.c          |  147 ++++++++++++++++++++++++++++
 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/pg_proc.h              |    2 +
 src/include/utils/builtins.h               |    1 +
 src/test/regress/expected/create_table.out |   26 +++++
 src/test/regress/sql/create_table.sql      |   13 +++
 9 files changed, 267 insertions(+), 26 deletions(-)

diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index ec966c7..4691b5b 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, bool missing_ok);
+static char *pg_get_partkeydef_worker(Oid relId, int prettyFlags);
 static char *pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 							int prettyFlags, bool missing_ok);
 static text *pg_get_expr_worker(text *expr, Oid relid, const char *relname,
@@ -1389,6 +1391,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 [ 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/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 4ee10fc..a127672 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -1253,9 +1253,10 @@ expand_table_name_patterns(Archive *fout,
 						  "SELECT c.oid"
 						  "\nFROM pg_catalog.pg_class c"
 		"\n     LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace"
-					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c')\n",
+					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c')\n",
 						  RELKIND_RELATION, RELKIND_SEQUENCE, RELKIND_VIEW,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_REL);
 		processSQLNamePattern(GetConnection(fout), query, cell->val, true,
 							  false, "n.nspname", "c.relname", NULL,
 							  "pg_catalog.pg_table_is_visible(c.oid)");
@@ -2117,6 +2118,9 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo, bool oids)
 	/* Skip FOREIGN TABLEs (no data to dump) */
 	if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 		return;
+	/* Skip partitioned tables (data in partitions) */
+	if (tbinfo->relkind == RELKIND_PARTITIONED_REL)
+		return;
 
 	/* Don't dump data in unlogged tables, if so requested */
 	if (tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED &&
@@ -5195,6 +5199,7 @@ getTables(Archive *fout, int *numTables)
 	int			i_reloftype;
 	int			i_relpages;
 	int			i_changed_acl;
+	int			i_partkeydef;
 
 	/* Make sure we are in proper schema */
 	selectSourceSchema(fout, "pg_catalog");
@@ -5280,7 +5285,8 @@ getTables(Archive *fout, int *numTables)
 						  "OR %s IS NOT NULL "
 						  "OR %s IS NOT NULL"
 						  "))"
-						  "AS changed_acl "
+						  "AS changed_acl, "
+						  "CASE WHEN c.relkind = 'P' THEN pg_catalog.pg_get_partkeydef(c.oid) ELSE NULL END AS partkeydef "
 						  "FROM pg_class c "
 						  "LEFT JOIN pg_depend d ON "
 						  "(c.relkind = '%c' AND "
@@ -5292,7 +5298,7 @@ getTables(Archive *fout, int *numTables)
 						  "(c.oid = pip.objoid "
 						  "AND pip.classoid = 'pg_class'::regclass "
 						  "AND pip.objsubid = 0) "
-				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c') "
+				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c', '%c') "
 						  "ORDER BY c.oid",
 						  acl_subquery->data,
 						  racl_subquery->data,
@@ -5306,7 +5312,8 @@ getTables(Archive *fout, int *numTables)
 						  RELKIND_SEQUENCE,
 						  RELKIND_RELATION, RELKIND_SEQUENCE,
 						  RELKIND_VIEW, RELKIND_COMPOSITE_TYPE,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_REL);
 
 		destroyPQExpBuffer(acl_subquery);
 		destroyPQExpBuffer(racl_subquery);
@@ -5868,6 +5875,7 @@ getTables(Archive *fout, int *numTables)
 	i_toastreloptions = PQfnumber(res, "toast_reloptions");
 	i_reloftype = PQfnumber(res, "reloftype");
 	i_changed_acl = PQfnumber(res, "changed_acl");
+	i_partkeydef = PQfnumber(res, "partkeydef");
 
 	if (dopt->lockWaitTimeout && fout->remoteVersion >= 70300)
 	{
@@ -5938,6 +5946,7 @@ getTables(Archive *fout, int *numTables)
 		else
 			tblinfo[i].checkoption = pg_strdup(PQgetvalue(res, i, i_checkoption));
 		tblinfo[i].toast_reloptions = pg_strdup(PQgetvalue(res, i, i_toastreloptions));
+		tblinfo[i].partkeydef = pg_strdup(PQgetvalue(res, i, i_partkeydef));
 
 		/* other fields were zeroed above */
 
@@ -5982,7 +5991,9 @@ getTables(Archive *fout, int *numTables)
 		 * We only need to lock the table for certain components; see
 		 * pg_dump.h
 		 */
-		if (tblinfo[i].dobj.dump && tblinfo[i].relkind == RELKIND_RELATION &&
+		if (tblinfo[i].dobj.dump &&
+			(tblinfo[i].relkind == RELKIND_RELATION ||
+			 tblinfo->relkind == RELKIND_PARTITIONED_REL) &&
 			(tblinfo[i].dobj.dump & DUMP_COMPONENTS_REQUIRING_LOCK))
 		{
 			resetPQExpBuffer(query);
@@ -6083,7 +6094,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);
 
@@ -15436,6 +15450,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));
 		}
@@ -15496,6 +15513,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++)
@@ -15514,7 +15532,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
@@ -15731,6 +15750,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 	 * dump properties we only have ALTER TABLE syntax for
 	 */
 	if ((tbinfo->relkind == RELKIND_RELATION ||
+		 tbinfo->relkind == RELKIND_PARTITIONED_REL ||
 		 tbinfo->relkind == RELKIND_MATVIEW) &&
 		tbinfo->relreplident != REPLICA_IDENTITY_DEFAULT)
 	{
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 2bfa2d9..0292859 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -310,6 +310,7 @@ typedef struct _tableInfo
 	bool	   *inhNotNull;		/* true if NOT NULL is inherited */
 	struct _attrDefInfo **attrdefs;		/* DEFAULT expressions */
 	struct _constraintInfo *checkexprs; /* CHECK constraints */
+	char	   *partkeydef;		/* partition key definition */
 
 	/*
 	 * Stuff computed only for dumpable tables.
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 27be102..3ba8a90 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -856,6 +856,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"),
@@ -865,6 +866,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");
@@ -911,7 +913,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
@@ -1555,8 +1557,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)");
 	}
 
@@ -1621,6 +1623,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\"",
@@ -1634,8 +1644,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");
@@ -1655,12 +1665,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");
 	}
 
@@ -1760,7 +1770,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);
@@ -1768,14 +1778,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 */
@@ -1914,7 +1943,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;
@@ -2473,7 +2502,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;
@@ -2684,7 +2713,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
@@ -3018,6 +3047,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"),
@@ -3029,6 +3059,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"));
 
@@ -3067,7 +3098,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 
 	appendPQExpBufferStr(&buf, "\nWHERE c.relkind IN (");
 	if (showTables)
-		appendPQExpBufferStr(&buf, "'r',");
+		appendPQExpBufferStr(&buf, "'r', 'P',");
 	if (showViews)
 		appendPQExpBufferStr(&buf, "'v',");
 	if (showMatViews)
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 8469d9f..cd4eb7c 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -427,7 +427,7 @@ static const SchemaQuery Query_for_list_of_tables = {
 	/* catname */
 	"pg_catalog.pg_class c",
 	/* selcondition */
-	"c.relkind IN ('r')",
+	"c.relkind IN ('r', 'P')",
 	/* viscondition */
 	"pg_catalog.pg_table_is_visible(c.oid)",
 	/* namespace */
@@ -458,7 +458,7 @@ static const SchemaQuery Query_for_list_of_updatables = {
 	/* catname */
 	"pg_catalog.pg_class c",
 	/* selcondition */
-	"c.relkind IN ('r', 'f', 'v')",
+	"c.relkind IN ('r', 'f', 'v', 'P')",
 	/* viscondition */
 	"pg_catalog.pg_table_is_visible(c.oid)",
 	/* namespace */
@@ -488,7 +488,7 @@ static const SchemaQuery Query_for_list_of_tsvmf = {
 	/* catname */
 	"pg_catalog.pg_class c",
 	/* selcondition */
-	"c.relkind IN ('r', 'S', 'v', 'm', 'f')",
+	"c.relkind IN ('r', 'S', 'v', 'm', 'f', 'P')",
 	/* viscondition */
 	"pg_catalog.pg_table_is_visible(c.oid)",
 	/* namespace */
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index 270dd21..5a444d9 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/utils/builtins.h b/src/include/utils/builtins.h
index 8cebc86..351bfd3 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -722,6 +722,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/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index fb7a1b3..4fc8c06 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -413,3 +413,29 @@ SELECT connoinherit FROM pg_constraint WHERE conrelid = 'no_inh_con_parted'::reg
 (1 row)
 
 DROP TABLE no_inh_con_parted;
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+Partitioned table "public.describe_range_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition Key: PARTITION BY RANGE (((a + b)))
+
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+Partitioned table "public.describe_list_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition Key: PARTITION BY LIST (a)
+
+DROP TABLE describe_range_key, describe_list_key;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index cb17517..7c7a18a 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -404,3 +404,16 @@ CREATE TABLE no_inh_con_parted (
 ) PARTITION BY RANGE (a);
 SELECT connoinherit FROM pg_constraint WHERE conrelid = 'no_inh_con_parted'::regclass;
 DROP TABLE no_inh_con_parted;
+
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+DROP TABLE describe_range_key, describe_list_key;
-- 
1.7.1


--------------8C4C48E640631A327D3D2826
Content-Type: text/x-diff;
 name="0003-Catalog-and-DDL-for-partitions.patch"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
 filename="0003-Catalog-and-DDL-for-partitions.patch"



^ permalink  raw  reply  [nested|flat] 110+ messages in thread

* [PATCH 2/9] psql and pg_dump support for partitioned tables.
@ 2016-07-12 08:20  amit <[email protected]>
  0 siblings, 0 replies; 110+ messages in thread

From: amit @ 2016-07-12 08:20 UTC (permalink / raw)

Takes care of both the partition key deparse stuff and the new relkind.
---
 src/backend/utils/adt/ruleutils.c          |  140 ++++++++++++++++++++++++++++
 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/pg_proc.h              |    2 +
 src/include/utils/builtins.h               |    1 +
 src/test/regress/expected/create_table.out |   26 +++++
 src/test/regress/sql/create_table.sql      |   13 +++
 9 files changed, 260 insertions(+), 26 deletions(-)

diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 8a81d7a..03be202 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_table.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
@@ -315,6 +316,7 @@ static char *pg_get_indexdef_worker(Oid indexrelid, int colno,
 					   const Oid *excludeOps,
 					   bool attrsOnly, bool showTblSpc,
 					   int prettyFlags, bool missing_ok);
+static char *pg_get_partkeydef_worker(Oid relid, int prettyFlags);
 static char *pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 							int prettyFlags, bool missing_ok);
 static text *pg_get_expr_worker(text *expr, Oid relid, const char *relname,
@@ -1389,6 +1391,144 @@ 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 [ opclass_name ] [, ...])
+ */
+Datum
+pg_get_partkeydef(PG_FUNCTION_ARGS)
+{
+	Oid			relid = PG_GETARG_OID(0);
+	int			prettyFlags;
+
+	prettyFlags = PRETTYFLAG_INDENT;
+	PG_RETURN_TEXT_P(string_to_text(pg_get_partkeydef_worker(relid,
+									prettyFlags)));
+}
+
+/*
+ * Internal workhorse to decompile a partition key definition.
+ */
+static char *
+pg_get_partkeydef_worker(Oid relid, int prettyFlags)
+{
+	Form_pg_partitioned_table	form;
+	HeapTuple	tuple;
+	oidvector  *partclass;
+	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_table) GETSTRUCT(tuple);
+
+	Assert(form->partrelid == relid);
+
+	/* Must get partclass, and partexprs the hard way */
+	datum = SysCacheGetAttr(PARTEDRELID, tuple,
+							Anum_pg_partitioned_table_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_table_partexprbin))
+	{
+		Datum		exprsDatum;
+		bool		isnull;
+		char	   *exprsString;
+
+		exprsDatum = SysCacheGetAttr(PARTEDRELID, tuple,
+									 Anum_pg_partitioned_table_partexprbin, &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);
+
+	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,
+											0, 0);
+
+			appendStringInfoString(&buf, 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/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index ba9c276..c805a84 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -1253,9 +1253,10 @@ expand_table_name_patterns(Archive *fout,
 						  "SELECT c.oid"
 						  "\nFROM pg_catalog.pg_class c"
 		"\n     LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace"
-					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c')\n",
+					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c')\n",
 						  RELKIND_RELATION, RELKIND_SEQUENCE, RELKIND_VIEW,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 		processSQLNamePattern(GetConnection(fout), query, cell->val, true,
 							  false, "n.nspname", "c.relname", NULL,
 							  "pg_catalog.pg_table_is_visible(c.oid)");
@@ -2125,6 +2126,9 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo, bool oids)
 	/* Skip FOREIGN TABLEs (no data to dump) */
 	if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 		return;
+	/* Skip partitioned tables (data in partitions) */
+	if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+		return;
 
 	/* Don't dump data in unlogged tables, if so requested */
 	if (tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED &&
@@ -5204,6 +5208,7 @@ getTables(Archive *fout, int *numTables)
 	int			i_reloftype;
 	int			i_relpages;
 	int			i_changed_acl;
+	int			i_partkeydef;
 
 	/* Make sure we are in proper schema */
 	selectSourceSchema(fout, "pg_catalog");
@@ -5289,7 +5294,8 @@ getTables(Archive *fout, int *numTables)
 						  "OR %s IS NOT NULL "
 						  "OR %s IS NOT NULL"
 						  "))"
-						  "AS changed_acl "
+						  "AS changed_acl, "
+						  "CASE WHEN c.relkind = 'P' THEN pg_catalog.pg_get_partkeydef(c.oid) ELSE NULL END AS partkeydef "
 						  "FROM pg_class c "
 						  "LEFT JOIN pg_depend d ON "
 						  "(c.relkind = '%c' AND "
@@ -5301,7 +5307,7 @@ getTables(Archive *fout, int *numTables)
 						  "(c.oid = pip.objoid "
 						  "AND pip.classoid = 'pg_class'::regclass "
 						  "AND pip.objsubid = 0) "
-				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c') "
+				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c', '%c') "
 						  "ORDER BY c.oid",
 						  acl_subquery->data,
 						  racl_subquery->data,
@@ -5315,7 +5321,8 @@ getTables(Archive *fout, int *numTables)
 						  RELKIND_SEQUENCE,
 						  RELKIND_RELATION, RELKIND_SEQUENCE,
 						  RELKIND_VIEW, RELKIND_COMPOSITE_TYPE,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 
 		destroyPQExpBuffer(acl_subquery);
 		destroyPQExpBuffer(racl_subquery);
@@ -5877,6 +5884,7 @@ getTables(Archive *fout, int *numTables)
 	i_toastreloptions = PQfnumber(res, "toast_reloptions");
 	i_reloftype = PQfnumber(res, "reloftype");
 	i_changed_acl = PQfnumber(res, "changed_acl");
+	i_partkeydef = PQfnumber(res, "partkeydef");
 
 	if (dopt->lockWaitTimeout && fout->remoteVersion >= 70300)
 	{
@@ -5947,6 +5955,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 */
 
@@ -5991,7 +6000,9 @@ getTables(Archive *fout, int *numTables)
 		 * We only need to lock the table for certain components; see
 		 * pg_dump.h
 		 */
-		if (tblinfo[i].dobj.dump && tblinfo[i].relkind == RELKIND_RELATION &&
+		if (tblinfo[i].dobj.dump &&
+			(tblinfo[i].relkind == RELKIND_RELATION ||
+			 tblinfo->relkind == RELKIND_PARTITIONED_TABLE) &&
 			(tblinfo[i].dobj.dump & DUMP_COMPONENTS_REQUIRING_LOCK))
 		{
 			resetPQExpBuffer(query);
@@ -6093,7 +6104,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);
 
@@ -15446,6 +15460,9 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 				appendPQExpBufferChar(q, ')');
 			}
 
+			if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+				appendPQExpBuffer(q, "\nPARTITION BY %s", tbinfo->partkeydef);
+
 			if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 				appendPQExpBuffer(q, "\nSERVER %s", fmtId(srvname));
 		}
@@ -15506,6 +15523,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 		 */
 		if (dopt->binary_upgrade &&
 			(tbinfo->relkind == RELKIND_RELATION ||
+			 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 			 tbinfo->relkind == RELKIND_FOREIGN_TABLE))
 		{
 			for (j = 0; j < tbinfo->numatts; j++)
@@ -15524,7 +15542,8 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 					appendStringLiteralAH(q, fmtId(tbinfo->dobj.name), fout);
 					appendPQExpBufferStr(q, "::pg_catalog.regclass;\n");
 
-					if (tbinfo->relkind == RELKIND_RELATION)
+					if (tbinfo->relkind == RELKIND_RELATION ||
+						tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
 						appendPQExpBuffer(q, "ALTER TABLE ONLY %s ",
 										  fmtId(tbinfo->dobj.name));
 					else
@@ -15741,6 +15760,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 	 * dump properties we only have ALTER TABLE syntax for
 	 */
 	if ((tbinfo->relkind == RELKIND_RELATION ||
+		 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 		 tbinfo->relkind == RELKIND_MATVIEW) &&
 		tbinfo->relreplident != REPLICA_IDENTITY_DEFAULT)
 	{
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 2bfa2d9..0292859 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -310,6 +310,7 @@ typedef struct _tableInfo
 	bool	   *inhNotNull;		/* true if NOT NULL is inherited */
 	struct _attrDefInfo **attrdefs;		/* DEFAULT expressions */
 	struct _constraintInfo *checkexprs; /* CHECK constraints */
+	char	   *partkeydef;		/* partition key definition */
 
 	/*
 	 * Stuff computed only for dumpable tables.
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 6275a68..10d924a 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -861,6 +861,7 @@ permissionsList(const char *pattern)
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'S' THEN '%s'"
@@ -870,6 +871,7 @@ permissionsList(const char *pattern)
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("sequence"),
@@ -920,7 +922,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
@@ -1567,8 +1569,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)");
 	}
 
@@ -1633,6 +1635,14 @@ describeOneTableDetails(const char *schemaname,
 			printfPQExpBuffer(&title, _("Foreign table \"%s.%s\""),
 							  schemaname, relationname);
 			break;
+		case 'P':
+			if (tableinfo.relpersistence == 'u')
+				printfPQExpBuffer(&title, _("Unlogged table \"%s.%s\""),
+								  schemaname, relationname);
+			else
+				printfPQExpBuffer(&title, _("Table \"%s.%s\""),
+								  schemaname, relationname);
+			break;
 		default:
 			/* untranslated unknown relkind */
 			printfPQExpBuffer(&title, "?%c? \"%s.%s\"",
@@ -1646,8 +1656,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");
@@ -1667,12 +1677,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");
 	}
 
@@ -1772,7 +1782,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);
@@ -1780,14 +1790,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 (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 */
@@ -1926,7 +1955,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;
@@ -2485,7 +2514,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;
@@ -2696,7 +2725,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
@@ -3024,6 +3053,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'i' THEN '%s'"
@@ -3035,6 +3065,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("index"),
@@ -3079,7 +3110,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 019f75a..5e5c370 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/pg_proc.h b/src/include/catalog/pg_proc.h
index e2d08ba..b45688b 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -1980,6 +1980,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/utils/builtins.h b/src/include/utils/builtins.h
index 2ae212a..e800647 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -729,6 +729,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/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 708232d..2fec847 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -408,3 +408,29 @@ CREATE TABLE no_inh_con_parted (
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
 ERROR:  cannot add NO INHERIT constraint to partitioned table "no_inh_con_parted"
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+Table "public.describe_range_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition Key: RANGE ((a + b))
+
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+Table "public.describe_list_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition Key: LIST (a)
+
+DROP TABLE describe_range_key, describe_list_key;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 6e4a8be..4dd6a0a 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -402,3 +402,16 @@ CREATE TABLE no_inh_con_parted (
 	a int,
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
+
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+DROP TABLE describe_range_key, describe_list_key;
-- 
1.7.1


--------------4012470FC48539D6F6714CEA
Content-Type: text/x-diff;
 name="0003-Catalog-and-DDL-for-partitions-4.patch"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
 filename="0003-Catalog-and-DDL-for-partitions-4.patch"



^ permalink  raw  reply  [nested|flat] 110+ messages in thread

* [PATCH 2/9] psql and pg_dump support for partitioned tables.
@ 2016-07-12 08:20  amit <[email protected]>
  0 siblings, 0 replies; 110+ messages in thread

From: amit @ 2016-07-12 08:20 UTC (permalink / raw)

Takes care of both the partition key deparse stuff and the new relkind.
---
 src/backend/utils/adt/ruleutils.c          |  140 ++++++++++++++++++++++++++++
 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/pg_proc.h              |    2 +
 src/include/utils/builtins.h               |    1 +
 src/test/regress/expected/create_table.out |   26 +++++
 src/test/regress/sql/create_table.sql      |   13 +++
 9 files changed, 260 insertions(+), 26 deletions(-)

diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 8a81d7a..03be202 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_table.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
@@ -315,6 +316,7 @@ static char *pg_get_indexdef_worker(Oid indexrelid, int colno,
 					   const Oid *excludeOps,
 					   bool attrsOnly, bool showTblSpc,
 					   int prettyFlags, bool missing_ok);
+static char *pg_get_partkeydef_worker(Oid relid, int prettyFlags);
 static char *pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 							int prettyFlags, bool missing_ok);
 static text *pg_get_expr_worker(text *expr, Oid relid, const char *relname,
@@ -1389,6 +1391,144 @@ 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 [ opclass_name ] [, ...])
+ */
+Datum
+pg_get_partkeydef(PG_FUNCTION_ARGS)
+{
+	Oid			relid = PG_GETARG_OID(0);
+	int			prettyFlags;
+
+	prettyFlags = PRETTYFLAG_INDENT;
+	PG_RETURN_TEXT_P(string_to_text(pg_get_partkeydef_worker(relid,
+									prettyFlags)));
+}
+
+/*
+ * Internal workhorse to decompile a partition key definition.
+ */
+static char *
+pg_get_partkeydef_worker(Oid relid, int prettyFlags)
+{
+	Form_pg_partitioned_table	form;
+	HeapTuple	tuple;
+	oidvector  *partclass;
+	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_table) GETSTRUCT(tuple);
+
+	Assert(form->partrelid == relid);
+
+	/* Must get partclass, and partexprs the hard way */
+	datum = SysCacheGetAttr(PARTEDRELID, tuple,
+							Anum_pg_partitioned_table_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_table_partexprbin))
+	{
+		Datum		exprsDatum;
+		bool		isnull;
+		char	   *exprsString;
+
+		exprsDatum = SysCacheGetAttr(PARTEDRELID, tuple,
+									 Anum_pg_partitioned_table_partexprbin, &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);
+
+	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,
+											0, 0);
+
+			appendStringInfoString(&buf, 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/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index ba9c276..c805a84 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -1253,9 +1253,10 @@ expand_table_name_patterns(Archive *fout,
 						  "SELECT c.oid"
 						  "\nFROM pg_catalog.pg_class c"
 		"\n     LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace"
-					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c')\n",
+					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c')\n",
 						  RELKIND_RELATION, RELKIND_SEQUENCE, RELKIND_VIEW,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 		processSQLNamePattern(GetConnection(fout), query, cell->val, true,
 							  false, "n.nspname", "c.relname", NULL,
 							  "pg_catalog.pg_table_is_visible(c.oid)");
@@ -2125,6 +2126,9 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo, bool oids)
 	/* Skip FOREIGN TABLEs (no data to dump) */
 	if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 		return;
+	/* Skip partitioned tables (data in partitions) */
+	if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+		return;
 
 	/* Don't dump data in unlogged tables, if so requested */
 	if (tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED &&
@@ -5204,6 +5208,7 @@ getTables(Archive *fout, int *numTables)
 	int			i_reloftype;
 	int			i_relpages;
 	int			i_changed_acl;
+	int			i_partkeydef;
 
 	/* Make sure we are in proper schema */
 	selectSourceSchema(fout, "pg_catalog");
@@ -5289,7 +5294,8 @@ getTables(Archive *fout, int *numTables)
 						  "OR %s IS NOT NULL "
 						  "OR %s IS NOT NULL"
 						  "))"
-						  "AS changed_acl "
+						  "AS changed_acl, "
+						  "CASE WHEN c.relkind = 'P' THEN pg_catalog.pg_get_partkeydef(c.oid) ELSE NULL END AS partkeydef "
 						  "FROM pg_class c "
 						  "LEFT JOIN pg_depend d ON "
 						  "(c.relkind = '%c' AND "
@@ -5301,7 +5307,7 @@ getTables(Archive *fout, int *numTables)
 						  "(c.oid = pip.objoid "
 						  "AND pip.classoid = 'pg_class'::regclass "
 						  "AND pip.objsubid = 0) "
-				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c') "
+				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c', '%c') "
 						  "ORDER BY c.oid",
 						  acl_subquery->data,
 						  racl_subquery->data,
@@ -5315,7 +5321,8 @@ getTables(Archive *fout, int *numTables)
 						  RELKIND_SEQUENCE,
 						  RELKIND_RELATION, RELKIND_SEQUENCE,
 						  RELKIND_VIEW, RELKIND_COMPOSITE_TYPE,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 
 		destroyPQExpBuffer(acl_subquery);
 		destroyPQExpBuffer(racl_subquery);
@@ -5877,6 +5884,7 @@ getTables(Archive *fout, int *numTables)
 	i_toastreloptions = PQfnumber(res, "toast_reloptions");
 	i_reloftype = PQfnumber(res, "reloftype");
 	i_changed_acl = PQfnumber(res, "changed_acl");
+	i_partkeydef = PQfnumber(res, "partkeydef");
 
 	if (dopt->lockWaitTimeout && fout->remoteVersion >= 70300)
 	{
@@ -5947,6 +5955,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 */
 
@@ -5991,7 +6000,9 @@ getTables(Archive *fout, int *numTables)
 		 * We only need to lock the table for certain components; see
 		 * pg_dump.h
 		 */
-		if (tblinfo[i].dobj.dump && tblinfo[i].relkind == RELKIND_RELATION &&
+		if (tblinfo[i].dobj.dump &&
+			(tblinfo[i].relkind == RELKIND_RELATION ||
+			 tblinfo->relkind == RELKIND_PARTITIONED_TABLE) &&
 			(tblinfo[i].dobj.dump & DUMP_COMPONENTS_REQUIRING_LOCK))
 		{
 			resetPQExpBuffer(query);
@@ -6093,7 +6104,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);
 
@@ -15446,6 +15460,9 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 				appendPQExpBufferChar(q, ')');
 			}
 
+			if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+				appendPQExpBuffer(q, "\nPARTITION BY %s", tbinfo->partkeydef);
+
 			if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 				appendPQExpBuffer(q, "\nSERVER %s", fmtId(srvname));
 		}
@@ -15506,6 +15523,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 		 */
 		if (dopt->binary_upgrade &&
 			(tbinfo->relkind == RELKIND_RELATION ||
+			 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 			 tbinfo->relkind == RELKIND_FOREIGN_TABLE))
 		{
 			for (j = 0; j < tbinfo->numatts; j++)
@@ -15524,7 +15542,8 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 					appendStringLiteralAH(q, fmtId(tbinfo->dobj.name), fout);
 					appendPQExpBufferStr(q, "::pg_catalog.regclass;\n");
 
-					if (tbinfo->relkind == RELKIND_RELATION)
+					if (tbinfo->relkind == RELKIND_RELATION ||
+						tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
 						appendPQExpBuffer(q, "ALTER TABLE ONLY %s ",
 										  fmtId(tbinfo->dobj.name));
 					else
@@ -15741,6 +15760,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 	 * dump properties we only have ALTER TABLE syntax for
 	 */
 	if ((tbinfo->relkind == RELKIND_RELATION ||
+		 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 		 tbinfo->relkind == RELKIND_MATVIEW) &&
 		tbinfo->relreplident != REPLICA_IDENTITY_DEFAULT)
 	{
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 2bfa2d9..0292859 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -310,6 +310,7 @@ typedef struct _tableInfo
 	bool	   *inhNotNull;		/* true if NOT NULL is inherited */
 	struct _attrDefInfo **attrdefs;		/* DEFAULT expressions */
 	struct _constraintInfo *checkexprs; /* CHECK constraints */
+	char	   *partkeydef;		/* partition key definition */
 
 	/*
 	 * Stuff computed only for dumpable tables.
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 6275a68..10d924a 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -861,6 +861,7 @@ permissionsList(const char *pattern)
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'S' THEN '%s'"
@@ -870,6 +871,7 @@ permissionsList(const char *pattern)
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("sequence"),
@@ -920,7 +922,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
@@ -1567,8 +1569,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)");
 	}
 
@@ -1633,6 +1635,14 @@ describeOneTableDetails(const char *schemaname,
 			printfPQExpBuffer(&title, _("Foreign table \"%s.%s\""),
 							  schemaname, relationname);
 			break;
+		case 'P':
+			if (tableinfo.relpersistence == 'u')
+				printfPQExpBuffer(&title, _("Unlogged table \"%s.%s\""),
+								  schemaname, relationname);
+			else
+				printfPQExpBuffer(&title, _("Table \"%s.%s\""),
+								  schemaname, relationname);
+			break;
 		default:
 			/* untranslated unknown relkind */
 			printfPQExpBuffer(&title, "?%c? \"%s.%s\"",
@@ -1646,8 +1656,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");
@@ -1667,12 +1677,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");
 	}
 
@@ -1772,7 +1782,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);
@@ -1780,14 +1790,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 (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 */
@@ -1926,7 +1955,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;
@@ -2485,7 +2514,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;
@@ -2696,7 +2725,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
@@ -3024,6 +3053,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'i' THEN '%s'"
@@ -3035,6 +3065,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("index"),
@@ -3079,7 +3110,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 50a45eb..8284a9c 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/pg_proc.h b/src/include/catalog/pg_proc.h
index e2d08ba..b45688b 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -1980,6 +1980,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/utils/builtins.h b/src/include/utils/builtins.h
index 2ae212a..e800647 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -729,6 +729,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/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 0af1422..91ba1be 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -412,3 +412,29 @@ CREATE TABLE no_inh_con_parted (
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
 ERROR:  cannot add NO INHERIT constraint to partitioned table "no_inh_con_parted"
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+Table "public.describe_range_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition Key: RANGE ((a + b))
+
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+Table "public.describe_list_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition Key: LIST (a)
+
+DROP TABLE describe_range_key, describe_list_key;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 418ac55..b75542d 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -406,3 +406,16 @@ CREATE TABLE no_inh_con_parted (
 	a int,
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
+
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+DROP TABLE describe_range_key, describe_list_key;
-- 
1.7.1


--------------D008BA677674C4836D7B65AD
Content-Type: text/x-diff;
 name="0003-Catalog-and-DDL-for-partitions-5.patch"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
 filename="0003-Catalog-and-DDL-for-partitions-5.patch"



^ permalink  raw  reply  [nested|flat] 110+ messages in thread

* [PATCH 2/9] psql and pg_dump support for partitioned tables.
@ 2016-07-12 08:20  amit <[email protected]>
  0 siblings, 0 replies; 110+ messages in thread

From: amit @ 2016-07-12 08:20 UTC (permalink / raw)

Takes care of both the partition key deparse stuff and the new relkind.
---
 src/backend/utils/adt/ruleutils.c          |  140 ++++++++++++++++++++++++++++
 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/pg_proc.h              |    2 +
 src/include/utils/builtins.h               |    1 +
 src/test/regress/expected/create_table.out |   26 +++++
 src/test/regress/sql/create_table.sql      |   13 +++
 9 files changed, 260 insertions(+), 26 deletions(-)

diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 8a81d7a..03be202 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_table.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
@@ -315,6 +316,7 @@ static char *pg_get_indexdef_worker(Oid indexrelid, int colno,
 					   const Oid *excludeOps,
 					   bool attrsOnly, bool showTblSpc,
 					   int prettyFlags, bool missing_ok);
+static char *pg_get_partkeydef_worker(Oid relid, int prettyFlags);
 static char *pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 							int prettyFlags, bool missing_ok);
 static text *pg_get_expr_worker(text *expr, Oid relid, const char *relname,
@@ -1389,6 +1391,144 @@ 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 [ opclass_name ] [, ...])
+ */
+Datum
+pg_get_partkeydef(PG_FUNCTION_ARGS)
+{
+	Oid			relid = PG_GETARG_OID(0);
+	int			prettyFlags;
+
+	prettyFlags = PRETTYFLAG_INDENT;
+	PG_RETURN_TEXT_P(string_to_text(pg_get_partkeydef_worker(relid,
+									prettyFlags)));
+}
+
+/*
+ * Internal workhorse to decompile a partition key definition.
+ */
+static char *
+pg_get_partkeydef_worker(Oid relid, int prettyFlags)
+{
+	Form_pg_partitioned_table	form;
+	HeapTuple	tuple;
+	oidvector  *partclass;
+	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_table) GETSTRUCT(tuple);
+
+	Assert(form->partrelid == relid);
+
+	/* Must get partclass, and partexprs the hard way */
+	datum = SysCacheGetAttr(PARTEDRELID, tuple,
+							Anum_pg_partitioned_table_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_table_partexprbin))
+	{
+		Datum		exprsDatum;
+		bool		isnull;
+		char	   *exprsString;
+
+		exprsDatum = SysCacheGetAttr(PARTEDRELID, tuple,
+									 Anum_pg_partitioned_table_partexprbin, &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);
+
+	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,
+											0, 0);
+
+			appendStringInfoString(&buf, 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/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 51b8a1a..854dddc 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -1253,9 +1253,10 @@ expand_table_name_patterns(Archive *fout,
 						  "SELECT c.oid"
 						  "\nFROM pg_catalog.pg_class c"
 		"\n     LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace"
-					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c')\n",
+					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c')\n",
 						  RELKIND_RELATION, RELKIND_SEQUENCE, RELKIND_VIEW,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 		processSQLNamePattern(GetConnection(fout), query, cell->val, true,
 							  false, "n.nspname", "c.relname", NULL,
 							  "pg_catalog.pg_table_is_visible(c.oid)");
@@ -2125,6 +2126,9 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo, bool oids)
 	/* Skip FOREIGN TABLEs (no data to dump) */
 	if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 		return;
+	/* Skip partitioned tables (data in partitions) */
+	if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+		return;
 
 	/* Don't dump data in unlogged tables, if so requested */
 	if (tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED &&
@@ -5211,6 +5215,7 @@ getTables(Archive *fout, int *numTables)
 	int			i_reloftype;
 	int			i_relpages;
 	int			i_changed_acl;
+	int			i_partkeydef;
 
 	/* Make sure we are in proper schema */
 	selectSourceSchema(fout, "pg_catalog");
@@ -5296,7 +5301,8 @@ getTables(Archive *fout, int *numTables)
 						  "OR %s IS NOT NULL "
 						  "OR %s IS NOT NULL"
 						  "))"
-						  "AS changed_acl "
+						  "AS changed_acl, "
+						  "CASE WHEN c.relkind = 'P' THEN pg_catalog.pg_get_partkeydef(c.oid) ELSE NULL END AS partkeydef "
 						  "FROM pg_class c "
 						  "LEFT JOIN pg_depend d ON "
 						  "(c.relkind = '%c' AND "
@@ -5308,7 +5314,7 @@ getTables(Archive *fout, int *numTables)
 						  "(c.oid = pip.objoid "
 						  "AND pip.classoid = 'pg_class'::regclass "
 						  "AND pip.objsubid = 0) "
-				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c') "
+				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c', '%c') "
 						  "ORDER BY c.oid",
 						  acl_subquery->data,
 						  racl_subquery->data,
@@ -5322,7 +5328,8 @@ getTables(Archive *fout, int *numTables)
 						  RELKIND_SEQUENCE,
 						  RELKIND_RELATION, RELKIND_SEQUENCE,
 						  RELKIND_VIEW, RELKIND_COMPOSITE_TYPE,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 
 		destroyPQExpBuffer(acl_subquery);
 		destroyPQExpBuffer(racl_subquery);
@@ -5884,6 +5891,7 @@ getTables(Archive *fout, int *numTables)
 	i_toastreloptions = PQfnumber(res, "toast_reloptions");
 	i_reloftype = PQfnumber(res, "reloftype");
 	i_changed_acl = PQfnumber(res, "changed_acl");
+	i_partkeydef = PQfnumber(res, "partkeydef");
 
 	if (dopt->lockWaitTimeout && fout->remoteVersion >= 70300)
 	{
@@ -5954,6 +5962,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 */
 
@@ -5998,7 +6007,9 @@ getTables(Archive *fout, int *numTables)
 		 * We only need to lock the table for certain components; see
 		 * pg_dump.h
 		 */
-		if (tblinfo[i].dobj.dump && tblinfo[i].relkind == RELKIND_RELATION &&
+		if (tblinfo[i].dobj.dump &&
+			(tblinfo[i].relkind == RELKIND_RELATION ||
+			 tblinfo->relkind == RELKIND_PARTITIONED_TABLE) &&
 			(tblinfo[i].dobj.dump & DUMP_COMPONENTS_REQUIRING_LOCK))
 		{
 			resetPQExpBuffer(query);
@@ -6100,7 +6111,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);
 
@@ -15453,6 +15467,9 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 				appendPQExpBufferChar(q, ')');
 			}
 
+			if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+				appendPQExpBuffer(q, "\nPARTITION BY %s", tbinfo->partkeydef);
+
 			if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 				appendPQExpBuffer(q, "\nSERVER %s", fmtId(srvname));
 		}
@@ -15513,6 +15530,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 		 */
 		if (dopt->binary_upgrade &&
 			(tbinfo->relkind == RELKIND_RELATION ||
+			 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 			 tbinfo->relkind == RELKIND_FOREIGN_TABLE))
 		{
 			for (j = 0; j < tbinfo->numatts; j++)
@@ -15531,7 +15549,8 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 					appendStringLiteralAH(q, fmtId(tbinfo->dobj.name), fout);
 					appendPQExpBufferStr(q, "::pg_catalog.regclass;\n");
 
-					if (tbinfo->relkind == RELKIND_RELATION)
+					if (tbinfo->relkind == RELKIND_RELATION ||
+						tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
 						appendPQExpBuffer(q, "ALTER TABLE ONLY %s ",
 										  fmtId(tbinfo->dobj.name));
 					else
@@ -15748,6 +15767,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 	 * dump properties we only have ALTER TABLE syntax for
 	 */
 	if ((tbinfo->relkind == RELKIND_RELATION ||
+		 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 		 tbinfo->relkind == RELKIND_MATVIEW) &&
 		tbinfo->relreplident != REPLICA_IDENTITY_DEFAULT)
 	{
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 2bfa2d9..0292859 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -310,6 +310,7 @@ typedef struct _tableInfo
 	bool	   *inhNotNull;		/* true if NOT NULL is inherited */
 	struct _attrDefInfo **attrdefs;		/* DEFAULT expressions */
 	struct _constraintInfo *checkexprs; /* CHECK constraints */
+	char	   *partkeydef;		/* partition key definition */
 
 	/*
 	 * Stuff computed only for dumpable tables.
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 6275a68..db6dc5c 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -861,6 +861,7 @@ permissionsList(const char *pattern)
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'S' THEN '%s'"
@@ -870,6 +871,7 @@ permissionsList(const char *pattern)
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("sequence"),
@@ -920,7 +922,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
@@ -1567,8 +1569,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)");
 	}
 
@@ -1633,6 +1635,14 @@ describeOneTableDetails(const char *schemaname,
 			printfPQExpBuffer(&title, _("Foreign table \"%s.%s\""),
 							  schemaname, relationname);
 			break;
+		case 'P':
+			if (tableinfo.relpersistence == 'u')
+				printfPQExpBuffer(&title, _("Unlogged table \"%s.%s\""),
+								  schemaname, relationname);
+			else
+				printfPQExpBuffer(&title, _("Table \"%s.%s\""),
+								  schemaname, relationname);
+			break;
 		default:
 			/* untranslated unknown relkind */
 			printfPQExpBuffer(&title, "?%c? \"%s.%s\"",
@@ -1646,8 +1656,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");
@@ -1667,12 +1677,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");
 	}
 
@@ -1772,7 +1782,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);
@@ -1780,14 +1790,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 (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 */
@@ -1926,7 +1955,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;
@@ -2485,7 +2514,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;
@@ -2696,7 +2725,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
@@ -3024,6 +3053,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'i' THEN '%s'"
@@ -3035,6 +3065,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("index"),
@@ -3079,7 +3110,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 50a45eb..8284a9c 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/pg_proc.h b/src/include/catalog/pg_proc.h
index e2d08ba..b45688b 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -1980,6 +1980,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/utils/builtins.h b/src/include/utils/builtins.h
index 2ae212a..e800647 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -729,6 +729,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/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 1394362..260ec34 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -407,3 +407,29 @@ CREATE TABLE no_inh_con_parted (
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
 ERROR:  cannot add NO INHERIT constraint to partitioned table "no_inh_con_parted"
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+Table "public.describe_range_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition key: RANGE ((a + b))
+
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+Table "public.describe_list_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition key: LIST (a)
+
+DROP TABLE describe_range_key, describe_list_key;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index d383f58..7a1f8de 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -401,3 +401,16 @@ CREATE TABLE no_inh_con_parted (
 	a int,
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
+
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+DROP TABLE describe_range_key, describe_list_key;
-- 
1.7.1


--------------C3235EE96C69D8DAE4DB543E
Content-Type: text/x-diff;
 name="0003-Catalog-and-DDL-for-partitions-6.patch"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
 filename="0003-Catalog-and-DDL-for-partitions-6.patch"



^ permalink  raw  reply  [nested|flat] 110+ messages in thread

* [PATCH 2/8] psql and pg_dump support for partitioned tables.
@ 2016-07-12 08:20  amit <[email protected]>
  0 siblings, 0 replies; 110+ messages in thread

From: amit @ 2016-07-12 08:20 UTC (permalink / raw)

Takes care of both the partition key deparse stuff and the new relkind.
---
 src/backend/utils/adt/ruleutils.c          |  147 ++++++++++++++++++++++++++++
 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/pg_proc.h              |    2 +
 src/include/utils/builtins.h               |    1 +
 src/test/regress/expected/create_table.out |   26 +++++
 src/test/regress/sql/create_table.sql      |   13 +++
 9 files changed, 267 insertions(+), 26 deletions(-)

diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index ec966c7..4691b5b 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, bool missing_ok);
+static char *pg_get_partkeydef_worker(Oid relId, int prettyFlags);
 static char *pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 							int prettyFlags, bool missing_ok);
 static text *pg_get_expr_worker(text *expr, Oid relid, const char *relname,
@@ -1389,6 +1391,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 [ 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/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 4ee10fc..a127672 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -1253,9 +1253,10 @@ expand_table_name_patterns(Archive *fout,
 						  "SELECT c.oid"
 						  "\nFROM pg_catalog.pg_class c"
 		"\n     LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace"
-					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c')\n",
+					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c')\n",
 						  RELKIND_RELATION, RELKIND_SEQUENCE, RELKIND_VIEW,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_REL);
 		processSQLNamePattern(GetConnection(fout), query, cell->val, true,
 							  false, "n.nspname", "c.relname", NULL,
 							  "pg_catalog.pg_table_is_visible(c.oid)");
@@ -2117,6 +2118,9 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo, bool oids)
 	/* Skip FOREIGN TABLEs (no data to dump) */
 	if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 		return;
+	/* Skip partitioned tables (data in partitions) */
+	if (tbinfo->relkind == RELKIND_PARTITIONED_REL)
+		return;
 
 	/* Don't dump data in unlogged tables, if so requested */
 	if (tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED &&
@@ -5195,6 +5199,7 @@ getTables(Archive *fout, int *numTables)
 	int			i_reloftype;
 	int			i_relpages;
 	int			i_changed_acl;
+	int			i_partkeydef;
 
 	/* Make sure we are in proper schema */
 	selectSourceSchema(fout, "pg_catalog");
@@ -5280,7 +5285,8 @@ getTables(Archive *fout, int *numTables)
 						  "OR %s IS NOT NULL "
 						  "OR %s IS NOT NULL"
 						  "))"
-						  "AS changed_acl "
+						  "AS changed_acl, "
+						  "CASE WHEN c.relkind = 'P' THEN pg_catalog.pg_get_partkeydef(c.oid) ELSE NULL END AS partkeydef "
 						  "FROM pg_class c "
 						  "LEFT JOIN pg_depend d ON "
 						  "(c.relkind = '%c' AND "
@@ -5292,7 +5298,7 @@ getTables(Archive *fout, int *numTables)
 						  "(c.oid = pip.objoid "
 						  "AND pip.classoid = 'pg_class'::regclass "
 						  "AND pip.objsubid = 0) "
-				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c') "
+				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c', '%c') "
 						  "ORDER BY c.oid",
 						  acl_subquery->data,
 						  racl_subquery->data,
@@ -5306,7 +5312,8 @@ getTables(Archive *fout, int *numTables)
 						  RELKIND_SEQUENCE,
 						  RELKIND_RELATION, RELKIND_SEQUENCE,
 						  RELKIND_VIEW, RELKIND_COMPOSITE_TYPE,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_REL);
 
 		destroyPQExpBuffer(acl_subquery);
 		destroyPQExpBuffer(racl_subquery);
@@ -5868,6 +5875,7 @@ getTables(Archive *fout, int *numTables)
 	i_toastreloptions = PQfnumber(res, "toast_reloptions");
 	i_reloftype = PQfnumber(res, "reloftype");
 	i_changed_acl = PQfnumber(res, "changed_acl");
+	i_partkeydef = PQfnumber(res, "partkeydef");
 
 	if (dopt->lockWaitTimeout && fout->remoteVersion >= 70300)
 	{
@@ -5938,6 +5946,7 @@ getTables(Archive *fout, int *numTables)
 		else
 			tblinfo[i].checkoption = pg_strdup(PQgetvalue(res, i, i_checkoption));
 		tblinfo[i].toast_reloptions = pg_strdup(PQgetvalue(res, i, i_toastreloptions));
+		tblinfo[i].partkeydef = pg_strdup(PQgetvalue(res, i, i_partkeydef));
 
 		/* other fields were zeroed above */
 
@@ -5982,7 +5991,9 @@ getTables(Archive *fout, int *numTables)
 		 * We only need to lock the table for certain components; see
 		 * pg_dump.h
 		 */
-		if (tblinfo[i].dobj.dump && tblinfo[i].relkind == RELKIND_RELATION &&
+		if (tblinfo[i].dobj.dump &&
+			(tblinfo[i].relkind == RELKIND_RELATION ||
+			 tblinfo->relkind == RELKIND_PARTITIONED_REL) &&
 			(tblinfo[i].dobj.dump & DUMP_COMPONENTS_REQUIRING_LOCK))
 		{
 			resetPQExpBuffer(query);
@@ -6083,7 +6094,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);
 
@@ -15436,6 +15450,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));
 		}
@@ -15496,6 +15513,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++)
@@ -15514,7 +15532,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
@@ -15731,6 +15750,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 	 * dump properties we only have ALTER TABLE syntax for
 	 */
 	if ((tbinfo->relkind == RELKIND_RELATION ||
+		 tbinfo->relkind == RELKIND_PARTITIONED_REL ||
 		 tbinfo->relkind == RELKIND_MATVIEW) &&
 		tbinfo->relreplident != REPLICA_IDENTITY_DEFAULT)
 	{
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 2bfa2d9..0292859 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -310,6 +310,7 @@ typedef struct _tableInfo
 	bool	   *inhNotNull;		/* true if NOT NULL is inherited */
 	struct _attrDefInfo **attrdefs;		/* DEFAULT expressions */
 	struct _constraintInfo *checkexprs; /* CHECK constraints */
+	char	   *partkeydef;		/* partition key definition */
 
 	/*
 	 * Stuff computed only for dumpable tables.
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 27be102..3ba8a90 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -856,6 +856,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"),
@@ -865,6 +866,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");
@@ -911,7 +913,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
@@ -1555,8 +1557,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)");
 	}
 
@@ -1621,6 +1623,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\"",
@@ -1634,8 +1644,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");
@@ -1655,12 +1665,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");
 	}
 
@@ -1760,7 +1770,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);
@@ -1768,14 +1778,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 */
@@ -1914,7 +1943,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;
@@ -2473,7 +2502,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;
@@ -2684,7 +2713,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
@@ -3018,6 +3047,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"),
@@ -3029,6 +3059,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"));
 
@@ -3067,7 +3098,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 
 	appendPQExpBufferStr(&buf, "\nWHERE c.relkind IN (");
 	if (showTables)
-		appendPQExpBufferStr(&buf, "'r',");
+		appendPQExpBufferStr(&buf, "'r', 'P',");
 	if (showViews)
 		appendPQExpBufferStr(&buf, "'v',");
 	if (showMatViews)
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 8469d9f..cd4eb7c 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -427,7 +427,7 @@ static const SchemaQuery Query_for_list_of_tables = {
 	/* catname */
 	"pg_catalog.pg_class c",
 	/* selcondition */
-	"c.relkind IN ('r')",
+	"c.relkind IN ('r', 'P')",
 	/* viscondition */
 	"pg_catalog.pg_table_is_visible(c.oid)",
 	/* namespace */
@@ -458,7 +458,7 @@ static const SchemaQuery Query_for_list_of_updatables = {
 	/* catname */
 	"pg_catalog.pg_class c",
 	/* selcondition */
-	"c.relkind IN ('r', 'f', 'v')",
+	"c.relkind IN ('r', 'f', 'v', 'P')",
 	/* viscondition */
 	"pg_catalog.pg_table_is_visible(c.oid)",
 	/* namespace */
@@ -488,7 +488,7 @@ static const SchemaQuery Query_for_list_of_tsvmf = {
 	/* catname */
 	"pg_catalog.pg_class c",
 	/* selcondition */
-	"c.relkind IN ('r', 'S', 'v', 'm', 'f')",
+	"c.relkind IN ('r', 'S', 'v', 'm', 'f', 'P')",
 	/* viscondition */
 	"pg_catalog.pg_table_is_visible(c.oid)",
 	/* namespace */
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index 270dd21..5a444d9 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/utils/builtins.h b/src/include/utils/builtins.h
index 8cebc86..351bfd3 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -722,6 +722,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/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index fb7a1b3..4fc8c06 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -413,3 +413,29 @@ SELECT connoinherit FROM pg_constraint WHERE conrelid = 'no_inh_con_parted'::reg
 (1 row)
 
 DROP TABLE no_inh_con_parted;
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+Partitioned table "public.describe_range_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition Key: PARTITION BY RANGE (((a + b)))
+
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+Partitioned table "public.describe_list_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition Key: PARTITION BY LIST (a)
+
+DROP TABLE describe_range_key, describe_list_key;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index cb17517..7c7a18a 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -404,3 +404,16 @@ CREATE TABLE no_inh_con_parted (
 ) PARTITION BY RANGE (a);
 SELECT connoinherit FROM pg_constraint WHERE conrelid = 'no_inh_con_parted'::regclass;
 DROP TABLE no_inh_con_parted;
+
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+DROP TABLE describe_range_key, describe_list_key;
-- 
1.7.1


--------------8C4C48E640631A327D3D2826
Content-Type: text/x-diff;
 name="0003-Catalog-and-DDL-for-partitions.patch"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
 filename="0003-Catalog-and-DDL-for-partitions.patch"



^ permalink  raw  reply  [nested|flat] 110+ messages in thread

* [PATCH 2/9] psql and pg_dump support for partitioned tables.
@ 2016-07-12 08:20  amit <[email protected]>
  0 siblings, 0 replies; 110+ messages in thread

From: amit @ 2016-07-12 08:20 UTC (permalink / raw)

Takes care of both the partition key deparse stuff and the new relkind.
---
 src/backend/utils/adt/ruleutils.c          |  140 ++++++++++++++++++++++++++++
 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/pg_proc.h              |    2 +
 src/include/utils/builtins.h               |    1 +
 src/test/regress/expected/create_table.out |   26 +++++
 src/test/regress/sql/create_table.sql      |   13 +++
 9 files changed, 260 insertions(+), 26 deletions(-)

diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 8a81d7a..77ce807 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_table.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
@@ -315,6 +316,7 @@ static char *pg_get_indexdef_worker(Oid indexrelid, int colno,
 					   const Oid *excludeOps,
 					   bool attrsOnly, bool showTblSpc,
 					   int prettyFlags, bool missing_ok);
+static char *pg_get_partkeydef_worker(Oid relId, int prettyFlags);
 static char *pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 							int prettyFlags, bool missing_ok);
 static text *pg_get_expr_worker(text *expr, Oid relid, const char *relname,
@@ -1389,6 +1391,144 @@ 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 [ 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_table	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_table) GETSTRUCT(tuple);
+
+	Assert(form->partedrelid == relId);
+
+	/* Must get partclass, and partexprs the hard way */
+	datum = SysCacheGetAttr(PARTEDRELID, tuple,
+							Anum_pg_partitioned_table_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_table_partexprbin))
+	{
+		Datum		exprsDatum;
+		bool		isnull;
+		char	   *exprsString;
+
+		exprsDatum = SysCacheGetAttr(PARTEDRELID, tuple,
+									 Anum_pg_partitioned_table_partexprbin, &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);
+
+	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,
+											0, 0);
+
+			appendStringInfoString(&buf, 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/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index a5c2d09..c418ba7 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -1253,9 +1253,10 @@ expand_table_name_patterns(Archive *fout,
 						  "SELECT c.oid"
 						  "\nFROM pg_catalog.pg_class c"
 		"\n     LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace"
-					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c')\n",
+					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c')\n",
 						  RELKIND_RELATION, RELKIND_SEQUENCE, RELKIND_VIEW,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 		processSQLNamePattern(GetConnection(fout), query, cell->val, true,
 							  false, "n.nspname", "c.relname", NULL,
 							  "pg_catalog.pg_table_is_visible(c.oid)");
@@ -2117,6 +2118,9 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo, bool oids)
 	/* Skip FOREIGN TABLEs (no data to dump) */
 	if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 		return;
+	/* Skip partitioned tables (data in partitions) */
+	if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+		return;
 
 	/* Don't dump data in unlogged tables, if so requested */
 	if (tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED &&
@@ -5195,6 +5199,7 @@ getTables(Archive *fout, int *numTables)
 	int			i_reloftype;
 	int			i_relpages;
 	int			i_changed_acl;
+	int			i_partkeydef;
 
 	/* Make sure we are in proper schema */
 	selectSourceSchema(fout, "pg_catalog");
@@ -5280,7 +5285,8 @@ getTables(Archive *fout, int *numTables)
 						  "OR %s IS NOT NULL "
 						  "OR %s IS NOT NULL"
 						  "))"
-						  "AS changed_acl "
+						  "AS changed_acl, "
+						  "CASE WHEN c.relkind = 'P' THEN pg_catalog.pg_get_partkeydef(c.oid) ELSE NULL END AS partkeydef "
 						  "FROM pg_class c "
 						  "LEFT JOIN pg_depend d ON "
 						  "(c.relkind = '%c' AND "
@@ -5292,7 +5298,7 @@ getTables(Archive *fout, int *numTables)
 						  "(c.oid = pip.objoid "
 						  "AND pip.classoid = 'pg_class'::regclass "
 						  "AND pip.objsubid = 0) "
-				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c') "
+				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c', '%c') "
 						  "ORDER BY c.oid",
 						  acl_subquery->data,
 						  racl_subquery->data,
@@ -5306,7 +5312,8 @@ getTables(Archive *fout, int *numTables)
 						  RELKIND_SEQUENCE,
 						  RELKIND_RELATION, RELKIND_SEQUENCE,
 						  RELKIND_VIEW, RELKIND_COMPOSITE_TYPE,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 
 		destroyPQExpBuffer(acl_subquery);
 		destroyPQExpBuffer(racl_subquery);
@@ -5868,6 +5875,7 @@ getTables(Archive *fout, int *numTables)
 	i_toastreloptions = PQfnumber(res, "toast_reloptions");
 	i_reloftype = PQfnumber(res, "reloftype");
 	i_changed_acl = PQfnumber(res, "changed_acl");
+	i_partkeydef = PQfnumber(res, "partkeydef");
 
 	if (dopt->lockWaitTimeout && fout->remoteVersion >= 70300)
 	{
@@ -5938,6 +5946,7 @@ getTables(Archive *fout, int *numTables)
 		else
 			tblinfo[i].checkoption = pg_strdup(PQgetvalue(res, i, i_checkoption));
 		tblinfo[i].toast_reloptions = pg_strdup(PQgetvalue(res, i, i_toastreloptions));
+		tblinfo[i].partkeydef = pg_strdup(PQgetvalue(res, i, i_partkeydef));
 
 		/* other fields were zeroed above */
 
@@ -5982,7 +5991,9 @@ getTables(Archive *fout, int *numTables)
 		 * We only need to lock the table for certain components; see
 		 * pg_dump.h
 		 */
-		if (tblinfo[i].dobj.dump && tblinfo[i].relkind == RELKIND_RELATION &&
+		if (tblinfo[i].dobj.dump &&
+			(tblinfo[i].relkind == RELKIND_RELATION ||
+			 tblinfo->relkind == RELKIND_PARTITIONED_TABLE) &&
 			(tblinfo[i].dobj.dump & DUMP_COMPONENTS_REQUIRING_LOCK))
 		{
 			resetPQExpBuffer(query);
@@ -6084,7 +6095,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);
 
@@ -15437,6 +15451,9 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 				appendPQExpBufferChar(q, ')');
 			}
 
+			if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+				appendPQExpBuffer(q, "\nPARTITION BY %s", tbinfo->partkeydef);
+
 			if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 				appendPQExpBuffer(q, "\nSERVER %s", fmtId(srvname));
 		}
@@ -15497,6 +15514,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 		 */
 		if (dopt->binary_upgrade &&
 			(tbinfo->relkind == RELKIND_RELATION ||
+			 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 			 tbinfo->relkind == RELKIND_FOREIGN_TABLE))
 		{
 			for (j = 0; j < tbinfo->numatts; j++)
@@ -15515,7 +15533,8 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 					appendStringLiteralAH(q, fmtId(tbinfo->dobj.name), fout);
 					appendPQExpBufferStr(q, "::pg_catalog.regclass;\n");
 
-					if (tbinfo->relkind == RELKIND_RELATION)
+					if (tbinfo->relkind == RELKIND_RELATION ||
+						tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
 						appendPQExpBuffer(q, "ALTER TABLE ONLY %s ",
 										  fmtId(tbinfo->dobj.name));
 					else
@@ -15732,6 +15751,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 	 * dump properties we only have ALTER TABLE syntax for
 	 */
 	if ((tbinfo->relkind == RELKIND_RELATION ||
+		 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 		 tbinfo->relkind == RELKIND_MATVIEW) &&
 		tbinfo->relreplident != REPLICA_IDENTITY_DEFAULT)
 	{
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 2bfa2d9..0292859 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -310,6 +310,7 @@ typedef struct _tableInfo
 	bool	   *inhNotNull;		/* true if NOT NULL is inherited */
 	struct _attrDefInfo **attrdefs;		/* DEFAULT expressions */
 	struct _constraintInfo *checkexprs; /* CHECK constraints */
+	char	   *partkeydef;		/* partition key definition */
 
 	/*
 	 * Stuff computed only for dumpable tables.
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 6275a68..10d924a 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -861,6 +861,7 @@ permissionsList(const char *pattern)
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'S' THEN '%s'"
@@ -870,6 +871,7 @@ permissionsList(const char *pattern)
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("sequence"),
@@ -920,7 +922,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
@@ -1567,8 +1569,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)");
 	}
 
@@ -1633,6 +1635,14 @@ describeOneTableDetails(const char *schemaname,
 			printfPQExpBuffer(&title, _("Foreign table \"%s.%s\""),
 							  schemaname, relationname);
 			break;
+		case 'P':
+			if (tableinfo.relpersistence == 'u')
+				printfPQExpBuffer(&title, _("Unlogged table \"%s.%s\""),
+								  schemaname, relationname);
+			else
+				printfPQExpBuffer(&title, _("Table \"%s.%s\""),
+								  schemaname, relationname);
+			break;
 		default:
 			/* untranslated unknown relkind */
 			printfPQExpBuffer(&title, "?%c? \"%s.%s\"",
@@ -1646,8 +1656,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");
@@ -1667,12 +1677,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");
 	}
 
@@ -1772,7 +1782,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);
@@ -1780,14 +1790,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 (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 */
@@ -1926,7 +1955,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;
@@ -2485,7 +2514,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;
@@ -2696,7 +2725,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
@@ -3024,6 +3053,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'i' THEN '%s'"
@@ -3035,6 +3065,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("index"),
@@ -3079,7 +3110,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 1345e4e..4686cd1 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/pg_proc.h b/src/include/catalog/pg_proc.h
index e2d08ba..b45688b 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -1980,6 +1980,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/utils/builtins.h b/src/include/utils/builtins.h
index 2ae212a..e800647 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -729,6 +729,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/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 708232d..2fec847 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -408,3 +408,29 @@ CREATE TABLE no_inh_con_parted (
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
 ERROR:  cannot add NO INHERIT constraint to partitioned table "no_inh_con_parted"
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+Table "public.describe_range_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition Key: RANGE ((a + b))
+
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+Table "public.describe_list_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition Key: LIST (a)
+
+DROP TABLE describe_range_key, describe_list_key;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 6e4a8be..4dd6a0a 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -402,3 +402,16 @@ CREATE TABLE no_inh_con_parted (
 	a int,
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
+
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+DROP TABLE describe_range_key, describe_list_key;
-- 
1.7.1


--------------954458C17BBEB1BF6FC758E7
Content-Type: text/x-diff;
 name="0003-Catalog-and-DDL-for-partitions-2.patch"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
 filename="0003-Catalog-and-DDL-for-partitions-2.patch"



^ permalink  raw  reply  [nested|flat] 110+ messages in thread

* [PATCH 2/9] psql and pg_dump support for partitioned tables.
@ 2016-07-12 08:20  amit <[email protected]>
  0 siblings, 0 replies; 110+ messages in thread

From: amit @ 2016-07-12 08:20 UTC (permalink / raw)

Takes care of both the partition key deparse stuff and the new relkind.
---
 src/backend/utils/adt/ruleutils.c          |  140 ++++++++++++++++++++++++++++
 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/pg_proc.h              |    2 +
 src/include/utils/builtins.h               |    1 +
 src/test/regress/expected/create_table.out |   26 +++++
 src/test/regress/sql/create_table.sql      |   13 +++
 9 files changed, 260 insertions(+), 26 deletions(-)

diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 8a81d7a..77ce807 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_table.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
@@ -315,6 +316,7 @@ static char *pg_get_indexdef_worker(Oid indexrelid, int colno,
 					   const Oid *excludeOps,
 					   bool attrsOnly, bool showTblSpc,
 					   int prettyFlags, bool missing_ok);
+static char *pg_get_partkeydef_worker(Oid relId, int prettyFlags);
 static char *pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 							int prettyFlags, bool missing_ok);
 static text *pg_get_expr_worker(text *expr, Oid relid, const char *relname,
@@ -1389,6 +1391,144 @@ 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 [ 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_table	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_table) GETSTRUCT(tuple);
+
+	Assert(form->partedrelid == relId);
+
+	/* Must get partclass, and partexprs the hard way */
+	datum = SysCacheGetAttr(PARTEDRELID, tuple,
+							Anum_pg_partitioned_table_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_table_partexprbin))
+	{
+		Datum		exprsDatum;
+		bool		isnull;
+		char	   *exprsString;
+
+		exprsDatum = SysCacheGetAttr(PARTEDRELID, tuple,
+									 Anum_pg_partitioned_table_partexprbin, &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);
+
+	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,
+											0, 0);
+
+			appendStringInfoString(&buf, 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/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index a5c2d09..c418ba7 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -1253,9 +1253,10 @@ expand_table_name_patterns(Archive *fout,
 						  "SELECT c.oid"
 						  "\nFROM pg_catalog.pg_class c"
 		"\n     LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace"
-					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c')\n",
+					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c')\n",
 						  RELKIND_RELATION, RELKIND_SEQUENCE, RELKIND_VIEW,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 		processSQLNamePattern(GetConnection(fout), query, cell->val, true,
 							  false, "n.nspname", "c.relname", NULL,
 							  "pg_catalog.pg_table_is_visible(c.oid)");
@@ -2117,6 +2118,9 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo, bool oids)
 	/* Skip FOREIGN TABLEs (no data to dump) */
 	if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 		return;
+	/* Skip partitioned tables (data in partitions) */
+	if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+		return;
 
 	/* Don't dump data in unlogged tables, if so requested */
 	if (tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED &&
@@ -5195,6 +5199,7 @@ getTables(Archive *fout, int *numTables)
 	int			i_reloftype;
 	int			i_relpages;
 	int			i_changed_acl;
+	int			i_partkeydef;
 
 	/* Make sure we are in proper schema */
 	selectSourceSchema(fout, "pg_catalog");
@@ -5280,7 +5285,8 @@ getTables(Archive *fout, int *numTables)
 						  "OR %s IS NOT NULL "
 						  "OR %s IS NOT NULL"
 						  "))"
-						  "AS changed_acl "
+						  "AS changed_acl, "
+						  "CASE WHEN c.relkind = 'P' THEN pg_catalog.pg_get_partkeydef(c.oid) ELSE NULL END AS partkeydef "
 						  "FROM pg_class c "
 						  "LEFT JOIN pg_depend d ON "
 						  "(c.relkind = '%c' AND "
@@ -5292,7 +5298,7 @@ getTables(Archive *fout, int *numTables)
 						  "(c.oid = pip.objoid "
 						  "AND pip.classoid = 'pg_class'::regclass "
 						  "AND pip.objsubid = 0) "
-				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c') "
+				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c', '%c') "
 						  "ORDER BY c.oid",
 						  acl_subquery->data,
 						  racl_subquery->data,
@@ -5306,7 +5312,8 @@ getTables(Archive *fout, int *numTables)
 						  RELKIND_SEQUENCE,
 						  RELKIND_RELATION, RELKIND_SEQUENCE,
 						  RELKIND_VIEW, RELKIND_COMPOSITE_TYPE,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 
 		destroyPQExpBuffer(acl_subquery);
 		destroyPQExpBuffer(racl_subquery);
@@ -5868,6 +5875,7 @@ getTables(Archive *fout, int *numTables)
 	i_toastreloptions = PQfnumber(res, "toast_reloptions");
 	i_reloftype = PQfnumber(res, "reloftype");
 	i_changed_acl = PQfnumber(res, "changed_acl");
+	i_partkeydef = PQfnumber(res, "partkeydef");
 
 	if (dopt->lockWaitTimeout && fout->remoteVersion >= 70300)
 	{
@@ -5938,6 +5946,7 @@ getTables(Archive *fout, int *numTables)
 		else
 			tblinfo[i].checkoption = pg_strdup(PQgetvalue(res, i, i_checkoption));
 		tblinfo[i].toast_reloptions = pg_strdup(PQgetvalue(res, i, i_toastreloptions));
+		tblinfo[i].partkeydef = pg_strdup(PQgetvalue(res, i, i_partkeydef));
 
 		/* other fields were zeroed above */
 
@@ -5982,7 +5991,9 @@ getTables(Archive *fout, int *numTables)
 		 * We only need to lock the table for certain components; see
 		 * pg_dump.h
 		 */
-		if (tblinfo[i].dobj.dump && tblinfo[i].relkind == RELKIND_RELATION &&
+		if (tblinfo[i].dobj.dump &&
+			(tblinfo[i].relkind == RELKIND_RELATION ||
+			 tblinfo->relkind == RELKIND_PARTITIONED_TABLE) &&
 			(tblinfo[i].dobj.dump & DUMP_COMPONENTS_REQUIRING_LOCK))
 		{
 			resetPQExpBuffer(query);
@@ -6084,7 +6095,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);
 
@@ -15437,6 +15451,9 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 				appendPQExpBufferChar(q, ')');
 			}
 
+			if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+				appendPQExpBuffer(q, "\nPARTITION BY %s", tbinfo->partkeydef);
+
 			if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 				appendPQExpBuffer(q, "\nSERVER %s", fmtId(srvname));
 		}
@@ -15497,6 +15514,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 		 */
 		if (dopt->binary_upgrade &&
 			(tbinfo->relkind == RELKIND_RELATION ||
+			 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 			 tbinfo->relkind == RELKIND_FOREIGN_TABLE))
 		{
 			for (j = 0; j < tbinfo->numatts; j++)
@@ -15515,7 +15533,8 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 					appendStringLiteralAH(q, fmtId(tbinfo->dobj.name), fout);
 					appendPQExpBufferStr(q, "::pg_catalog.regclass;\n");
 
-					if (tbinfo->relkind == RELKIND_RELATION)
+					if (tbinfo->relkind == RELKIND_RELATION ||
+						tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
 						appendPQExpBuffer(q, "ALTER TABLE ONLY %s ",
 										  fmtId(tbinfo->dobj.name));
 					else
@@ -15732,6 +15751,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 	 * dump properties we only have ALTER TABLE syntax for
 	 */
 	if ((tbinfo->relkind == RELKIND_RELATION ||
+		 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 		 tbinfo->relkind == RELKIND_MATVIEW) &&
 		tbinfo->relreplident != REPLICA_IDENTITY_DEFAULT)
 	{
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 2bfa2d9..0292859 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -310,6 +310,7 @@ typedef struct _tableInfo
 	bool	   *inhNotNull;		/* true if NOT NULL is inherited */
 	struct _attrDefInfo **attrdefs;		/* DEFAULT expressions */
 	struct _constraintInfo *checkexprs; /* CHECK constraints */
+	char	   *partkeydef;		/* partition key definition */
 
 	/*
 	 * Stuff computed only for dumpable tables.
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 6275a68..10d924a 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -861,6 +861,7 @@ permissionsList(const char *pattern)
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'S' THEN '%s'"
@@ -870,6 +871,7 @@ permissionsList(const char *pattern)
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("sequence"),
@@ -920,7 +922,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
@@ -1567,8 +1569,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)");
 	}
 
@@ -1633,6 +1635,14 @@ describeOneTableDetails(const char *schemaname,
 			printfPQExpBuffer(&title, _("Foreign table \"%s.%s\""),
 							  schemaname, relationname);
 			break;
+		case 'P':
+			if (tableinfo.relpersistence == 'u')
+				printfPQExpBuffer(&title, _("Unlogged table \"%s.%s\""),
+								  schemaname, relationname);
+			else
+				printfPQExpBuffer(&title, _("Table \"%s.%s\""),
+								  schemaname, relationname);
+			break;
 		default:
 			/* untranslated unknown relkind */
 			printfPQExpBuffer(&title, "?%c? \"%s.%s\"",
@@ -1646,8 +1656,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");
@@ -1667,12 +1677,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");
 	}
 
@@ -1772,7 +1782,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);
@@ -1780,14 +1790,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 (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 */
@@ -1926,7 +1955,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;
@@ -2485,7 +2514,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;
@@ -2696,7 +2725,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
@@ -3024,6 +3053,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'i' THEN '%s'"
@@ -3035,6 +3065,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("index"),
@@ -3079,7 +3110,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 019f75a..5e5c370 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/pg_proc.h b/src/include/catalog/pg_proc.h
index e2d08ba..b45688b 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -1980,6 +1980,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/utils/builtins.h b/src/include/utils/builtins.h
index 2ae212a..e800647 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -729,6 +729,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/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 708232d..2fec847 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -408,3 +408,29 @@ CREATE TABLE no_inh_con_parted (
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
 ERROR:  cannot add NO INHERIT constraint to partitioned table "no_inh_con_parted"
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+Table "public.describe_range_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition Key: RANGE ((a + b))
+
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+Table "public.describe_list_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition Key: LIST (a)
+
+DROP TABLE describe_range_key, describe_list_key;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 6e4a8be..4dd6a0a 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -402,3 +402,16 @@ CREATE TABLE no_inh_con_parted (
 	a int,
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
+
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+DROP TABLE describe_range_key, describe_list_key;
-- 
1.7.1


--------------6EB5B318125CB7DB1A4FD229
Content-Type: text/x-diff;
 name="0003-Catalog-and-DDL-for-partitions-3.patch"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
 filename="0003-Catalog-and-DDL-for-partitions-3.patch"



^ permalink  raw  reply  [nested|flat] 110+ messages in thread

* [PATCH 2/9] psql and pg_dump support for partitioned tables.
@ 2016-07-12 08:20  amit <[email protected]>
  0 siblings, 0 replies; 110+ messages in thread

From: amit @ 2016-07-12 08:20 UTC (permalink / raw)

Takes care of both the partition key deparse stuff and the new relkind.
---
 src/backend/utils/adt/ruleutils.c          |  140 ++++++++++++++++++++++++++++
 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/pg_proc.h              |    2 +
 src/include/utils/builtins.h               |    1 +
 src/test/regress/expected/create_table.out |   26 +++++
 src/test/regress/sql/create_table.sql      |   13 +++
 9 files changed, 260 insertions(+), 26 deletions(-)

diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 8a81d7a..03be202 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_table.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
@@ -315,6 +316,7 @@ static char *pg_get_indexdef_worker(Oid indexrelid, int colno,
 					   const Oid *excludeOps,
 					   bool attrsOnly, bool showTblSpc,
 					   int prettyFlags, bool missing_ok);
+static char *pg_get_partkeydef_worker(Oid relid, int prettyFlags);
 static char *pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 							int prettyFlags, bool missing_ok);
 static text *pg_get_expr_worker(text *expr, Oid relid, const char *relname,
@@ -1389,6 +1391,144 @@ 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 [ opclass_name ] [, ...])
+ */
+Datum
+pg_get_partkeydef(PG_FUNCTION_ARGS)
+{
+	Oid			relid = PG_GETARG_OID(0);
+	int			prettyFlags;
+
+	prettyFlags = PRETTYFLAG_INDENT;
+	PG_RETURN_TEXT_P(string_to_text(pg_get_partkeydef_worker(relid,
+									prettyFlags)));
+}
+
+/*
+ * Internal workhorse to decompile a partition key definition.
+ */
+static char *
+pg_get_partkeydef_worker(Oid relid, int prettyFlags)
+{
+	Form_pg_partitioned_table	form;
+	HeapTuple	tuple;
+	oidvector  *partclass;
+	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_table) GETSTRUCT(tuple);
+
+	Assert(form->partrelid == relid);
+
+	/* Must get partclass, and partexprs the hard way */
+	datum = SysCacheGetAttr(PARTEDRELID, tuple,
+							Anum_pg_partitioned_table_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_table_partexprbin))
+	{
+		Datum		exprsDatum;
+		bool		isnull;
+		char	   *exprsString;
+
+		exprsDatum = SysCacheGetAttr(PARTEDRELID, tuple,
+									 Anum_pg_partitioned_table_partexprbin, &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);
+
+	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,
+											0, 0);
+
+			appendStringInfoString(&buf, 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/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index ba9c276..c805a84 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -1253,9 +1253,10 @@ expand_table_name_patterns(Archive *fout,
 						  "SELECT c.oid"
 						  "\nFROM pg_catalog.pg_class c"
 		"\n     LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace"
-					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c')\n",
+					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c')\n",
 						  RELKIND_RELATION, RELKIND_SEQUENCE, RELKIND_VIEW,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 		processSQLNamePattern(GetConnection(fout), query, cell->val, true,
 							  false, "n.nspname", "c.relname", NULL,
 							  "pg_catalog.pg_table_is_visible(c.oid)");
@@ -2125,6 +2126,9 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo, bool oids)
 	/* Skip FOREIGN TABLEs (no data to dump) */
 	if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 		return;
+	/* Skip partitioned tables (data in partitions) */
+	if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+		return;
 
 	/* Don't dump data in unlogged tables, if so requested */
 	if (tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED &&
@@ -5204,6 +5208,7 @@ getTables(Archive *fout, int *numTables)
 	int			i_reloftype;
 	int			i_relpages;
 	int			i_changed_acl;
+	int			i_partkeydef;
 
 	/* Make sure we are in proper schema */
 	selectSourceSchema(fout, "pg_catalog");
@@ -5289,7 +5294,8 @@ getTables(Archive *fout, int *numTables)
 						  "OR %s IS NOT NULL "
 						  "OR %s IS NOT NULL"
 						  "))"
-						  "AS changed_acl "
+						  "AS changed_acl, "
+						  "CASE WHEN c.relkind = 'P' THEN pg_catalog.pg_get_partkeydef(c.oid) ELSE NULL END AS partkeydef "
 						  "FROM pg_class c "
 						  "LEFT JOIN pg_depend d ON "
 						  "(c.relkind = '%c' AND "
@@ -5301,7 +5307,7 @@ getTables(Archive *fout, int *numTables)
 						  "(c.oid = pip.objoid "
 						  "AND pip.classoid = 'pg_class'::regclass "
 						  "AND pip.objsubid = 0) "
-				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c') "
+				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c', '%c') "
 						  "ORDER BY c.oid",
 						  acl_subquery->data,
 						  racl_subquery->data,
@@ -5315,7 +5321,8 @@ getTables(Archive *fout, int *numTables)
 						  RELKIND_SEQUENCE,
 						  RELKIND_RELATION, RELKIND_SEQUENCE,
 						  RELKIND_VIEW, RELKIND_COMPOSITE_TYPE,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 
 		destroyPQExpBuffer(acl_subquery);
 		destroyPQExpBuffer(racl_subquery);
@@ -5877,6 +5884,7 @@ getTables(Archive *fout, int *numTables)
 	i_toastreloptions = PQfnumber(res, "toast_reloptions");
 	i_reloftype = PQfnumber(res, "reloftype");
 	i_changed_acl = PQfnumber(res, "changed_acl");
+	i_partkeydef = PQfnumber(res, "partkeydef");
 
 	if (dopt->lockWaitTimeout && fout->remoteVersion >= 70300)
 	{
@@ -5947,6 +5955,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 */
 
@@ -5991,7 +6000,9 @@ getTables(Archive *fout, int *numTables)
 		 * We only need to lock the table for certain components; see
 		 * pg_dump.h
 		 */
-		if (tblinfo[i].dobj.dump && tblinfo[i].relkind == RELKIND_RELATION &&
+		if (tblinfo[i].dobj.dump &&
+			(tblinfo[i].relkind == RELKIND_RELATION ||
+			 tblinfo->relkind == RELKIND_PARTITIONED_TABLE) &&
 			(tblinfo[i].dobj.dump & DUMP_COMPONENTS_REQUIRING_LOCK))
 		{
 			resetPQExpBuffer(query);
@@ -6093,7 +6104,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);
 
@@ -15446,6 +15460,9 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 				appendPQExpBufferChar(q, ')');
 			}
 
+			if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+				appendPQExpBuffer(q, "\nPARTITION BY %s", tbinfo->partkeydef);
+
 			if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 				appendPQExpBuffer(q, "\nSERVER %s", fmtId(srvname));
 		}
@@ -15506,6 +15523,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 		 */
 		if (dopt->binary_upgrade &&
 			(tbinfo->relkind == RELKIND_RELATION ||
+			 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 			 tbinfo->relkind == RELKIND_FOREIGN_TABLE))
 		{
 			for (j = 0; j < tbinfo->numatts; j++)
@@ -15524,7 +15542,8 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 					appendStringLiteralAH(q, fmtId(tbinfo->dobj.name), fout);
 					appendPQExpBufferStr(q, "::pg_catalog.regclass;\n");
 
-					if (tbinfo->relkind == RELKIND_RELATION)
+					if (tbinfo->relkind == RELKIND_RELATION ||
+						tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
 						appendPQExpBuffer(q, "ALTER TABLE ONLY %s ",
 										  fmtId(tbinfo->dobj.name));
 					else
@@ -15741,6 +15760,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 	 * dump properties we only have ALTER TABLE syntax for
 	 */
 	if ((tbinfo->relkind == RELKIND_RELATION ||
+		 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 		 tbinfo->relkind == RELKIND_MATVIEW) &&
 		tbinfo->relreplident != REPLICA_IDENTITY_DEFAULT)
 	{
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 2bfa2d9..0292859 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -310,6 +310,7 @@ typedef struct _tableInfo
 	bool	   *inhNotNull;		/* true if NOT NULL is inherited */
 	struct _attrDefInfo **attrdefs;		/* DEFAULT expressions */
 	struct _constraintInfo *checkexprs; /* CHECK constraints */
+	char	   *partkeydef;		/* partition key definition */
 
 	/*
 	 * Stuff computed only for dumpable tables.
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 6275a68..10d924a 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -861,6 +861,7 @@ permissionsList(const char *pattern)
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'S' THEN '%s'"
@@ -870,6 +871,7 @@ permissionsList(const char *pattern)
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("sequence"),
@@ -920,7 +922,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
@@ -1567,8 +1569,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)");
 	}
 
@@ -1633,6 +1635,14 @@ describeOneTableDetails(const char *schemaname,
 			printfPQExpBuffer(&title, _("Foreign table \"%s.%s\""),
 							  schemaname, relationname);
 			break;
+		case 'P':
+			if (tableinfo.relpersistence == 'u')
+				printfPQExpBuffer(&title, _("Unlogged table \"%s.%s\""),
+								  schemaname, relationname);
+			else
+				printfPQExpBuffer(&title, _("Table \"%s.%s\""),
+								  schemaname, relationname);
+			break;
 		default:
 			/* untranslated unknown relkind */
 			printfPQExpBuffer(&title, "?%c? \"%s.%s\"",
@@ -1646,8 +1656,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");
@@ -1667,12 +1677,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");
 	}
 
@@ -1772,7 +1782,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);
@@ -1780,14 +1790,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 (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 */
@@ -1926,7 +1955,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;
@@ -2485,7 +2514,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;
@@ -2696,7 +2725,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
@@ -3024,6 +3053,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'i' THEN '%s'"
@@ -3035,6 +3065,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("index"),
@@ -3079,7 +3110,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 019f75a..5e5c370 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/pg_proc.h b/src/include/catalog/pg_proc.h
index e2d08ba..b45688b 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -1980,6 +1980,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/utils/builtins.h b/src/include/utils/builtins.h
index 2ae212a..e800647 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -729,6 +729,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/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 708232d..2fec847 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -408,3 +408,29 @@ CREATE TABLE no_inh_con_parted (
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
 ERROR:  cannot add NO INHERIT constraint to partitioned table "no_inh_con_parted"
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+Table "public.describe_range_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition Key: RANGE ((a + b))
+
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+Table "public.describe_list_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition Key: LIST (a)
+
+DROP TABLE describe_range_key, describe_list_key;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 6e4a8be..4dd6a0a 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -402,3 +402,16 @@ CREATE TABLE no_inh_con_parted (
 	a int,
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
+
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+DROP TABLE describe_range_key, describe_list_key;
-- 
1.7.1


--------------4012470FC48539D6F6714CEA
Content-Type: text/x-diff;
 name="0003-Catalog-and-DDL-for-partitions-4.patch"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
 filename="0003-Catalog-and-DDL-for-partitions-4.patch"



^ permalink  raw  reply  [nested|flat] 110+ messages in thread

* [PATCH 2/9] psql and pg_dump support for partitioned tables.
@ 2016-07-12 08:20  amit <[email protected]>
  0 siblings, 0 replies; 110+ messages in thread

From: amit @ 2016-07-12 08:20 UTC (permalink / raw)

Takes care of both the partition key deparse stuff and the new relkind.
---
 src/backend/utils/adt/ruleutils.c          |  140 ++++++++++++++++++++++++++++
 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/pg_proc.h              |    2 +
 src/include/utils/builtins.h               |    1 +
 src/test/regress/expected/create_table.out |   26 +++++
 src/test/regress/sql/create_table.sql      |   13 +++
 9 files changed, 260 insertions(+), 26 deletions(-)

diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 8a81d7a..03be202 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_table.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
@@ -315,6 +316,7 @@ static char *pg_get_indexdef_worker(Oid indexrelid, int colno,
 					   const Oid *excludeOps,
 					   bool attrsOnly, bool showTblSpc,
 					   int prettyFlags, bool missing_ok);
+static char *pg_get_partkeydef_worker(Oid relid, int prettyFlags);
 static char *pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 							int prettyFlags, bool missing_ok);
 static text *pg_get_expr_worker(text *expr, Oid relid, const char *relname,
@@ -1389,6 +1391,144 @@ 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 [ opclass_name ] [, ...])
+ */
+Datum
+pg_get_partkeydef(PG_FUNCTION_ARGS)
+{
+	Oid			relid = PG_GETARG_OID(0);
+	int			prettyFlags;
+
+	prettyFlags = PRETTYFLAG_INDENT;
+	PG_RETURN_TEXT_P(string_to_text(pg_get_partkeydef_worker(relid,
+									prettyFlags)));
+}
+
+/*
+ * Internal workhorse to decompile a partition key definition.
+ */
+static char *
+pg_get_partkeydef_worker(Oid relid, int prettyFlags)
+{
+	Form_pg_partitioned_table	form;
+	HeapTuple	tuple;
+	oidvector  *partclass;
+	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_table) GETSTRUCT(tuple);
+
+	Assert(form->partrelid == relid);
+
+	/* Must get partclass, and partexprs the hard way */
+	datum = SysCacheGetAttr(PARTEDRELID, tuple,
+							Anum_pg_partitioned_table_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_table_partexprbin))
+	{
+		Datum		exprsDatum;
+		bool		isnull;
+		char	   *exprsString;
+
+		exprsDatum = SysCacheGetAttr(PARTEDRELID, tuple,
+									 Anum_pg_partitioned_table_partexprbin, &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);
+
+	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,
+											0, 0);
+
+			appendStringInfoString(&buf, 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/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 51b8a1a..854dddc 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -1253,9 +1253,10 @@ expand_table_name_patterns(Archive *fout,
 						  "SELECT c.oid"
 						  "\nFROM pg_catalog.pg_class c"
 		"\n     LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace"
-					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c')\n",
+					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c')\n",
 						  RELKIND_RELATION, RELKIND_SEQUENCE, RELKIND_VIEW,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 		processSQLNamePattern(GetConnection(fout), query, cell->val, true,
 							  false, "n.nspname", "c.relname", NULL,
 							  "pg_catalog.pg_table_is_visible(c.oid)");
@@ -2125,6 +2126,9 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo, bool oids)
 	/* Skip FOREIGN TABLEs (no data to dump) */
 	if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 		return;
+	/* Skip partitioned tables (data in partitions) */
+	if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+		return;
 
 	/* Don't dump data in unlogged tables, if so requested */
 	if (tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED &&
@@ -5211,6 +5215,7 @@ getTables(Archive *fout, int *numTables)
 	int			i_reloftype;
 	int			i_relpages;
 	int			i_changed_acl;
+	int			i_partkeydef;
 
 	/* Make sure we are in proper schema */
 	selectSourceSchema(fout, "pg_catalog");
@@ -5296,7 +5301,8 @@ getTables(Archive *fout, int *numTables)
 						  "OR %s IS NOT NULL "
 						  "OR %s IS NOT NULL"
 						  "))"
-						  "AS changed_acl "
+						  "AS changed_acl, "
+						  "CASE WHEN c.relkind = 'P' THEN pg_catalog.pg_get_partkeydef(c.oid) ELSE NULL END AS partkeydef "
 						  "FROM pg_class c "
 						  "LEFT JOIN pg_depend d ON "
 						  "(c.relkind = '%c' AND "
@@ -5308,7 +5314,7 @@ getTables(Archive *fout, int *numTables)
 						  "(c.oid = pip.objoid "
 						  "AND pip.classoid = 'pg_class'::regclass "
 						  "AND pip.objsubid = 0) "
-				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c') "
+				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c', '%c') "
 						  "ORDER BY c.oid",
 						  acl_subquery->data,
 						  racl_subquery->data,
@@ -5322,7 +5328,8 @@ getTables(Archive *fout, int *numTables)
 						  RELKIND_SEQUENCE,
 						  RELKIND_RELATION, RELKIND_SEQUENCE,
 						  RELKIND_VIEW, RELKIND_COMPOSITE_TYPE,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 
 		destroyPQExpBuffer(acl_subquery);
 		destroyPQExpBuffer(racl_subquery);
@@ -5884,6 +5891,7 @@ getTables(Archive *fout, int *numTables)
 	i_toastreloptions = PQfnumber(res, "toast_reloptions");
 	i_reloftype = PQfnumber(res, "reloftype");
 	i_changed_acl = PQfnumber(res, "changed_acl");
+	i_partkeydef = PQfnumber(res, "partkeydef");
 
 	if (dopt->lockWaitTimeout && fout->remoteVersion >= 70300)
 	{
@@ -5954,6 +5962,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 */
 
@@ -5998,7 +6007,9 @@ getTables(Archive *fout, int *numTables)
 		 * We only need to lock the table for certain components; see
 		 * pg_dump.h
 		 */
-		if (tblinfo[i].dobj.dump && tblinfo[i].relkind == RELKIND_RELATION &&
+		if (tblinfo[i].dobj.dump &&
+			(tblinfo[i].relkind == RELKIND_RELATION ||
+			 tblinfo->relkind == RELKIND_PARTITIONED_TABLE) &&
 			(tblinfo[i].dobj.dump & DUMP_COMPONENTS_REQUIRING_LOCK))
 		{
 			resetPQExpBuffer(query);
@@ -6100,7 +6111,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);
 
@@ -15453,6 +15467,9 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 				appendPQExpBufferChar(q, ')');
 			}
 
+			if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+				appendPQExpBuffer(q, "\nPARTITION BY %s", tbinfo->partkeydef);
+
 			if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 				appendPQExpBuffer(q, "\nSERVER %s", fmtId(srvname));
 		}
@@ -15513,6 +15530,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 		 */
 		if (dopt->binary_upgrade &&
 			(tbinfo->relkind == RELKIND_RELATION ||
+			 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 			 tbinfo->relkind == RELKIND_FOREIGN_TABLE))
 		{
 			for (j = 0; j < tbinfo->numatts; j++)
@@ -15531,7 +15549,8 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 					appendStringLiteralAH(q, fmtId(tbinfo->dobj.name), fout);
 					appendPQExpBufferStr(q, "::pg_catalog.regclass;\n");
 
-					if (tbinfo->relkind == RELKIND_RELATION)
+					if (tbinfo->relkind == RELKIND_RELATION ||
+						tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
 						appendPQExpBuffer(q, "ALTER TABLE ONLY %s ",
 										  fmtId(tbinfo->dobj.name));
 					else
@@ -15748,6 +15767,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 	 * dump properties we only have ALTER TABLE syntax for
 	 */
 	if ((tbinfo->relkind == RELKIND_RELATION ||
+		 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 		 tbinfo->relkind == RELKIND_MATVIEW) &&
 		tbinfo->relreplident != REPLICA_IDENTITY_DEFAULT)
 	{
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 2bfa2d9..0292859 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -310,6 +310,7 @@ typedef struct _tableInfo
 	bool	   *inhNotNull;		/* true if NOT NULL is inherited */
 	struct _attrDefInfo **attrdefs;		/* DEFAULT expressions */
 	struct _constraintInfo *checkexprs; /* CHECK constraints */
+	char	   *partkeydef;		/* partition key definition */
 
 	/*
 	 * Stuff computed only for dumpable tables.
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 6275a68..db6dc5c 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -861,6 +861,7 @@ permissionsList(const char *pattern)
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'S' THEN '%s'"
@@ -870,6 +871,7 @@ permissionsList(const char *pattern)
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("sequence"),
@@ -920,7 +922,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
@@ -1567,8 +1569,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)");
 	}
 
@@ -1633,6 +1635,14 @@ describeOneTableDetails(const char *schemaname,
 			printfPQExpBuffer(&title, _("Foreign table \"%s.%s\""),
 							  schemaname, relationname);
 			break;
+		case 'P':
+			if (tableinfo.relpersistence == 'u')
+				printfPQExpBuffer(&title, _("Unlogged table \"%s.%s\""),
+								  schemaname, relationname);
+			else
+				printfPQExpBuffer(&title, _("Table \"%s.%s\""),
+								  schemaname, relationname);
+			break;
 		default:
 			/* untranslated unknown relkind */
 			printfPQExpBuffer(&title, "?%c? \"%s.%s\"",
@@ -1646,8 +1656,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");
@@ -1667,12 +1677,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");
 	}
 
@@ -1772,7 +1782,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);
@@ -1780,14 +1790,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 (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 */
@@ -1926,7 +1955,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;
@@ -2485,7 +2514,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;
@@ -2696,7 +2725,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
@@ -3024,6 +3053,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'i' THEN '%s'"
@@ -3035,6 +3065,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("index"),
@@ -3079,7 +3110,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 50a45eb..8284a9c 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/pg_proc.h b/src/include/catalog/pg_proc.h
index e2d08ba..b45688b 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -1980,6 +1980,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/utils/builtins.h b/src/include/utils/builtins.h
index 2ae212a..e800647 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -729,6 +729,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/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 1394362..260ec34 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -407,3 +407,29 @@ CREATE TABLE no_inh_con_parted (
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
 ERROR:  cannot add NO INHERIT constraint to partitioned table "no_inh_con_parted"
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+Table "public.describe_range_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition key: RANGE ((a + b))
+
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+Table "public.describe_list_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition key: LIST (a)
+
+DROP TABLE describe_range_key, describe_list_key;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index d383f58..7a1f8de 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -401,3 +401,16 @@ CREATE TABLE no_inh_con_parted (
 	a int,
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
+
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+DROP TABLE describe_range_key, describe_list_key;
-- 
1.7.1


--------------C3235EE96C69D8DAE4DB543E
Content-Type: text/x-diff;
 name="0003-Catalog-and-DDL-for-partitions-6.patch"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
 filename="0003-Catalog-and-DDL-for-partitions-6.patch"



^ permalink  raw  reply  [nested|flat] 110+ messages in thread

* [PATCH 2/8] psql and pg_dump support for partitioned tables.
@ 2016-07-12 08:20  amit <[email protected]>
  0 siblings, 0 replies; 110+ messages in thread

From: amit @ 2016-07-12 08:20 UTC (permalink / raw)

Takes care of both the partition key deparse stuff and the new relkind.
---
 src/backend/utils/adt/ruleutils.c          |  147 ++++++++++++++++++++++++++++
 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/pg_proc.h              |    2 +
 src/include/utils/builtins.h               |    1 +
 src/test/regress/expected/create_table.out |   26 +++++
 src/test/regress/sql/create_table.sql      |   13 +++
 9 files changed, 267 insertions(+), 26 deletions(-)

diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index ec966c7..4691b5b 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, bool missing_ok);
+static char *pg_get_partkeydef_worker(Oid relId, int prettyFlags);
 static char *pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 							int prettyFlags, bool missing_ok);
 static text *pg_get_expr_worker(text *expr, Oid relid, const char *relname,
@@ -1389,6 +1391,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 [ 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/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 4ee10fc..a127672 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -1253,9 +1253,10 @@ expand_table_name_patterns(Archive *fout,
 						  "SELECT c.oid"
 						  "\nFROM pg_catalog.pg_class c"
 		"\n     LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace"
-					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c')\n",
+					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c')\n",
 						  RELKIND_RELATION, RELKIND_SEQUENCE, RELKIND_VIEW,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_REL);
 		processSQLNamePattern(GetConnection(fout), query, cell->val, true,
 							  false, "n.nspname", "c.relname", NULL,
 							  "pg_catalog.pg_table_is_visible(c.oid)");
@@ -2117,6 +2118,9 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo, bool oids)
 	/* Skip FOREIGN TABLEs (no data to dump) */
 	if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 		return;
+	/* Skip partitioned tables (data in partitions) */
+	if (tbinfo->relkind == RELKIND_PARTITIONED_REL)
+		return;
 
 	/* Don't dump data in unlogged tables, if so requested */
 	if (tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED &&
@@ -5195,6 +5199,7 @@ getTables(Archive *fout, int *numTables)
 	int			i_reloftype;
 	int			i_relpages;
 	int			i_changed_acl;
+	int			i_partkeydef;
 
 	/* Make sure we are in proper schema */
 	selectSourceSchema(fout, "pg_catalog");
@@ -5280,7 +5285,8 @@ getTables(Archive *fout, int *numTables)
 						  "OR %s IS NOT NULL "
 						  "OR %s IS NOT NULL"
 						  "))"
-						  "AS changed_acl "
+						  "AS changed_acl, "
+						  "CASE WHEN c.relkind = 'P' THEN pg_catalog.pg_get_partkeydef(c.oid) ELSE NULL END AS partkeydef "
 						  "FROM pg_class c "
 						  "LEFT JOIN pg_depend d ON "
 						  "(c.relkind = '%c' AND "
@@ -5292,7 +5298,7 @@ getTables(Archive *fout, int *numTables)
 						  "(c.oid = pip.objoid "
 						  "AND pip.classoid = 'pg_class'::regclass "
 						  "AND pip.objsubid = 0) "
-				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c') "
+				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c', '%c') "
 						  "ORDER BY c.oid",
 						  acl_subquery->data,
 						  racl_subquery->data,
@@ -5306,7 +5312,8 @@ getTables(Archive *fout, int *numTables)
 						  RELKIND_SEQUENCE,
 						  RELKIND_RELATION, RELKIND_SEQUENCE,
 						  RELKIND_VIEW, RELKIND_COMPOSITE_TYPE,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_REL);
 
 		destroyPQExpBuffer(acl_subquery);
 		destroyPQExpBuffer(racl_subquery);
@@ -5868,6 +5875,7 @@ getTables(Archive *fout, int *numTables)
 	i_toastreloptions = PQfnumber(res, "toast_reloptions");
 	i_reloftype = PQfnumber(res, "reloftype");
 	i_changed_acl = PQfnumber(res, "changed_acl");
+	i_partkeydef = PQfnumber(res, "partkeydef");
 
 	if (dopt->lockWaitTimeout && fout->remoteVersion >= 70300)
 	{
@@ -5938,6 +5946,7 @@ getTables(Archive *fout, int *numTables)
 		else
 			tblinfo[i].checkoption = pg_strdup(PQgetvalue(res, i, i_checkoption));
 		tblinfo[i].toast_reloptions = pg_strdup(PQgetvalue(res, i, i_toastreloptions));
+		tblinfo[i].partkeydef = pg_strdup(PQgetvalue(res, i, i_partkeydef));
 
 		/* other fields were zeroed above */
 
@@ -5982,7 +5991,9 @@ getTables(Archive *fout, int *numTables)
 		 * We only need to lock the table for certain components; see
 		 * pg_dump.h
 		 */
-		if (tblinfo[i].dobj.dump && tblinfo[i].relkind == RELKIND_RELATION &&
+		if (tblinfo[i].dobj.dump &&
+			(tblinfo[i].relkind == RELKIND_RELATION ||
+			 tblinfo->relkind == RELKIND_PARTITIONED_REL) &&
 			(tblinfo[i].dobj.dump & DUMP_COMPONENTS_REQUIRING_LOCK))
 		{
 			resetPQExpBuffer(query);
@@ -6083,7 +6094,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);
 
@@ -15436,6 +15450,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));
 		}
@@ -15496,6 +15513,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++)
@@ -15514,7 +15532,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
@@ -15731,6 +15750,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 	 * dump properties we only have ALTER TABLE syntax for
 	 */
 	if ((tbinfo->relkind == RELKIND_RELATION ||
+		 tbinfo->relkind == RELKIND_PARTITIONED_REL ||
 		 tbinfo->relkind == RELKIND_MATVIEW) &&
 		tbinfo->relreplident != REPLICA_IDENTITY_DEFAULT)
 	{
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 2bfa2d9..0292859 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -310,6 +310,7 @@ typedef struct _tableInfo
 	bool	   *inhNotNull;		/* true if NOT NULL is inherited */
 	struct _attrDefInfo **attrdefs;		/* DEFAULT expressions */
 	struct _constraintInfo *checkexprs; /* CHECK constraints */
+	char	   *partkeydef;		/* partition key definition */
 
 	/*
 	 * Stuff computed only for dumpable tables.
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 27be102..3ba8a90 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -856,6 +856,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"),
@@ -865,6 +866,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");
@@ -911,7 +913,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
@@ -1555,8 +1557,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)");
 	}
 
@@ -1621,6 +1623,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\"",
@@ -1634,8 +1644,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");
@@ -1655,12 +1665,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");
 	}
 
@@ -1760,7 +1770,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);
@@ -1768,14 +1778,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 */
@@ -1914,7 +1943,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;
@@ -2473,7 +2502,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;
@@ -2684,7 +2713,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
@@ -3018,6 +3047,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"),
@@ -3029,6 +3059,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"));
 
@@ -3067,7 +3098,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 
 	appendPQExpBufferStr(&buf, "\nWHERE c.relkind IN (");
 	if (showTables)
-		appendPQExpBufferStr(&buf, "'r',");
+		appendPQExpBufferStr(&buf, "'r', 'P',");
 	if (showViews)
 		appendPQExpBufferStr(&buf, "'v',");
 	if (showMatViews)
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 8469d9f..cd4eb7c 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -427,7 +427,7 @@ static const SchemaQuery Query_for_list_of_tables = {
 	/* catname */
 	"pg_catalog.pg_class c",
 	/* selcondition */
-	"c.relkind IN ('r')",
+	"c.relkind IN ('r', 'P')",
 	/* viscondition */
 	"pg_catalog.pg_table_is_visible(c.oid)",
 	/* namespace */
@@ -458,7 +458,7 @@ static const SchemaQuery Query_for_list_of_updatables = {
 	/* catname */
 	"pg_catalog.pg_class c",
 	/* selcondition */
-	"c.relkind IN ('r', 'f', 'v')",
+	"c.relkind IN ('r', 'f', 'v', 'P')",
 	/* viscondition */
 	"pg_catalog.pg_table_is_visible(c.oid)",
 	/* namespace */
@@ -488,7 +488,7 @@ static const SchemaQuery Query_for_list_of_tsvmf = {
 	/* catname */
 	"pg_catalog.pg_class c",
 	/* selcondition */
-	"c.relkind IN ('r', 'S', 'v', 'm', 'f')",
+	"c.relkind IN ('r', 'S', 'v', 'm', 'f', 'P')",
 	/* viscondition */
 	"pg_catalog.pg_table_is_visible(c.oid)",
 	/* namespace */
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index 270dd21..5a444d9 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/utils/builtins.h b/src/include/utils/builtins.h
index 8cebc86..351bfd3 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -722,6 +722,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/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index fb7a1b3..4fc8c06 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -413,3 +413,29 @@ SELECT connoinherit FROM pg_constraint WHERE conrelid = 'no_inh_con_parted'::reg
 (1 row)
 
 DROP TABLE no_inh_con_parted;
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+Partitioned table "public.describe_range_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition Key: PARTITION BY RANGE (((a + b)))
+
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+Partitioned table "public.describe_list_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition Key: PARTITION BY LIST (a)
+
+DROP TABLE describe_range_key, describe_list_key;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index cb17517..7c7a18a 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -404,3 +404,16 @@ CREATE TABLE no_inh_con_parted (
 ) PARTITION BY RANGE (a);
 SELECT connoinherit FROM pg_constraint WHERE conrelid = 'no_inh_con_parted'::regclass;
 DROP TABLE no_inh_con_parted;
+
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+DROP TABLE describe_range_key, describe_list_key;
-- 
1.7.1


--------------8C4C48E640631A327D3D2826
Content-Type: text/x-diff;
 name="0003-Catalog-and-DDL-for-partitions.patch"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
 filename="0003-Catalog-and-DDL-for-partitions.patch"



^ permalink  raw  reply  [nested|flat] 110+ messages in thread

* [PATCH 2/9] psql and pg_dump support for partitioned tables.
@ 2016-07-12 08:20  amit <[email protected]>
  0 siblings, 0 replies; 110+ messages in thread

From: amit @ 2016-07-12 08:20 UTC (permalink / raw)

Takes care of both the partition key deparse stuff and the new relkind.
---
 src/backend/utils/adt/ruleutils.c          |  140 ++++++++++++++++++++++++++++
 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/pg_proc.h              |    2 +
 src/include/utils/builtins.h               |    1 +
 src/test/regress/expected/create_table.out |   26 +++++
 src/test/regress/sql/create_table.sql      |   13 +++
 9 files changed, 260 insertions(+), 26 deletions(-)

diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 8a81d7a..77ce807 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_table.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
@@ -315,6 +316,7 @@ static char *pg_get_indexdef_worker(Oid indexrelid, int colno,
 					   const Oid *excludeOps,
 					   bool attrsOnly, bool showTblSpc,
 					   int prettyFlags, bool missing_ok);
+static char *pg_get_partkeydef_worker(Oid relId, int prettyFlags);
 static char *pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 							int prettyFlags, bool missing_ok);
 static text *pg_get_expr_worker(text *expr, Oid relid, const char *relname,
@@ -1389,6 +1391,144 @@ 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 [ 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_table	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_table) GETSTRUCT(tuple);
+
+	Assert(form->partedrelid == relId);
+
+	/* Must get partclass, and partexprs the hard way */
+	datum = SysCacheGetAttr(PARTEDRELID, tuple,
+							Anum_pg_partitioned_table_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_table_partexprbin))
+	{
+		Datum		exprsDatum;
+		bool		isnull;
+		char	   *exprsString;
+
+		exprsDatum = SysCacheGetAttr(PARTEDRELID, tuple,
+									 Anum_pg_partitioned_table_partexprbin, &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);
+
+	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,
+											0, 0);
+
+			appendStringInfoString(&buf, 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/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index a5c2d09..c418ba7 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -1253,9 +1253,10 @@ expand_table_name_patterns(Archive *fout,
 						  "SELECT c.oid"
 						  "\nFROM pg_catalog.pg_class c"
 		"\n     LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace"
-					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c')\n",
+					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c')\n",
 						  RELKIND_RELATION, RELKIND_SEQUENCE, RELKIND_VIEW,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 		processSQLNamePattern(GetConnection(fout), query, cell->val, true,
 							  false, "n.nspname", "c.relname", NULL,
 							  "pg_catalog.pg_table_is_visible(c.oid)");
@@ -2117,6 +2118,9 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo, bool oids)
 	/* Skip FOREIGN TABLEs (no data to dump) */
 	if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 		return;
+	/* Skip partitioned tables (data in partitions) */
+	if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+		return;
 
 	/* Don't dump data in unlogged tables, if so requested */
 	if (tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED &&
@@ -5195,6 +5199,7 @@ getTables(Archive *fout, int *numTables)
 	int			i_reloftype;
 	int			i_relpages;
 	int			i_changed_acl;
+	int			i_partkeydef;
 
 	/* Make sure we are in proper schema */
 	selectSourceSchema(fout, "pg_catalog");
@@ -5280,7 +5285,8 @@ getTables(Archive *fout, int *numTables)
 						  "OR %s IS NOT NULL "
 						  "OR %s IS NOT NULL"
 						  "))"
-						  "AS changed_acl "
+						  "AS changed_acl, "
+						  "CASE WHEN c.relkind = 'P' THEN pg_catalog.pg_get_partkeydef(c.oid) ELSE NULL END AS partkeydef "
 						  "FROM pg_class c "
 						  "LEFT JOIN pg_depend d ON "
 						  "(c.relkind = '%c' AND "
@@ -5292,7 +5298,7 @@ getTables(Archive *fout, int *numTables)
 						  "(c.oid = pip.objoid "
 						  "AND pip.classoid = 'pg_class'::regclass "
 						  "AND pip.objsubid = 0) "
-				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c') "
+				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c', '%c') "
 						  "ORDER BY c.oid",
 						  acl_subquery->data,
 						  racl_subquery->data,
@@ -5306,7 +5312,8 @@ getTables(Archive *fout, int *numTables)
 						  RELKIND_SEQUENCE,
 						  RELKIND_RELATION, RELKIND_SEQUENCE,
 						  RELKIND_VIEW, RELKIND_COMPOSITE_TYPE,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 
 		destroyPQExpBuffer(acl_subquery);
 		destroyPQExpBuffer(racl_subquery);
@@ -5868,6 +5875,7 @@ getTables(Archive *fout, int *numTables)
 	i_toastreloptions = PQfnumber(res, "toast_reloptions");
 	i_reloftype = PQfnumber(res, "reloftype");
 	i_changed_acl = PQfnumber(res, "changed_acl");
+	i_partkeydef = PQfnumber(res, "partkeydef");
 
 	if (dopt->lockWaitTimeout && fout->remoteVersion >= 70300)
 	{
@@ -5938,6 +5946,7 @@ getTables(Archive *fout, int *numTables)
 		else
 			tblinfo[i].checkoption = pg_strdup(PQgetvalue(res, i, i_checkoption));
 		tblinfo[i].toast_reloptions = pg_strdup(PQgetvalue(res, i, i_toastreloptions));
+		tblinfo[i].partkeydef = pg_strdup(PQgetvalue(res, i, i_partkeydef));
 
 		/* other fields were zeroed above */
 
@@ -5982,7 +5991,9 @@ getTables(Archive *fout, int *numTables)
 		 * We only need to lock the table for certain components; see
 		 * pg_dump.h
 		 */
-		if (tblinfo[i].dobj.dump && tblinfo[i].relkind == RELKIND_RELATION &&
+		if (tblinfo[i].dobj.dump &&
+			(tblinfo[i].relkind == RELKIND_RELATION ||
+			 tblinfo->relkind == RELKIND_PARTITIONED_TABLE) &&
 			(tblinfo[i].dobj.dump & DUMP_COMPONENTS_REQUIRING_LOCK))
 		{
 			resetPQExpBuffer(query);
@@ -6084,7 +6095,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);
 
@@ -15437,6 +15451,9 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 				appendPQExpBufferChar(q, ')');
 			}
 
+			if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+				appendPQExpBuffer(q, "\nPARTITION BY %s", tbinfo->partkeydef);
+
 			if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 				appendPQExpBuffer(q, "\nSERVER %s", fmtId(srvname));
 		}
@@ -15497,6 +15514,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 		 */
 		if (dopt->binary_upgrade &&
 			(tbinfo->relkind == RELKIND_RELATION ||
+			 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 			 tbinfo->relkind == RELKIND_FOREIGN_TABLE))
 		{
 			for (j = 0; j < tbinfo->numatts; j++)
@@ -15515,7 +15533,8 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 					appendStringLiteralAH(q, fmtId(tbinfo->dobj.name), fout);
 					appendPQExpBufferStr(q, "::pg_catalog.regclass;\n");
 
-					if (tbinfo->relkind == RELKIND_RELATION)
+					if (tbinfo->relkind == RELKIND_RELATION ||
+						tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
 						appendPQExpBuffer(q, "ALTER TABLE ONLY %s ",
 										  fmtId(tbinfo->dobj.name));
 					else
@@ -15732,6 +15751,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 	 * dump properties we only have ALTER TABLE syntax for
 	 */
 	if ((tbinfo->relkind == RELKIND_RELATION ||
+		 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 		 tbinfo->relkind == RELKIND_MATVIEW) &&
 		tbinfo->relreplident != REPLICA_IDENTITY_DEFAULT)
 	{
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 2bfa2d9..0292859 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -310,6 +310,7 @@ typedef struct _tableInfo
 	bool	   *inhNotNull;		/* true if NOT NULL is inherited */
 	struct _attrDefInfo **attrdefs;		/* DEFAULT expressions */
 	struct _constraintInfo *checkexprs; /* CHECK constraints */
+	char	   *partkeydef;		/* partition key definition */
 
 	/*
 	 * Stuff computed only for dumpable tables.
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 6275a68..10d924a 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -861,6 +861,7 @@ permissionsList(const char *pattern)
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'S' THEN '%s'"
@@ -870,6 +871,7 @@ permissionsList(const char *pattern)
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("sequence"),
@@ -920,7 +922,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
@@ -1567,8 +1569,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)");
 	}
 
@@ -1633,6 +1635,14 @@ describeOneTableDetails(const char *schemaname,
 			printfPQExpBuffer(&title, _("Foreign table \"%s.%s\""),
 							  schemaname, relationname);
 			break;
+		case 'P':
+			if (tableinfo.relpersistence == 'u')
+				printfPQExpBuffer(&title, _("Unlogged table \"%s.%s\""),
+								  schemaname, relationname);
+			else
+				printfPQExpBuffer(&title, _("Table \"%s.%s\""),
+								  schemaname, relationname);
+			break;
 		default:
 			/* untranslated unknown relkind */
 			printfPQExpBuffer(&title, "?%c? \"%s.%s\"",
@@ -1646,8 +1656,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");
@@ -1667,12 +1677,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");
 	}
 
@@ -1772,7 +1782,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);
@@ -1780,14 +1790,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 (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 */
@@ -1926,7 +1955,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;
@@ -2485,7 +2514,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;
@@ -2696,7 +2725,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
@@ -3024,6 +3053,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'i' THEN '%s'"
@@ -3035,6 +3065,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("index"),
@@ -3079,7 +3110,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 1345e4e..4686cd1 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/pg_proc.h b/src/include/catalog/pg_proc.h
index e2d08ba..b45688b 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -1980,6 +1980,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/utils/builtins.h b/src/include/utils/builtins.h
index 2ae212a..e800647 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -729,6 +729,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/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 708232d..2fec847 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -408,3 +408,29 @@ CREATE TABLE no_inh_con_parted (
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
 ERROR:  cannot add NO INHERIT constraint to partitioned table "no_inh_con_parted"
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+Table "public.describe_range_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition Key: RANGE ((a + b))
+
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+Table "public.describe_list_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition Key: LIST (a)
+
+DROP TABLE describe_range_key, describe_list_key;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 6e4a8be..4dd6a0a 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -402,3 +402,16 @@ CREATE TABLE no_inh_con_parted (
 	a int,
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
+
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+DROP TABLE describe_range_key, describe_list_key;
-- 
1.7.1


--------------954458C17BBEB1BF6FC758E7
Content-Type: text/x-diff;
 name="0003-Catalog-and-DDL-for-partitions-2.patch"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
 filename="0003-Catalog-and-DDL-for-partitions-2.patch"



^ permalink  raw  reply  [nested|flat] 110+ messages in thread

* [PATCH 2/9] psql and pg_dump support for partitioned tables.
@ 2016-07-12 08:20  amit <[email protected]>
  0 siblings, 0 replies; 110+ messages in thread

From: amit @ 2016-07-12 08:20 UTC (permalink / raw)

Takes care of both the partition key deparse stuff and the new relkind.
---
 src/backend/utils/adt/ruleutils.c          |  140 ++++++++++++++++++++++++++++
 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/pg_proc.h              |    2 +
 src/include/utils/builtins.h               |    1 +
 src/test/regress/expected/create_table.out |   26 +++++
 src/test/regress/sql/create_table.sql      |   13 +++
 9 files changed, 260 insertions(+), 26 deletions(-)

diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 8a81d7a..77ce807 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_table.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
@@ -315,6 +316,7 @@ static char *pg_get_indexdef_worker(Oid indexrelid, int colno,
 					   const Oid *excludeOps,
 					   bool attrsOnly, bool showTblSpc,
 					   int prettyFlags, bool missing_ok);
+static char *pg_get_partkeydef_worker(Oid relId, int prettyFlags);
 static char *pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 							int prettyFlags, bool missing_ok);
 static text *pg_get_expr_worker(text *expr, Oid relid, const char *relname,
@@ -1389,6 +1391,144 @@ 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 [ 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_table	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_table) GETSTRUCT(tuple);
+
+	Assert(form->partedrelid == relId);
+
+	/* Must get partclass, and partexprs the hard way */
+	datum = SysCacheGetAttr(PARTEDRELID, tuple,
+							Anum_pg_partitioned_table_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_table_partexprbin))
+	{
+		Datum		exprsDatum;
+		bool		isnull;
+		char	   *exprsString;
+
+		exprsDatum = SysCacheGetAttr(PARTEDRELID, tuple,
+									 Anum_pg_partitioned_table_partexprbin, &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);
+
+	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,
+											0, 0);
+
+			appendStringInfoString(&buf, 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/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index a5c2d09..c418ba7 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -1253,9 +1253,10 @@ expand_table_name_patterns(Archive *fout,
 						  "SELECT c.oid"
 						  "\nFROM pg_catalog.pg_class c"
 		"\n     LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace"
-					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c')\n",
+					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c')\n",
 						  RELKIND_RELATION, RELKIND_SEQUENCE, RELKIND_VIEW,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 		processSQLNamePattern(GetConnection(fout), query, cell->val, true,
 							  false, "n.nspname", "c.relname", NULL,
 							  "pg_catalog.pg_table_is_visible(c.oid)");
@@ -2117,6 +2118,9 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo, bool oids)
 	/* Skip FOREIGN TABLEs (no data to dump) */
 	if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 		return;
+	/* Skip partitioned tables (data in partitions) */
+	if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+		return;
 
 	/* Don't dump data in unlogged tables, if so requested */
 	if (tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED &&
@@ -5195,6 +5199,7 @@ getTables(Archive *fout, int *numTables)
 	int			i_reloftype;
 	int			i_relpages;
 	int			i_changed_acl;
+	int			i_partkeydef;
 
 	/* Make sure we are in proper schema */
 	selectSourceSchema(fout, "pg_catalog");
@@ -5280,7 +5285,8 @@ getTables(Archive *fout, int *numTables)
 						  "OR %s IS NOT NULL "
 						  "OR %s IS NOT NULL"
 						  "))"
-						  "AS changed_acl "
+						  "AS changed_acl, "
+						  "CASE WHEN c.relkind = 'P' THEN pg_catalog.pg_get_partkeydef(c.oid) ELSE NULL END AS partkeydef "
 						  "FROM pg_class c "
 						  "LEFT JOIN pg_depend d ON "
 						  "(c.relkind = '%c' AND "
@@ -5292,7 +5298,7 @@ getTables(Archive *fout, int *numTables)
 						  "(c.oid = pip.objoid "
 						  "AND pip.classoid = 'pg_class'::regclass "
 						  "AND pip.objsubid = 0) "
-				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c') "
+				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c', '%c') "
 						  "ORDER BY c.oid",
 						  acl_subquery->data,
 						  racl_subquery->data,
@@ -5306,7 +5312,8 @@ getTables(Archive *fout, int *numTables)
 						  RELKIND_SEQUENCE,
 						  RELKIND_RELATION, RELKIND_SEQUENCE,
 						  RELKIND_VIEW, RELKIND_COMPOSITE_TYPE,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 
 		destroyPQExpBuffer(acl_subquery);
 		destroyPQExpBuffer(racl_subquery);
@@ -5868,6 +5875,7 @@ getTables(Archive *fout, int *numTables)
 	i_toastreloptions = PQfnumber(res, "toast_reloptions");
 	i_reloftype = PQfnumber(res, "reloftype");
 	i_changed_acl = PQfnumber(res, "changed_acl");
+	i_partkeydef = PQfnumber(res, "partkeydef");
 
 	if (dopt->lockWaitTimeout && fout->remoteVersion >= 70300)
 	{
@@ -5938,6 +5946,7 @@ getTables(Archive *fout, int *numTables)
 		else
 			tblinfo[i].checkoption = pg_strdup(PQgetvalue(res, i, i_checkoption));
 		tblinfo[i].toast_reloptions = pg_strdup(PQgetvalue(res, i, i_toastreloptions));
+		tblinfo[i].partkeydef = pg_strdup(PQgetvalue(res, i, i_partkeydef));
 
 		/* other fields were zeroed above */
 
@@ -5982,7 +5991,9 @@ getTables(Archive *fout, int *numTables)
 		 * We only need to lock the table for certain components; see
 		 * pg_dump.h
 		 */
-		if (tblinfo[i].dobj.dump && tblinfo[i].relkind == RELKIND_RELATION &&
+		if (tblinfo[i].dobj.dump &&
+			(tblinfo[i].relkind == RELKIND_RELATION ||
+			 tblinfo->relkind == RELKIND_PARTITIONED_TABLE) &&
 			(tblinfo[i].dobj.dump & DUMP_COMPONENTS_REQUIRING_LOCK))
 		{
 			resetPQExpBuffer(query);
@@ -6084,7 +6095,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);
 
@@ -15437,6 +15451,9 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 				appendPQExpBufferChar(q, ')');
 			}
 
+			if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+				appendPQExpBuffer(q, "\nPARTITION BY %s", tbinfo->partkeydef);
+
 			if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 				appendPQExpBuffer(q, "\nSERVER %s", fmtId(srvname));
 		}
@@ -15497,6 +15514,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 		 */
 		if (dopt->binary_upgrade &&
 			(tbinfo->relkind == RELKIND_RELATION ||
+			 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 			 tbinfo->relkind == RELKIND_FOREIGN_TABLE))
 		{
 			for (j = 0; j < tbinfo->numatts; j++)
@@ -15515,7 +15533,8 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 					appendStringLiteralAH(q, fmtId(tbinfo->dobj.name), fout);
 					appendPQExpBufferStr(q, "::pg_catalog.regclass;\n");
 
-					if (tbinfo->relkind == RELKIND_RELATION)
+					if (tbinfo->relkind == RELKIND_RELATION ||
+						tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
 						appendPQExpBuffer(q, "ALTER TABLE ONLY %s ",
 										  fmtId(tbinfo->dobj.name));
 					else
@@ -15732,6 +15751,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 	 * dump properties we only have ALTER TABLE syntax for
 	 */
 	if ((tbinfo->relkind == RELKIND_RELATION ||
+		 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 		 tbinfo->relkind == RELKIND_MATVIEW) &&
 		tbinfo->relreplident != REPLICA_IDENTITY_DEFAULT)
 	{
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 2bfa2d9..0292859 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -310,6 +310,7 @@ typedef struct _tableInfo
 	bool	   *inhNotNull;		/* true if NOT NULL is inherited */
 	struct _attrDefInfo **attrdefs;		/* DEFAULT expressions */
 	struct _constraintInfo *checkexprs; /* CHECK constraints */
+	char	   *partkeydef;		/* partition key definition */
 
 	/*
 	 * Stuff computed only for dumpable tables.
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 6275a68..10d924a 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -861,6 +861,7 @@ permissionsList(const char *pattern)
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'S' THEN '%s'"
@@ -870,6 +871,7 @@ permissionsList(const char *pattern)
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("sequence"),
@@ -920,7 +922,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
@@ -1567,8 +1569,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)");
 	}
 
@@ -1633,6 +1635,14 @@ describeOneTableDetails(const char *schemaname,
 			printfPQExpBuffer(&title, _("Foreign table \"%s.%s\""),
 							  schemaname, relationname);
 			break;
+		case 'P':
+			if (tableinfo.relpersistence == 'u')
+				printfPQExpBuffer(&title, _("Unlogged table \"%s.%s\""),
+								  schemaname, relationname);
+			else
+				printfPQExpBuffer(&title, _("Table \"%s.%s\""),
+								  schemaname, relationname);
+			break;
 		default:
 			/* untranslated unknown relkind */
 			printfPQExpBuffer(&title, "?%c? \"%s.%s\"",
@@ -1646,8 +1656,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");
@@ -1667,12 +1677,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");
 	}
 
@@ -1772,7 +1782,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);
@@ -1780,14 +1790,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 (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 */
@@ -1926,7 +1955,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;
@@ -2485,7 +2514,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;
@@ -2696,7 +2725,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
@@ -3024,6 +3053,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'i' THEN '%s'"
@@ -3035,6 +3065,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("index"),
@@ -3079,7 +3110,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 019f75a..5e5c370 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/pg_proc.h b/src/include/catalog/pg_proc.h
index e2d08ba..b45688b 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -1980,6 +1980,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/utils/builtins.h b/src/include/utils/builtins.h
index 2ae212a..e800647 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -729,6 +729,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/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 708232d..2fec847 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -408,3 +408,29 @@ CREATE TABLE no_inh_con_parted (
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
 ERROR:  cannot add NO INHERIT constraint to partitioned table "no_inh_con_parted"
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+Table "public.describe_range_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition Key: RANGE ((a + b))
+
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+Table "public.describe_list_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition Key: LIST (a)
+
+DROP TABLE describe_range_key, describe_list_key;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 6e4a8be..4dd6a0a 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -402,3 +402,16 @@ CREATE TABLE no_inh_con_parted (
 	a int,
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
+
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+DROP TABLE describe_range_key, describe_list_key;
-- 
1.7.1


--------------6EB5B318125CB7DB1A4FD229
Content-Type: text/x-diff;
 name="0003-Catalog-and-DDL-for-partitions-3.patch"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
 filename="0003-Catalog-and-DDL-for-partitions-3.patch"



^ permalink  raw  reply  [nested|flat] 110+ messages in thread

* [PATCH 2/9] psql and pg_dump support for partitioned tables.
@ 2016-07-12 08:20  amit <[email protected]>
  0 siblings, 0 replies; 110+ messages in thread

From: amit @ 2016-07-12 08:20 UTC (permalink / raw)

Takes care of both the partition key deparse stuff and the new relkind.
---
 src/backend/utils/adt/ruleutils.c          |  140 ++++++++++++++++++++++++++++
 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/pg_proc.h              |    2 +
 src/include/utils/builtins.h               |    1 +
 src/test/regress/expected/create_table.out |   26 +++++
 src/test/regress/sql/create_table.sql      |   13 +++
 9 files changed, 260 insertions(+), 26 deletions(-)

diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 8a81d7a..03be202 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_table.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
@@ -315,6 +316,7 @@ static char *pg_get_indexdef_worker(Oid indexrelid, int colno,
 					   const Oid *excludeOps,
 					   bool attrsOnly, bool showTblSpc,
 					   int prettyFlags, bool missing_ok);
+static char *pg_get_partkeydef_worker(Oid relid, int prettyFlags);
 static char *pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 							int prettyFlags, bool missing_ok);
 static text *pg_get_expr_worker(text *expr, Oid relid, const char *relname,
@@ -1389,6 +1391,144 @@ 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 [ opclass_name ] [, ...])
+ */
+Datum
+pg_get_partkeydef(PG_FUNCTION_ARGS)
+{
+	Oid			relid = PG_GETARG_OID(0);
+	int			prettyFlags;
+
+	prettyFlags = PRETTYFLAG_INDENT;
+	PG_RETURN_TEXT_P(string_to_text(pg_get_partkeydef_worker(relid,
+									prettyFlags)));
+}
+
+/*
+ * Internal workhorse to decompile a partition key definition.
+ */
+static char *
+pg_get_partkeydef_worker(Oid relid, int prettyFlags)
+{
+	Form_pg_partitioned_table	form;
+	HeapTuple	tuple;
+	oidvector  *partclass;
+	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_table) GETSTRUCT(tuple);
+
+	Assert(form->partrelid == relid);
+
+	/* Must get partclass, and partexprs the hard way */
+	datum = SysCacheGetAttr(PARTEDRELID, tuple,
+							Anum_pg_partitioned_table_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_table_partexprbin))
+	{
+		Datum		exprsDatum;
+		bool		isnull;
+		char	   *exprsString;
+
+		exprsDatum = SysCacheGetAttr(PARTEDRELID, tuple,
+									 Anum_pg_partitioned_table_partexprbin, &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);
+
+	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,
+											0, 0);
+
+			appendStringInfoString(&buf, 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/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index ba9c276..c805a84 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -1253,9 +1253,10 @@ expand_table_name_patterns(Archive *fout,
 						  "SELECT c.oid"
 						  "\nFROM pg_catalog.pg_class c"
 		"\n     LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace"
-					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c')\n",
+					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c')\n",
 						  RELKIND_RELATION, RELKIND_SEQUENCE, RELKIND_VIEW,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 		processSQLNamePattern(GetConnection(fout), query, cell->val, true,
 							  false, "n.nspname", "c.relname", NULL,
 							  "pg_catalog.pg_table_is_visible(c.oid)");
@@ -2125,6 +2126,9 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo, bool oids)
 	/* Skip FOREIGN TABLEs (no data to dump) */
 	if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 		return;
+	/* Skip partitioned tables (data in partitions) */
+	if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+		return;
 
 	/* Don't dump data in unlogged tables, if so requested */
 	if (tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED &&
@@ -5204,6 +5208,7 @@ getTables(Archive *fout, int *numTables)
 	int			i_reloftype;
 	int			i_relpages;
 	int			i_changed_acl;
+	int			i_partkeydef;
 
 	/* Make sure we are in proper schema */
 	selectSourceSchema(fout, "pg_catalog");
@@ -5289,7 +5294,8 @@ getTables(Archive *fout, int *numTables)
 						  "OR %s IS NOT NULL "
 						  "OR %s IS NOT NULL"
 						  "))"
-						  "AS changed_acl "
+						  "AS changed_acl, "
+						  "CASE WHEN c.relkind = 'P' THEN pg_catalog.pg_get_partkeydef(c.oid) ELSE NULL END AS partkeydef "
 						  "FROM pg_class c "
 						  "LEFT JOIN pg_depend d ON "
 						  "(c.relkind = '%c' AND "
@@ -5301,7 +5307,7 @@ getTables(Archive *fout, int *numTables)
 						  "(c.oid = pip.objoid "
 						  "AND pip.classoid = 'pg_class'::regclass "
 						  "AND pip.objsubid = 0) "
-				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c') "
+				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c', '%c') "
 						  "ORDER BY c.oid",
 						  acl_subquery->data,
 						  racl_subquery->data,
@@ -5315,7 +5321,8 @@ getTables(Archive *fout, int *numTables)
 						  RELKIND_SEQUENCE,
 						  RELKIND_RELATION, RELKIND_SEQUENCE,
 						  RELKIND_VIEW, RELKIND_COMPOSITE_TYPE,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 
 		destroyPQExpBuffer(acl_subquery);
 		destroyPQExpBuffer(racl_subquery);
@@ -5877,6 +5884,7 @@ getTables(Archive *fout, int *numTables)
 	i_toastreloptions = PQfnumber(res, "toast_reloptions");
 	i_reloftype = PQfnumber(res, "reloftype");
 	i_changed_acl = PQfnumber(res, "changed_acl");
+	i_partkeydef = PQfnumber(res, "partkeydef");
 
 	if (dopt->lockWaitTimeout && fout->remoteVersion >= 70300)
 	{
@@ -5947,6 +5955,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 */
 
@@ -5991,7 +6000,9 @@ getTables(Archive *fout, int *numTables)
 		 * We only need to lock the table for certain components; see
 		 * pg_dump.h
 		 */
-		if (tblinfo[i].dobj.dump && tblinfo[i].relkind == RELKIND_RELATION &&
+		if (tblinfo[i].dobj.dump &&
+			(tblinfo[i].relkind == RELKIND_RELATION ||
+			 tblinfo->relkind == RELKIND_PARTITIONED_TABLE) &&
 			(tblinfo[i].dobj.dump & DUMP_COMPONENTS_REQUIRING_LOCK))
 		{
 			resetPQExpBuffer(query);
@@ -6093,7 +6104,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);
 
@@ -15446,6 +15460,9 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 				appendPQExpBufferChar(q, ')');
 			}
 
+			if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+				appendPQExpBuffer(q, "\nPARTITION BY %s", tbinfo->partkeydef);
+
 			if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 				appendPQExpBuffer(q, "\nSERVER %s", fmtId(srvname));
 		}
@@ -15506,6 +15523,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 		 */
 		if (dopt->binary_upgrade &&
 			(tbinfo->relkind == RELKIND_RELATION ||
+			 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 			 tbinfo->relkind == RELKIND_FOREIGN_TABLE))
 		{
 			for (j = 0; j < tbinfo->numatts; j++)
@@ -15524,7 +15542,8 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 					appendStringLiteralAH(q, fmtId(tbinfo->dobj.name), fout);
 					appendPQExpBufferStr(q, "::pg_catalog.regclass;\n");
 
-					if (tbinfo->relkind == RELKIND_RELATION)
+					if (tbinfo->relkind == RELKIND_RELATION ||
+						tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
 						appendPQExpBuffer(q, "ALTER TABLE ONLY %s ",
 										  fmtId(tbinfo->dobj.name));
 					else
@@ -15741,6 +15760,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 	 * dump properties we only have ALTER TABLE syntax for
 	 */
 	if ((tbinfo->relkind == RELKIND_RELATION ||
+		 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 		 tbinfo->relkind == RELKIND_MATVIEW) &&
 		tbinfo->relreplident != REPLICA_IDENTITY_DEFAULT)
 	{
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 2bfa2d9..0292859 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -310,6 +310,7 @@ typedef struct _tableInfo
 	bool	   *inhNotNull;		/* true if NOT NULL is inherited */
 	struct _attrDefInfo **attrdefs;		/* DEFAULT expressions */
 	struct _constraintInfo *checkexprs; /* CHECK constraints */
+	char	   *partkeydef;		/* partition key definition */
 
 	/*
 	 * Stuff computed only for dumpable tables.
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 6275a68..10d924a 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -861,6 +861,7 @@ permissionsList(const char *pattern)
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'S' THEN '%s'"
@@ -870,6 +871,7 @@ permissionsList(const char *pattern)
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("sequence"),
@@ -920,7 +922,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
@@ -1567,8 +1569,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)");
 	}
 
@@ -1633,6 +1635,14 @@ describeOneTableDetails(const char *schemaname,
 			printfPQExpBuffer(&title, _("Foreign table \"%s.%s\""),
 							  schemaname, relationname);
 			break;
+		case 'P':
+			if (tableinfo.relpersistence == 'u')
+				printfPQExpBuffer(&title, _("Unlogged table \"%s.%s\""),
+								  schemaname, relationname);
+			else
+				printfPQExpBuffer(&title, _("Table \"%s.%s\""),
+								  schemaname, relationname);
+			break;
 		default:
 			/* untranslated unknown relkind */
 			printfPQExpBuffer(&title, "?%c? \"%s.%s\"",
@@ -1646,8 +1656,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");
@@ -1667,12 +1677,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");
 	}
 
@@ -1772,7 +1782,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);
@@ -1780,14 +1790,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 (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 */
@@ -1926,7 +1955,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;
@@ -2485,7 +2514,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;
@@ -2696,7 +2725,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
@@ -3024,6 +3053,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'i' THEN '%s'"
@@ -3035,6 +3065,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("index"),
@@ -3079,7 +3110,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 019f75a..5e5c370 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/pg_proc.h b/src/include/catalog/pg_proc.h
index e2d08ba..b45688b 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -1980,6 +1980,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/utils/builtins.h b/src/include/utils/builtins.h
index 2ae212a..e800647 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -729,6 +729,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/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 708232d..2fec847 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -408,3 +408,29 @@ CREATE TABLE no_inh_con_parted (
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
 ERROR:  cannot add NO INHERIT constraint to partitioned table "no_inh_con_parted"
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+Table "public.describe_range_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition Key: RANGE ((a + b))
+
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+Table "public.describe_list_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition Key: LIST (a)
+
+DROP TABLE describe_range_key, describe_list_key;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 6e4a8be..4dd6a0a 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -402,3 +402,16 @@ CREATE TABLE no_inh_con_parted (
 	a int,
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
+
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+DROP TABLE describe_range_key, describe_list_key;
-- 
1.7.1


--------------4012470FC48539D6F6714CEA
Content-Type: text/x-diff;
 name="0003-Catalog-and-DDL-for-partitions-4.patch"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
 filename="0003-Catalog-and-DDL-for-partitions-4.patch"



^ permalink  raw  reply  [nested|flat] 110+ messages in thread

* [PATCH 2/9] psql and pg_dump support for partitioned tables.
@ 2016-07-12 08:20  amit <[email protected]>
  0 siblings, 0 replies; 110+ messages in thread

From: amit @ 2016-07-12 08:20 UTC (permalink / raw)

Takes care of both the partition key deparse stuff and the new relkind.
---
 src/backend/utils/adt/ruleutils.c          |  140 ++++++++++++++++++++++++++++
 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/pg_proc.h              |    2 +
 src/include/utils/builtins.h               |    1 +
 src/test/regress/expected/create_table.out |   26 +++++
 src/test/regress/sql/create_table.sql      |   13 +++
 9 files changed, 260 insertions(+), 26 deletions(-)

diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 8a81d7a..03be202 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_table.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
@@ -315,6 +316,7 @@ static char *pg_get_indexdef_worker(Oid indexrelid, int colno,
 					   const Oid *excludeOps,
 					   bool attrsOnly, bool showTblSpc,
 					   int prettyFlags, bool missing_ok);
+static char *pg_get_partkeydef_worker(Oid relid, int prettyFlags);
 static char *pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 							int prettyFlags, bool missing_ok);
 static text *pg_get_expr_worker(text *expr, Oid relid, const char *relname,
@@ -1389,6 +1391,144 @@ 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 [ opclass_name ] [, ...])
+ */
+Datum
+pg_get_partkeydef(PG_FUNCTION_ARGS)
+{
+	Oid			relid = PG_GETARG_OID(0);
+	int			prettyFlags;
+
+	prettyFlags = PRETTYFLAG_INDENT;
+	PG_RETURN_TEXT_P(string_to_text(pg_get_partkeydef_worker(relid,
+									prettyFlags)));
+}
+
+/*
+ * Internal workhorse to decompile a partition key definition.
+ */
+static char *
+pg_get_partkeydef_worker(Oid relid, int prettyFlags)
+{
+	Form_pg_partitioned_table	form;
+	HeapTuple	tuple;
+	oidvector  *partclass;
+	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_table) GETSTRUCT(tuple);
+
+	Assert(form->partrelid == relid);
+
+	/* Must get partclass, and partexprs the hard way */
+	datum = SysCacheGetAttr(PARTEDRELID, tuple,
+							Anum_pg_partitioned_table_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_table_partexprbin))
+	{
+		Datum		exprsDatum;
+		bool		isnull;
+		char	   *exprsString;
+
+		exprsDatum = SysCacheGetAttr(PARTEDRELID, tuple,
+									 Anum_pg_partitioned_table_partexprbin, &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);
+
+	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,
+											0, 0);
+
+			appendStringInfoString(&buf, 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/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index ba9c276..c805a84 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -1253,9 +1253,10 @@ expand_table_name_patterns(Archive *fout,
 						  "SELECT c.oid"
 						  "\nFROM pg_catalog.pg_class c"
 		"\n     LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace"
-					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c')\n",
+					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c')\n",
 						  RELKIND_RELATION, RELKIND_SEQUENCE, RELKIND_VIEW,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 		processSQLNamePattern(GetConnection(fout), query, cell->val, true,
 							  false, "n.nspname", "c.relname", NULL,
 							  "pg_catalog.pg_table_is_visible(c.oid)");
@@ -2125,6 +2126,9 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo, bool oids)
 	/* Skip FOREIGN TABLEs (no data to dump) */
 	if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 		return;
+	/* Skip partitioned tables (data in partitions) */
+	if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+		return;
 
 	/* Don't dump data in unlogged tables, if so requested */
 	if (tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED &&
@@ -5204,6 +5208,7 @@ getTables(Archive *fout, int *numTables)
 	int			i_reloftype;
 	int			i_relpages;
 	int			i_changed_acl;
+	int			i_partkeydef;
 
 	/* Make sure we are in proper schema */
 	selectSourceSchema(fout, "pg_catalog");
@@ -5289,7 +5294,8 @@ getTables(Archive *fout, int *numTables)
 						  "OR %s IS NOT NULL "
 						  "OR %s IS NOT NULL"
 						  "))"
-						  "AS changed_acl "
+						  "AS changed_acl, "
+						  "CASE WHEN c.relkind = 'P' THEN pg_catalog.pg_get_partkeydef(c.oid) ELSE NULL END AS partkeydef "
 						  "FROM pg_class c "
 						  "LEFT JOIN pg_depend d ON "
 						  "(c.relkind = '%c' AND "
@@ -5301,7 +5307,7 @@ getTables(Archive *fout, int *numTables)
 						  "(c.oid = pip.objoid "
 						  "AND pip.classoid = 'pg_class'::regclass "
 						  "AND pip.objsubid = 0) "
-				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c') "
+				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c', '%c') "
 						  "ORDER BY c.oid",
 						  acl_subquery->data,
 						  racl_subquery->data,
@@ -5315,7 +5321,8 @@ getTables(Archive *fout, int *numTables)
 						  RELKIND_SEQUENCE,
 						  RELKIND_RELATION, RELKIND_SEQUENCE,
 						  RELKIND_VIEW, RELKIND_COMPOSITE_TYPE,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 
 		destroyPQExpBuffer(acl_subquery);
 		destroyPQExpBuffer(racl_subquery);
@@ -5877,6 +5884,7 @@ getTables(Archive *fout, int *numTables)
 	i_toastreloptions = PQfnumber(res, "toast_reloptions");
 	i_reloftype = PQfnumber(res, "reloftype");
 	i_changed_acl = PQfnumber(res, "changed_acl");
+	i_partkeydef = PQfnumber(res, "partkeydef");
 
 	if (dopt->lockWaitTimeout && fout->remoteVersion >= 70300)
 	{
@@ -5947,6 +5955,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 */
 
@@ -5991,7 +6000,9 @@ getTables(Archive *fout, int *numTables)
 		 * We only need to lock the table for certain components; see
 		 * pg_dump.h
 		 */
-		if (tblinfo[i].dobj.dump && tblinfo[i].relkind == RELKIND_RELATION &&
+		if (tblinfo[i].dobj.dump &&
+			(tblinfo[i].relkind == RELKIND_RELATION ||
+			 tblinfo->relkind == RELKIND_PARTITIONED_TABLE) &&
 			(tblinfo[i].dobj.dump & DUMP_COMPONENTS_REQUIRING_LOCK))
 		{
 			resetPQExpBuffer(query);
@@ -6093,7 +6104,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);
 
@@ -15446,6 +15460,9 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 				appendPQExpBufferChar(q, ')');
 			}
 
+			if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+				appendPQExpBuffer(q, "\nPARTITION BY %s", tbinfo->partkeydef);
+
 			if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 				appendPQExpBuffer(q, "\nSERVER %s", fmtId(srvname));
 		}
@@ -15506,6 +15523,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 		 */
 		if (dopt->binary_upgrade &&
 			(tbinfo->relkind == RELKIND_RELATION ||
+			 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 			 tbinfo->relkind == RELKIND_FOREIGN_TABLE))
 		{
 			for (j = 0; j < tbinfo->numatts; j++)
@@ -15524,7 +15542,8 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 					appendStringLiteralAH(q, fmtId(tbinfo->dobj.name), fout);
 					appendPQExpBufferStr(q, "::pg_catalog.regclass;\n");
 
-					if (tbinfo->relkind == RELKIND_RELATION)
+					if (tbinfo->relkind == RELKIND_RELATION ||
+						tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
 						appendPQExpBuffer(q, "ALTER TABLE ONLY %s ",
 										  fmtId(tbinfo->dobj.name));
 					else
@@ -15741,6 +15760,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 	 * dump properties we only have ALTER TABLE syntax for
 	 */
 	if ((tbinfo->relkind == RELKIND_RELATION ||
+		 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 		 tbinfo->relkind == RELKIND_MATVIEW) &&
 		tbinfo->relreplident != REPLICA_IDENTITY_DEFAULT)
 	{
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 2bfa2d9..0292859 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -310,6 +310,7 @@ typedef struct _tableInfo
 	bool	   *inhNotNull;		/* true if NOT NULL is inherited */
 	struct _attrDefInfo **attrdefs;		/* DEFAULT expressions */
 	struct _constraintInfo *checkexprs; /* CHECK constraints */
+	char	   *partkeydef;		/* partition key definition */
 
 	/*
 	 * Stuff computed only for dumpable tables.
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 6275a68..10d924a 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -861,6 +861,7 @@ permissionsList(const char *pattern)
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'S' THEN '%s'"
@@ -870,6 +871,7 @@ permissionsList(const char *pattern)
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("sequence"),
@@ -920,7 +922,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
@@ -1567,8 +1569,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)");
 	}
 
@@ -1633,6 +1635,14 @@ describeOneTableDetails(const char *schemaname,
 			printfPQExpBuffer(&title, _("Foreign table \"%s.%s\""),
 							  schemaname, relationname);
 			break;
+		case 'P':
+			if (tableinfo.relpersistence == 'u')
+				printfPQExpBuffer(&title, _("Unlogged table \"%s.%s\""),
+								  schemaname, relationname);
+			else
+				printfPQExpBuffer(&title, _("Table \"%s.%s\""),
+								  schemaname, relationname);
+			break;
 		default:
 			/* untranslated unknown relkind */
 			printfPQExpBuffer(&title, "?%c? \"%s.%s\"",
@@ -1646,8 +1656,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");
@@ -1667,12 +1677,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");
 	}
 
@@ -1772,7 +1782,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);
@@ -1780,14 +1790,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 (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 */
@@ -1926,7 +1955,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;
@@ -2485,7 +2514,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;
@@ -2696,7 +2725,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
@@ -3024,6 +3053,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'i' THEN '%s'"
@@ -3035,6 +3065,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("index"),
@@ -3079,7 +3110,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 50a45eb..8284a9c 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/pg_proc.h b/src/include/catalog/pg_proc.h
index e2d08ba..b45688b 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -1980,6 +1980,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/utils/builtins.h b/src/include/utils/builtins.h
index 2ae212a..e800647 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -729,6 +729,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/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 0af1422..91ba1be 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -412,3 +412,29 @@ CREATE TABLE no_inh_con_parted (
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
 ERROR:  cannot add NO INHERIT constraint to partitioned table "no_inh_con_parted"
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+Table "public.describe_range_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition Key: RANGE ((a + b))
+
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+Table "public.describe_list_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition Key: LIST (a)
+
+DROP TABLE describe_range_key, describe_list_key;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 418ac55..b75542d 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -406,3 +406,16 @@ CREATE TABLE no_inh_con_parted (
 	a int,
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
+
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+DROP TABLE describe_range_key, describe_list_key;
-- 
1.7.1


--------------D008BA677674C4836D7B65AD
Content-Type: text/x-diff;
 name="0003-Catalog-and-DDL-for-partitions-5.patch"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
 filename="0003-Catalog-and-DDL-for-partitions-5.patch"



^ permalink  raw  reply  [nested|flat] 110+ messages in thread

* [PATCH 2/9] psql and pg_dump support for partitioned tables.
@ 2016-07-12 08:20  amit <[email protected]>
  0 siblings, 0 replies; 110+ messages in thread

From: amit @ 2016-07-12 08:20 UTC (permalink / raw)

Takes care of both the partition key deparse stuff and the new relkind.
---
 src/backend/utils/adt/ruleutils.c          |  140 ++++++++++++++++++++++++++++
 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/pg_proc.h              |    2 +
 src/include/utils/builtins.h               |    1 +
 src/test/regress/expected/create_table.out |   26 +++++
 src/test/regress/sql/create_table.sql      |   13 +++
 9 files changed, 260 insertions(+), 26 deletions(-)

diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 8a81d7a..03be202 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_table.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
@@ -315,6 +316,7 @@ static char *pg_get_indexdef_worker(Oid indexrelid, int colno,
 					   const Oid *excludeOps,
 					   bool attrsOnly, bool showTblSpc,
 					   int prettyFlags, bool missing_ok);
+static char *pg_get_partkeydef_worker(Oid relid, int prettyFlags);
 static char *pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 							int prettyFlags, bool missing_ok);
 static text *pg_get_expr_worker(text *expr, Oid relid, const char *relname,
@@ -1389,6 +1391,144 @@ 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 [ opclass_name ] [, ...])
+ */
+Datum
+pg_get_partkeydef(PG_FUNCTION_ARGS)
+{
+	Oid			relid = PG_GETARG_OID(0);
+	int			prettyFlags;
+
+	prettyFlags = PRETTYFLAG_INDENT;
+	PG_RETURN_TEXT_P(string_to_text(pg_get_partkeydef_worker(relid,
+									prettyFlags)));
+}
+
+/*
+ * Internal workhorse to decompile a partition key definition.
+ */
+static char *
+pg_get_partkeydef_worker(Oid relid, int prettyFlags)
+{
+	Form_pg_partitioned_table	form;
+	HeapTuple	tuple;
+	oidvector  *partclass;
+	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_table) GETSTRUCT(tuple);
+
+	Assert(form->partrelid == relid);
+
+	/* Must get partclass, and partexprs the hard way */
+	datum = SysCacheGetAttr(PARTEDRELID, tuple,
+							Anum_pg_partitioned_table_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_table_partexprbin))
+	{
+		Datum		exprsDatum;
+		bool		isnull;
+		char	   *exprsString;
+
+		exprsDatum = SysCacheGetAttr(PARTEDRELID, tuple,
+									 Anum_pg_partitioned_table_partexprbin, &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);
+
+	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,
+											0, 0);
+
+			appendStringInfoString(&buf, 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/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 51b8a1a..854dddc 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -1253,9 +1253,10 @@ expand_table_name_patterns(Archive *fout,
 						  "SELECT c.oid"
 						  "\nFROM pg_catalog.pg_class c"
 		"\n     LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace"
-					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c')\n",
+					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c')\n",
 						  RELKIND_RELATION, RELKIND_SEQUENCE, RELKIND_VIEW,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 		processSQLNamePattern(GetConnection(fout), query, cell->val, true,
 							  false, "n.nspname", "c.relname", NULL,
 							  "pg_catalog.pg_table_is_visible(c.oid)");
@@ -2125,6 +2126,9 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo, bool oids)
 	/* Skip FOREIGN TABLEs (no data to dump) */
 	if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 		return;
+	/* Skip partitioned tables (data in partitions) */
+	if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+		return;
 
 	/* Don't dump data in unlogged tables, if so requested */
 	if (tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED &&
@@ -5211,6 +5215,7 @@ getTables(Archive *fout, int *numTables)
 	int			i_reloftype;
 	int			i_relpages;
 	int			i_changed_acl;
+	int			i_partkeydef;
 
 	/* Make sure we are in proper schema */
 	selectSourceSchema(fout, "pg_catalog");
@@ -5296,7 +5301,8 @@ getTables(Archive *fout, int *numTables)
 						  "OR %s IS NOT NULL "
 						  "OR %s IS NOT NULL"
 						  "))"
-						  "AS changed_acl "
+						  "AS changed_acl, "
+						  "CASE WHEN c.relkind = 'P' THEN pg_catalog.pg_get_partkeydef(c.oid) ELSE NULL END AS partkeydef "
 						  "FROM pg_class c "
 						  "LEFT JOIN pg_depend d ON "
 						  "(c.relkind = '%c' AND "
@@ -5308,7 +5314,7 @@ getTables(Archive *fout, int *numTables)
 						  "(c.oid = pip.objoid "
 						  "AND pip.classoid = 'pg_class'::regclass "
 						  "AND pip.objsubid = 0) "
-				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c') "
+				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c', '%c') "
 						  "ORDER BY c.oid",
 						  acl_subquery->data,
 						  racl_subquery->data,
@@ -5322,7 +5328,8 @@ getTables(Archive *fout, int *numTables)
 						  RELKIND_SEQUENCE,
 						  RELKIND_RELATION, RELKIND_SEQUENCE,
 						  RELKIND_VIEW, RELKIND_COMPOSITE_TYPE,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 
 		destroyPQExpBuffer(acl_subquery);
 		destroyPQExpBuffer(racl_subquery);
@@ -5884,6 +5891,7 @@ getTables(Archive *fout, int *numTables)
 	i_toastreloptions = PQfnumber(res, "toast_reloptions");
 	i_reloftype = PQfnumber(res, "reloftype");
 	i_changed_acl = PQfnumber(res, "changed_acl");
+	i_partkeydef = PQfnumber(res, "partkeydef");
 
 	if (dopt->lockWaitTimeout && fout->remoteVersion >= 70300)
 	{
@@ -5954,6 +5962,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 */
 
@@ -5998,7 +6007,9 @@ getTables(Archive *fout, int *numTables)
 		 * We only need to lock the table for certain components; see
 		 * pg_dump.h
 		 */
-		if (tblinfo[i].dobj.dump && tblinfo[i].relkind == RELKIND_RELATION &&
+		if (tblinfo[i].dobj.dump &&
+			(tblinfo[i].relkind == RELKIND_RELATION ||
+			 tblinfo->relkind == RELKIND_PARTITIONED_TABLE) &&
 			(tblinfo[i].dobj.dump & DUMP_COMPONENTS_REQUIRING_LOCK))
 		{
 			resetPQExpBuffer(query);
@@ -6100,7 +6111,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);
 
@@ -15453,6 +15467,9 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 				appendPQExpBufferChar(q, ')');
 			}
 
+			if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+				appendPQExpBuffer(q, "\nPARTITION BY %s", tbinfo->partkeydef);
+
 			if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 				appendPQExpBuffer(q, "\nSERVER %s", fmtId(srvname));
 		}
@@ -15513,6 +15530,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 		 */
 		if (dopt->binary_upgrade &&
 			(tbinfo->relkind == RELKIND_RELATION ||
+			 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 			 tbinfo->relkind == RELKIND_FOREIGN_TABLE))
 		{
 			for (j = 0; j < tbinfo->numatts; j++)
@@ -15531,7 +15549,8 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 					appendStringLiteralAH(q, fmtId(tbinfo->dobj.name), fout);
 					appendPQExpBufferStr(q, "::pg_catalog.regclass;\n");
 
-					if (tbinfo->relkind == RELKIND_RELATION)
+					if (tbinfo->relkind == RELKIND_RELATION ||
+						tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
 						appendPQExpBuffer(q, "ALTER TABLE ONLY %s ",
 										  fmtId(tbinfo->dobj.name));
 					else
@@ -15748,6 +15767,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 	 * dump properties we only have ALTER TABLE syntax for
 	 */
 	if ((tbinfo->relkind == RELKIND_RELATION ||
+		 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 		 tbinfo->relkind == RELKIND_MATVIEW) &&
 		tbinfo->relreplident != REPLICA_IDENTITY_DEFAULT)
 	{
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 2bfa2d9..0292859 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -310,6 +310,7 @@ typedef struct _tableInfo
 	bool	   *inhNotNull;		/* true if NOT NULL is inherited */
 	struct _attrDefInfo **attrdefs;		/* DEFAULT expressions */
 	struct _constraintInfo *checkexprs; /* CHECK constraints */
+	char	   *partkeydef;		/* partition key definition */
 
 	/*
 	 * Stuff computed only for dumpable tables.
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 6275a68..db6dc5c 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -861,6 +861,7 @@ permissionsList(const char *pattern)
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'S' THEN '%s'"
@@ -870,6 +871,7 @@ permissionsList(const char *pattern)
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("sequence"),
@@ -920,7 +922,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
@@ -1567,8 +1569,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)");
 	}
 
@@ -1633,6 +1635,14 @@ describeOneTableDetails(const char *schemaname,
 			printfPQExpBuffer(&title, _("Foreign table \"%s.%s\""),
 							  schemaname, relationname);
 			break;
+		case 'P':
+			if (tableinfo.relpersistence == 'u')
+				printfPQExpBuffer(&title, _("Unlogged table \"%s.%s\""),
+								  schemaname, relationname);
+			else
+				printfPQExpBuffer(&title, _("Table \"%s.%s\""),
+								  schemaname, relationname);
+			break;
 		default:
 			/* untranslated unknown relkind */
 			printfPQExpBuffer(&title, "?%c? \"%s.%s\"",
@@ -1646,8 +1656,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");
@@ -1667,12 +1677,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");
 	}
 
@@ -1772,7 +1782,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);
@@ -1780,14 +1790,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 (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 */
@@ -1926,7 +1955,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;
@@ -2485,7 +2514,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;
@@ -2696,7 +2725,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
@@ -3024,6 +3053,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'i' THEN '%s'"
@@ -3035,6 +3065,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("index"),
@@ -3079,7 +3110,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 50a45eb..8284a9c 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/pg_proc.h b/src/include/catalog/pg_proc.h
index e2d08ba..b45688b 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -1980,6 +1980,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/utils/builtins.h b/src/include/utils/builtins.h
index 2ae212a..e800647 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -729,6 +729,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/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 1394362..260ec34 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -407,3 +407,29 @@ CREATE TABLE no_inh_con_parted (
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
 ERROR:  cannot add NO INHERIT constraint to partitioned table "no_inh_con_parted"
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+Table "public.describe_range_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition key: RANGE ((a + b))
+
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+Table "public.describe_list_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition key: LIST (a)
+
+DROP TABLE describe_range_key, describe_list_key;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index d383f58..7a1f8de 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -401,3 +401,16 @@ CREATE TABLE no_inh_con_parted (
 	a int,
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
+
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+DROP TABLE describe_range_key, describe_list_key;
-- 
1.7.1


--------------C3235EE96C69D8DAE4DB543E
Content-Type: text/x-diff;
 name="0003-Catalog-and-DDL-for-partitions-6.patch"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
 filename="0003-Catalog-and-DDL-for-partitions-6.patch"



^ permalink  raw  reply  [nested|flat] 110+ messages in thread

* [PATCH 2/8] psql and pg_dump support for partitioned tables.
@ 2016-07-12 08:20  amit <[email protected]>
  0 siblings, 0 replies; 110+ messages in thread

From: amit @ 2016-07-12 08:20 UTC (permalink / raw)

Takes care of both the partition key deparse stuff and the new relkind.
---
 src/backend/utils/adt/ruleutils.c          |  147 ++++++++++++++++++++++++++++
 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/pg_proc.h              |    2 +
 src/include/utils/builtins.h               |    1 +
 src/test/regress/expected/create_table.out |   26 +++++
 src/test/regress/sql/create_table.sql      |   13 +++
 9 files changed, 267 insertions(+), 26 deletions(-)

diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index ec966c7..4691b5b 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, bool missing_ok);
+static char *pg_get_partkeydef_worker(Oid relId, int prettyFlags);
 static char *pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 							int prettyFlags, bool missing_ok);
 static text *pg_get_expr_worker(text *expr, Oid relid, const char *relname,
@@ -1389,6 +1391,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 [ 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/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 4ee10fc..a127672 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -1253,9 +1253,10 @@ expand_table_name_patterns(Archive *fout,
 						  "SELECT c.oid"
 						  "\nFROM pg_catalog.pg_class c"
 		"\n     LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace"
-					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c')\n",
+					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c')\n",
 						  RELKIND_RELATION, RELKIND_SEQUENCE, RELKIND_VIEW,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_REL);
 		processSQLNamePattern(GetConnection(fout), query, cell->val, true,
 							  false, "n.nspname", "c.relname", NULL,
 							  "pg_catalog.pg_table_is_visible(c.oid)");
@@ -2117,6 +2118,9 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo, bool oids)
 	/* Skip FOREIGN TABLEs (no data to dump) */
 	if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 		return;
+	/* Skip partitioned tables (data in partitions) */
+	if (tbinfo->relkind == RELKIND_PARTITIONED_REL)
+		return;
 
 	/* Don't dump data in unlogged tables, if so requested */
 	if (tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED &&
@@ -5195,6 +5199,7 @@ getTables(Archive *fout, int *numTables)
 	int			i_reloftype;
 	int			i_relpages;
 	int			i_changed_acl;
+	int			i_partkeydef;
 
 	/* Make sure we are in proper schema */
 	selectSourceSchema(fout, "pg_catalog");
@@ -5280,7 +5285,8 @@ getTables(Archive *fout, int *numTables)
 						  "OR %s IS NOT NULL "
 						  "OR %s IS NOT NULL"
 						  "))"
-						  "AS changed_acl "
+						  "AS changed_acl, "
+						  "CASE WHEN c.relkind = 'P' THEN pg_catalog.pg_get_partkeydef(c.oid) ELSE NULL END AS partkeydef "
 						  "FROM pg_class c "
 						  "LEFT JOIN pg_depend d ON "
 						  "(c.relkind = '%c' AND "
@@ -5292,7 +5298,7 @@ getTables(Archive *fout, int *numTables)
 						  "(c.oid = pip.objoid "
 						  "AND pip.classoid = 'pg_class'::regclass "
 						  "AND pip.objsubid = 0) "
-				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c') "
+				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c', '%c') "
 						  "ORDER BY c.oid",
 						  acl_subquery->data,
 						  racl_subquery->data,
@@ -5306,7 +5312,8 @@ getTables(Archive *fout, int *numTables)
 						  RELKIND_SEQUENCE,
 						  RELKIND_RELATION, RELKIND_SEQUENCE,
 						  RELKIND_VIEW, RELKIND_COMPOSITE_TYPE,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_REL);
 
 		destroyPQExpBuffer(acl_subquery);
 		destroyPQExpBuffer(racl_subquery);
@@ -5868,6 +5875,7 @@ getTables(Archive *fout, int *numTables)
 	i_toastreloptions = PQfnumber(res, "toast_reloptions");
 	i_reloftype = PQfnumber(res, "reloftype");
 	i_changed_acl = PQfnumber(res, "changed_acl");
+	i_partkeydef = PQfnumber(res, "partkeydef");
 
 	if (dopt->lockWaitTimeout && fout->remoteVersion >= 70300)
 	{
@@ -5938,6 +5946,7 @@ getTables(Archive *fout, int *numTables)
 		else
 			tblinfo[i].checkoption = pg_strdup(PQgetvalue(res, i, i_checkoption));
 		tblinfo[i].toast_reloptions = pg_strdup(PQgetvalue(res, i, i_toastreloptions));
+		tblinfo[i].partkeydef = pg_strdup(PQgetvalue(res, i, i_partkeydef));
 
 		/* other fields were zeroed above */
 
@@ -5982,7 +5991,9 @@ getTables(Archive *fout, int *numTables)
 		 * We only need to lock the table for certain components; see
 		 * pg_dump.h
 		 */
-		if (tblinfo[i].dobj.dump && tblinfo[i].relkind == RELKIND_RELATION &&
+		if (tblinfo[i].dobj.dump &&
+			(tblinfo[i].relkind == RELKIND_RELATION ||
+			 tblinfo->relkind == RELKIND_PARTITIONED_REL) &&
 			(tblinfo[i].dobj.dump & DUMP_COMPONENTS_REQUIRING_LOCK))
 		{
 			resetPQExpBuffer(query);
@@ -6083,7 +6094,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);
 
@@ -15436,6 +15450,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));
 		}
@@ -15496,6 +15513,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++)
@@ -15514,7 +15532,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
@@ -15731,6 +15750,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 	 * dump properties we only have ALTER TABLE syntax for
 	 */
 	if ((tbinfo->relkind == RELKIND_RELATION ||
+		 tbinfo->relkind == RELKIND_PARTITIONED_REL ||
 		 tbinfo->relkind == RELKIND_MATVIEW) &&
 		tbinfo->relreplident != REPLICA_IDENTITY_DEFAULT)
 	{
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 2bfa2d9..0292859 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -310,6 +310,7 @@ typedef struct _tableInfo
 	bool	   *inhNotNull;		/* true if NOT NULL is inherited */
 	struct _attrDefInfo **attrdefs;		/* DEFAULT expressions */
 	struct _constraintInfo *checkexprs; /* CHECK constraints */
+	char	   *partkeydef;		/* partition key definition */
 
 	/*
 	 * Stuff computed only for dumpable tables.
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 27be102..3ba8a90 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -856,6 +856,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"),
@@ -865,6 +866,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");
@@ -911,7 +913,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
@@ -1555,8 +1557,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)");
 	}
 
@@ -1621,6 +1623,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\"",
@@ -1634,8 +1644,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");
@@ -1655,12 +1665,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");
 	}
 
@@ -1760,7 +1770,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);
@@ -1768,14 +1778,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 */
@@ -1914,7 +1943,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;
@@ -2473,7 +2502,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;
@@ -2684,7 +2713,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
@@ -3018,6 +3047,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"),
@@ -3029,6 +3059,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"));
 
@@ -3067,7 +3098,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 
 	appendPQExpBufferStr(&buf, "\nWHERE c.relkind IN (");
 	if (showTables)
-		appendPQExpBufferStr(&buf, "'r',");
+		appendPQExpBufferStr(&buf, "'r', 'P',");
 	if (showViews)
 		appendPQExpBufferStr(&buf, "'v',");
 	if (showMatViews)
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 8469d9f..cd4eb7c 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -427,7 +427,7 @@ static const SchemaQuery Query_for_list_of_tables = {
 	/* catname */
 	"pg_catalog.pg_class c",
 	/* selcondition */
-	"c.relkind IN ('r')",
+	"c.relkind IN ('r', 'P')",
 	/* viscondition */
 	"pg_catalog.pg_table_is_visible(c.oid)",
 	/* namespace */
@@ -458,7 +458,7 @@ static const SchemaQuery Query_for_list_of_updatables = {
 	/* catname */
 	"pg_catalog.pg_class c",
 	/* selcondition */
-	"c.relkind IN ('r', 'f', 'v')",
+	"c.relkind IN ('r', 'f', 'v', 'P')",
 	/* viscondition */
 	"pg_catalog.pg_table_is_visible(c.oid)",
 	/* namespace */
@@ -488,7 +488,7 @@ static const SchemaQuery Query_for_list_of_tsvmf = {
 	/* catname */
 	"pg_catalog.pg_class c",
 	/* selcondition */
-	"c.relkind IN ('r', 'S', 'v', 'm', 'f')",
+	"c.relkind IN ('r', 'S', 'v', 'm', 'f', 'P')",
 	/* viscondition */
 	"pg_catalog.pg_table_is_visible(c.oid)",
 	/* namespace */
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index 270dd21..5a444d9 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/utils/builtins.h b/src/include/utils/builtins.h
index 8cebc86..351bfd3 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -722,6 +722,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/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index fb7a1b3..4fc8c06 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -413,3 +413,29 @@ SELECT connoinherit FROM pg_constraint WHERE conrelid = 'no_inh_con_parted'::reg
 (1 row)
 
 DROP TABLE no_inh_con_parted;
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+Partitioned table "public.describe_range_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition Key: PARTITION BY RANGE (((a + b)))
+
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+Partitioned table "public.describe_list_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition Key: PARTITION BY LIST (a)
+
+DROP TABLE describe_range_key, describe_list_key;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index cb17517..7c7a18a 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -404,3 +404,16 @@ CREATE TABLE no_inh_con_parted (
 ) PARTITION BY RANGE (a);
 SELECT connoinherit FROM pg_constraint WHERE conrelid = 'no_inh_con_parted'::regclass;
 DROP TABLE no_inh_con_parted;
+
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+DROP TABLE describe_range_key, describe_list_key;
-- 
1.7.1


--------------8C4C48E640631A327D3D2826
Content-Type: text/x-diff;
 name="0003-Catalog-and-DDL-for-partitions.patch"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
 filename="0003-Catalog-and-DDL-for-partitions.patch"



^ permalink  raw  reply  [nested|flat] 110+ messages in thread

* [PATCH 2/9] psql and pg_dump support for partitioned tables.
@ 2016-07-12 08:20  amit <[email protected]>
  0 siblings, 0 replies; 110+ messages in thread

From: amit @ 2016-07-12 08:20 UTC (permalink / raw)

Takes care of both the partition key deparse stuff and the new relkind.
---
 src/backend/utils/adt/ruleutils.c          |  140 ++++++++++++++++++++++++++++
 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/pg_proc.h              |    2 +
 src/include/utils/builtins.h               |    1 +
 src/test/regress/expected/create_table.out |   26 +++++
 src/test/regress/sql/create_table.sql      |   13 +++
 9 files changed, 260 insertions(+), 26 deletions(-)

diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 8a81d7a..77ce807 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_table.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
@@ -315,6 +316,7 @@ static char *pg_get_indexdef_worker(Oid indexrelid, int colno,
 					   const Oid *excludeOps,
 					   bool attrsOnly, bool showTblSpc,
 					   int prettyFlags, bool missing_ok);
+static char *pg_get_partkeydef_worker(Oid relId, int prettyFlags);
 static char *pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 							int prettyFlags, bool missing_ok);
 static text *pg_get_expr_worker(text *expr, Oid relid, const char *relname,
@@ -1389,6 +1391,144 @@ 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 [ 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_table	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_table) GETSTRUCT(tuple);
+
+	Assert(form->partedrelid == relId);
+
+	/* Must get partclass, and partexprs the hard way */
+	datum = SysCacheGetAttr(PARTEDRELID, tuple,
+							Anum_pg_partitioned_table_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_table_partexprbin))
+	{
+		Datum		exprsDatum;
+		bool		isnull;
+		char	   *exprsString;
+
+		exprsDatum = SysCacheGetAttr(PARTEDRELID, tuple,
+									 Anum_pg_partitioned_table_partexprbin, &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);
+
+	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,
+											0, 0);
+
+			appendStringInfoString(&buf, 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/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index a5c2d09..c418ba7 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -1253,9 +1253,10 @@ expand_table_name_patterns(Archive *fout,
 						  "SELECT c.oid"
 						  "\nFROM pg_catalog.pg_class c"
 		"\n     LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace"
-					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c')\n",
+					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c')\n",
 						  RELKIND_RELATION, RELKIND_SEQUENCE, RELKIND_VIEW,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 		processSQLNamePattern(GetConnection(fout), query, cell->val, true,
 							  false, "n.nspname", "c.relname", NULL,
 							  "pg_catalog.pg_table_is_visible(c.oid)");
@@ -2117,6 +2118,9 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo, bool oids)
 	/* Skip FOREIGN TABLEs (no data to dump) */
 	if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 		return;
+	/* Skip partitioned tables (data in partitions) */
+	if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+		return;
 
 	/* Don't dump data in unlogged tables, if so requested */
 	if (tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED &&
@@ -5195,6 +5199,7 @@ getTables(Archive *fout, int *numTables)
 	int			i_reloftype;
 	int			i_relpages;
 	int			i_changed_acl;
+	int			i_partkeydef;
 
 	/* Make sure we are in proper schema */
 	selectSourceSchema(fout, "pg_catalog");
@@ -5280,7 +5285,8 @@ getTables(Archive *fout, int *numTables)
 						  "OR %s IS NOT NULL "
 						  "OR %s IS NOT NULL"
 						  "))"
-						  "AS changed_acl "
+						  "AS changed_acl, "
+						  "CASE WHEN c.relkind = 'P' THEN pg_catalog.pg_get_partkeydef(c.oid) ELSE NULL END AS partkeydef "
 						  "FROM pg_class c "
 						  "LEFT JOIN pg_depend d ON "
 						  "(c.relkind = '%c' AND "
@@ -5292,7 +5298,7 @@ getTables(Archive *fout, int *numTables)
 						  "(c.oid = pip.objoid "
 						  "AND pip.classoid = 'pg_class'::regclass "
 						  "AND pip.objsubid = 0) "
-				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c') "
+				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c', '%c') "
 						  "ORDER BY c.oid",
 						  acl_subquery->data,
 						  racl_subquery->data,
@@ -5306,7 +5312,8 @@ getTables(Archive *fout, int *numTables)
 						  RELKIND_SEQUENCE,
 						  RELKIND_RELATION, RELKIND_SEQUENCE,
 						  RELKIND_VIEW, RELKIND_COMPOSITE_TYPE,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 
 		destroyPQExpBuffer(acl_subquery);
 		destroyPQExpBuffer(racl_subquery);
@@ -5868,6 +5875,7 @@ getTables(Archive *fout, int *numTables)
 	i_toastreloptions = PQfnumber(res, "toast_reloptions");
 	i_reloftype = PQfnumber(res, "reloftype");
 	i_changed_acl = PQfnumber(res, "changed_acl");
+	i_partkeydef = PQfnumber(res, "partkeydef");
 
 	if (dopt->lockWaitTimeout && fout->remoteVersion >= 70300)
 	{
@@ -5938,6 +5946,7 @@ getTables(Archive *fout, int *numTables)
 		else
 			tblinfo[i].checkoption = pg_strdup(PQgetvalue(res, i, i_checkoption));
 		tblinfo[i].toast_reloptions = pg_strdup(PQgetvalue(res, i, i_toastreloptions));
+		tblinfo[i].partkeydef = pg_strdup(PQgetvalue(res, i, i_partkeydef));
 
 		/* other fields were zeroed above */
 
@@ -5982,7 +5991,9 @@ getTables(Archive *fout, int *numTables)
 		 * We only need to lock the table for certain components; see
 		 * pg_dump.h
 		 */
-		if (tblinfo[i].dobj.dump && tblinfo[i].relkind == RELKIND_RELATION &&
+		if (tblinfo[i].dobj.dump &&
+			(tblinfo[i].relkind == RELKIND_RELATION ||
+			 tblinfo->relkind == RELKIND_PARTITIONED_TABLE) &&
 			(tblinfo[i].dobj.dump & DUMP_COMPONENTS_REQUIRING_LOCK))
 		{
 			resetPQExpBuffer(query);
@@ -6084,7 +6095,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);
 
@@ -15437,6 +15451,9 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 				appendPQExpBufferChar(q, ')');
 			}
 
+			if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+				appendPQExpBuffer(q, "\nPARTITION BY %s", tbinfo->partkeydef);
+
 			if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 				appendPQExpBuffer(q, "\nSERVER %s", fmtId(srvname));
 		}
@@ -15497,6 +15514,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 		 */
 		if (dopt->binary_upgrade &&
 			(tbinfo->relkind == RELKIND_RELATION ||
+			 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 			 tbinfo->relkind == RELKIND_FOREIGN_TABLE))
 		{
 			for (j = 0; j < tbinfo->numatts; j++)
@@ -15515,7 +15533,8 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 					appendStringLiteralAH(q, fmtId(tbinfo->dobj.name), fout);
 					appendPQExpBufferStr(q, "::pg_catalog.regclass;\n");
 
-					if (tbinfo->relkind == RELKIND_RELATION)
+					if (tbinfo->relkind == RELKIND_RELATION ||
+						tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
 						appendPQExpBuffer(q, "ALTER TABLE ONLY %s ",
 										  fmtId(tbinfo->dobj.name));
 					else
@@ -15732,6 +15751,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 	 * dump properties we only have ALTER TABLE syntax for
 	 */
 	if ((tbinfo->relkind == RELKIND_RELATION ||
+		 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 		 tbinfo->relkind == RELKIND_MATVIEW) &&
 		tbinfo->relreplident != REPLICA_IDENTITY_DEFAULT)
 	{
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 2bfa2d9..0292859 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -310,6 +310,7 @@ typedef struct _tableInfo
 	bool	   *inhNotNull;		/* true if NOT NULL is inherited */
 	struct _attrDefInfo **attrdefs;		/* DEFAULT expressions */
 	struct _constraintInfo *checkexprs; /* CHECK constraints */
+	char	   *partkeydef;		/* partition key definition */
 
 	/*
 	 * Stuff computed only for dumpable tables.
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 6275a68..10d924a 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -861,6 +861,7 @@ permissionsList(const char *pattern)
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'S' THEN '%s'"
@@ -870,6 +871,7 @@ permissionsList(const char *pattern)
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("sequence"),
@@ -920,7 +922,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
@@ -1567,8 +1569,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)");
 	}
 
@@ -1633,6 +1635,14 @@ describeOneTableDetails(const char *schemaname,
 			printfPQExpBuffer(&title, _("Foreign table \"%s.%s\""),
 							  schemaname, relationname);
 			break;
+		case 'P':
+			if (tableinfo.relpersistence == 'u')
+				printfPQExpBuffer(&title, _("Unlogged table \"%s.%s\""),
+								  schemaname, relationname);
+			else
+				printfPQExpBuffer(&title, _("Table \"%s.%s\""),
+								  schemaname, relationname);
+			break;
 		default:
 			/* untranslated unknown relkind */
 			printfPQExpBuffer(&title, "?%c? \"%s.%s\"",
@@ -1646,8 +1656,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");
@@ -1667,12 +1677,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");
 	}
 
@@ -1772,7 +1782,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);
@@ -1780,14 +1790,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 (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 */
@@ -1926,7 +1955,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;
@@ -2485,7 +2514,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;
@@ -2696,7 +2725,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
@@ -3024,6 +3053,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'i' THEN '%s'"
@@ -3035,6 +3065,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("index"),
@@ -3079,7 +3110,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 1345e4e..4686cd1 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/pg_proc.h b/src/include/catalog/pg_proc.h
index e2d08ba..b45688b 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -1980,6 +1980,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/utils/builtins.h b/src/include/utils/builtins.h
index 2ae212a..e800647 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -729,6 +729,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/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 708232d..2fec847 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -408,3 +408,29 @@ CREATE TABLE no_inh_con_parted (
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
 ERROR:  cannot add NO INHERIT constraint to partitioned table "no_inh_con_parted"
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+Table "public.describe_range_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition Key: RANGE ((a + b))
+
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+Table "public.describe_list_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition Key: LIST (a)
+
+DROP TABLE describe_range_key, describe_list_key;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 6e4a8be..4dd6a0a 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -402,3 +402,16 @@ CREATE TABLE no_inh_con_parted (
 	a int,
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
+
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+DROP TABLE describe_range_key, describe_list_key;
-- 
1.7.1


--------------954458C17BBEB1BF6FC758E7
Content-Type: text/x-diff;
 name="0003-Catalog-and-DDL-for-partitions-2.patch"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
 filename="0003-Catalog-and-DDL-for-partitions-2.patch"



^ permalink  raw  reply  [nested|flat] 110+ messages in thread

* [PATCH 2/9] psql and pg_dump support for partitioned tables.
@ 2016-07-12 08:20  amit <[email protected]>
  0 siblings, 0 replies; 110+ messages in thread

From: amit @ 2016-07-12 08:20 UTC (permalink / raw)

Takes care of both the partition key deparse stuff and the new relkind.
---
 src/backend/utils/adt/ruleutils.c          |  140 ++++++++++++++++++++++++++++
 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/pg_proc.h              |    2 +
 src/include/utils/builtins.h               |    1 +
 src/test/regress/expected/create_table.out |   26 +++++
 src/test/regress/sql/create_table.sql      |   13 +++
 9 files changed, 260 insertions(+), 26 deletions(-)

diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 8a81d7a..77ce807 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_table.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
@@ -315,6 +316,7 @@ static char *pg_get_indexdef_worker(Oid indexrelid, int colno,
 					   const Oid *excludeOps,
 					   bool attrsOnly, bool showTblSpc,
 					   int prettyFlags, bool missing_ok);
+static char *pg_get_partkeydef_worker(Oid relId, int prettyFlags);
 static char *pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 							int prettyFlags, bool missing_ok);
 static text *pg_get_expr_worker(text *expr, Oid relid, const char *relname,
@@ -1389,6 +1391,144 @@ 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 [ 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_table	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_table) GETSTRUCT(tuple);
+
+	Assert(form->partedrelid == relId);
+
+	/* Must get partclass, and partexprs the hard way */
+	datum = SysCacheGetAttr(PARTEDRELID, tuple,
+							Anum_pg_partitioned_table_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_table_partexprbin))
+	{
+		Datum		exprsDatum;
+		bool		isnull;
+		char	   *exprsString;
+
+		exprsDatum = SysCacheGetAttr(PARTEDRELID, tuple,
+									 Anum_pg_partitioned_table_partexprbin, &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);
+
+	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,
+											0, 0);
+
+			appendStringInfoString(&buf, 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/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index a5c2d09..c418ba7 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -1253,9 +1253,10 @@ expand_table_name_patterns(Archive *fout,
 						  "SELECT c.oid"
 						  "\nFROM pg_catalog.pg_class c"
 		"\n     LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace"
-					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c')\n",
+					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c')\n",
 						  RELKIND_RELATION, RELKIND_SEQUENCE, RELKIND_VIEW,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 		processSQLNamePattern(GetConnection(fout), query, cell->val, true,
 							  false, "n.nspname", "c.relname", NULL,
 							  "pg_catalog.pg_table_is_visible(c.oid)");
@@ -2117,6 +2118,9 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo, bool oids)
 	/* Skip FOREIGN TABLEs (no data to dump) */
 	if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 		return;
+	/* Skip partitioned tables (data in partitions) */
+	if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+		return;
 
 	/* Don't dump data in unlogged tables, if so requested */
 	if (tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED &&
@@ -5195,6 +5199,7 @@ getTables(Archive *fout, int *numTables)
 	int			i_reloftype;
 	int			i_relpages;
 	int			i_changed_acl;
+	int			i_partkeydef;
 
 	/* Make sure we are in proper schema */
 	selectSourceSchema(fout, "pg_catalog");
@@ -5280,7 +5285,8 @@ getTables(Archive *fout, int *numTables)
 						  "OR %s IS NOT NULL "
 						  "OR %s IS NOT NULL"
 						  "))"
-						  "AS changed_acl "
+						  "AS changed_acl, "
+						  "CASE WHEN c.relkind = 'P' THEN pg_catalog.pg_get_partkeydef(c.oid) ELSE NULL END AS partkeydef "
 						  "FROM pg_class c "
 						  "LEFT JOIN pg_depend d ON "
 						  "(c.relkind = '%c' AND "
@@ -5292,7 +5298,7 @@ getTables(Archive *fout, int *numTables)
 						  "(c.oid = pip.objoid "
 						  "AND pip.classoid = 'pg_class'::regclass "
 						  "AND pip.objsubid = 0) "
-				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c') "
+				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c', '%c') "
 						  "ORDER BY c.oid",
 						  acl_subquery->data,
 						  racl_subquery->data,
@@ -5306,7 +5312,8 @@ getTables(Archive *fout, int *numTables)
 						  RELKIND_SEQUENCE,
 						  RELKIND_RELATION, RELKIND_SEQUENCE,
 						  RELKIND_VIEW, RELKIND_COMPOSITE_TYPE,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 
 		destroyPQExpBuffer(acl_subquery);
 		destroyPQExpBuffer(racl_subquery);
@@ -5868,6 +5875,7 @@ getTables(Archive *fout, int *numTables)
 	i_toastreloptions = PQfnumber(res, "toast_reloptions");
 	i_reloftype = PQfnumber(res, "reloftype");
 	i_changed_acl = PQfnumber(res, "changed_acl");
+	i_partkeydef = PQfnumber(res, "partkeydef");
 
 	if (dopt->lockWaitTimeout && fout->remoteVersion >= 70300)
 	{
@@ -5938,6 +5946,7 @@ getTables(Archive *fout, int *numTables)
 		else
 			tblinfo[i].checkoption = pg_strdup(PQgetvalue(res, i, i_checkoption));
 		tblinfo[i].toast_reloptions = pg_strdup(PQgetvalue(res, i, i_toastreloptions));
+		tblinfo[i].partkeydef = pg_strdup(PQgetvalue(res, i, i_partkeydef));
 
 		/* other fields were zeroed above */
 
@@ -5982,7 +5991,9 @@ getTables(Archive *fout, int *numTables)
 		 * We only need to lock the table for certain components; see
 		 * pg_dump.h
 		 */
-		if (tblinfo[i].dobj.dump && tblinfo[i].relkind == RELKIND_RELATION &&
+		if (tblinfo[i].dobj.dump &&
+			(tblinfo[i].relkind == RELKIND_RELATION ||
+			 tblinfo->relkind == RELKIND_PARTITIONED_TABLE) &&
 			(tblinfo[i].dobj.dump & DUMP_COMPONENTS_REQUIRING_LOCK))
 		{
 			resetPQExpBuffer(query);
@@ -6084,7 +6095,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);
 
@@ -15437,6 +15451,9 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 				appendPQExpBufferChar(q, ')');
 			}
 
+			if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+				appendPQExpBuffer(q, "\nPARTITION BY %s", tbinfo->partkeydef);
+
 			if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 				appendPQExpBuffer(q, "\nSERVER %s", fmtId(srvname));
 		}
@@ -15497,6 +15514,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 		 */
 		if (dopt->binary_upgrade &&
 			(tbinfo->relkind == RELKIND_RELATION ||
+			 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 			 tbinfo->relkind == RELKIND_FOREIGN_TABLE))
 		{
 			for (j = 0; j < tbinfo->numatts; j++)
@@ -15515,7 +15533,8 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 					appendStringLiteralAH(q, fmtId(tbinfo->dobj.name), fout);
 					appendPQExpBufferStr(q, "::pg_catalog.regclass;\n");
 
-					if (tbinfo->relkind == RELKIND_RELATION)
+					if (tbinfo->relkind == RELKIND_RELATION ||
+						tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
 						appendPQExpBuffer(q, "ALTER TABLE ONLY %s ",
 										  fmtId(tbinfo->dobj.name));
 					else
@@ -15732,6 +15751,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 	 * dump properties we only have ALTER TABLE syntax for
 	 */
 	if ((tbinfo->relkind == RELKIND_RELATION ||
+		 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 		 tbinfo->relkind == RELKIND_MATVIEW) &&
 		tbinfo->relreplident != REPLICA_IDENTITY_DEFAULT)
 	{
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 2bfa2d9..0292859 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -310,6 +310,7 @@ typedef struct _tableInfo
 	bool	   *inhNotNull;		/* true if NOT NULL is inherited */
 	struct _attrDefInfo **attrdefs;		/* DEFAULT expressions */
 	struct _constraintInfo *checkexprs; /* CHECK constraints */
+	char	   *partkeydef;		/* partition key definition */
 
 	/*
 	 * Stuff computed only for dumpable tables.
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 6275a68..10d924a 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -861,6 +861,7 @@ permissionsList(const char *pattern)
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'S' THEN '%s'"
@@ -870,6 +871,7 @@ permissionsList(const char *pattern)
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("sequence"),
@@ -920,7 +922,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
@@ -1567,8 +1569,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)");
 	}
 
@@ -1633,6 +1635,14 @@ describeOneTableDetails(const char *schemaname,
 			printfPQExpBuffer(&title, _("Foreign table \"%s.%s\""),
 							  schemaname, relationname);
 			break;
+		case 'P':
+			if (tableinfo.relpersistence == 'u')
+				printfPQExpBuffer(&title, _("Unlogged table \"%s.%s\""),
+								  schemaname, relationname);
+			else
+				printfPQExpBuffer(&title, _("Table \"%s.%s\""),
+								  schemaname, relationname);
+			break;
 		default:
 			/* untranslated unknown relkind */
 			printfPQExpBuffer(&title, "?%c? \"%s.%s\"",
@@ -1646,8 +1656,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");
@@ -1667,12 +1677,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");
 	}
 
@@ -1772,7 +1782,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);
@@ -1780,14 +1790,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 (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 */
@@ -1926,7 +1955,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;
@@ -2485,7 +2514,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;
@@ -2696,7 +2725,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
@@ -3024,6 +3053,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'i' THEN '%s'"
@@ -3035,6 +3065,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("index"),
@@ -3079,7 +3110,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 019f75a..5e5c370 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/pg_proc.h b/src/include/catalog/pg_proc.h
index e2d08ba..b45688b 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -1980,6 +1980,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/utils/builtins.h b/src/include/utils/builtins.h
index 2ae212a..e800647 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -729,6 +729,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/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 708232d..2fec847 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -408,3 +408,29 @@ CREATE TABLE no_inh_con_parted (
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
 ERROR:  cannot add NO INHERIT constraint to partitioned table "no_inh_con_parted"
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+Table "public.describe_range_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition Key: RANGE ((a + b))
+
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+Table "public.describe_list_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition Key: LIST (a)
+
+DROP TABLE describe_range_key, describe_list_key;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 6e4a8be..4dd6a0a 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -402,3 +402,16 @@ CREATE TABLE no_inh_con_parted (
 	a int,
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
+
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+DROP TABLE describe_range_key, describe_list_key;
-- 
1.7.1


--------------6EB5B318125CB7DB1A4FD229
Content-Type: text/x-diff;
 name="0003-Catalog-and-DDL-for-partitions-3.patch"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
 filename="0003-Catalog-and-DDL-for-partitions-3.patch"



^ permalink  raw  reply  [nested|flat] 110+ messages in thread

* [PATCH 2/9] psql and pg_dump support for partitioned tables.
@ 2016-07-12 08:20  amit <[email protected]>
  0 siblings, 0 replies; 110+ messages in thread

From: amit @ 2016-07-12 08:20 UTC (permalink / raw)

Takes care of both the partition key deparse stuff and the new relkind.
---
 src/backend/utils/adt/ruleutils.c          |  140 ++++++++++++++++++++++++++++
 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/pg_proc.h              |    2 +
 src/include/utils/builtins.h               |    1 +
 src/test/regress/expected/create_table.out |   26 +++++
 src/test/regress/sql/create_table.sql      |   13 +++
 9 files changed, 260 insertions(+), 26 deletions(-)

diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 8a81d7a..03be202 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_table.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
@@ -315,6 +316,7 @@ static char *pg_get_indexdef_worker(Oid indexrelid, int colno,
 					   const Oid *excludeOps,
 					   bool attrsOnly, bool showTblSpc,
 					   int prettyFlags, bool missing_ok);
+static char *pg_get_partkeydef_worker(Oid relid, int prettyFlags);
 static char *pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 							int prettyFlags, bool missing_ok);
 static text *pg_get_expr_worker(text *expr, Oid relid, const char *relname,
@@ -1389,6 +1391,144 @@ 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 [ opclass_name ] [, ...])
+ */
+Datum
+pg_get_partkeydef(PG_FUNCTION_ARGS)
+{
+	Oid			relid = PG_GETARG_OID(0);
+	int			prettyFlags;
+
+	prettyFlags = PRETTYFLAG_INDENT;
+	PG_RETURN_TEXT_P(string_to_text(pg_get_partkeydef_worker(relid,
+									prettyFlags)));
+}
+
+/*
+ * Internal workhorse to decompile a partition key definition.
+ */
+static char *
+pg_get_partkeydef_worker(Oid relid, int prettyFlags)
+{
+	Form_pg_partitioned_table	form;
+	HeapTuple	tuple;
+	oidvector  *partclass;
+	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_table) GETSTRUCT(tuple);
+
+	Assert(form->partrelid == relid);
+
+	/* Must get partclass, and partexprs the hard way */
+	datum = SysCacheGetAttr(PARTEDRELID, tuple,
+							Anum_pg_partitioned_table_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_table_partexprbin))
+	{
+		Datum		exprsDatum;
+		bool		isnull;
+		char	   *exprsString;
+
+		exprsDatum = SysCacheGetAttr(PARTEDRELID, tuple,
+									 Anum_pg_partitioned_table_partexprbin, &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);
+
+	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,
+											0, 0);
+
+			appendStringInfoString(&buf, 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/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index ba9c276..c805a84 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -1253,9 +1253,10 @@ expand_table_name_patterns(Archive *fout,
 						  "SELECT c.oid"
 						  "\nFROM pg_catalog.pg_class c"
 		"\n     LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace"
-					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c')\n",
+					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c')\n",
 						  RELKIND_RELATION, RELKIND_SEQUENCE, RELKIND_VIEW,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 		processSQLNamePattern(GetConnection(fout), query, cell->val, true,
 							  false, "n.nspname", "c.relname", NULL,
 							  "pg_catalog.pg_table_is_visible(c.oid)");
@@ -2125,6 +2126,9 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo, bool oids)
 	/* Skip FOREIGN TABLEs (no data to dump) */
 	if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 		return;
+	/* Skip partitioned tables (data in partitions) */
+	if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+		return;
 
 	/* Don't dump data in unlogged tables, if so requested */
 	if (tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED &&
@@ -5204,6 +5208,7 @@ getTables(Archive *fout, int *numTables)
 	int			i_reloftype;
 	int			i_relpages;
 	int			i_changed_acl;
+	int			i_partkeydef;
 
 	/* Make sure we are in proper schema */
 	selectSourceSchema(fout, "pg_catalog");
@@ -5289,7 +5294,8 @@ getTables(Archive *fout, int *numTables)
 						  "OR %s IS NOT NULL "
 						  "OR %s IS NOT NULL"
 						  "))"
-						  "AS changed_acl "
+						  "AS changed_acl, "
+						  "CASE WHEN c.relkind = 'P' THEN pg_catalog.pg_get_partkeydef(c.oid) ELSE NULL END AS partkeydef "
 						  "FROM pg_class c "
 						  "LEFT JOIN pg_depend d ON "
 						  "(c.relkind = '%c' AND "
@@ -5301,7 +5307,7 @@ getTables(Archive *fout, int *numTables)
 						  "(c.oid = pip.objoid "
 						  "AND pip.classoid = 'pg_class'::regclass "
 						  "AND pip.objsubid = 0) "
-				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c') "
+				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c', '%c') "
 						  "ORDER BY c.oid",
 						  acl_subquery->data,
 						  racl_subquery->data,
@@ -5315,7 +5321,8 @@ getTables(Archive *fout, int *numTables)
 						  RELKIND_SEQUENCE,
 						  RELKIND_RELATION, RELKIND_SEQUENCE,
 						  RELKIND_VIEW, RELKIND_COMPOSITE_TYPE,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 
 		destroyPQExpBuffer(acl_subquery);
 		destroyPQExpBuffer(racl_subquery);
@@ -5877,6 +5884,7 @@ getTables(Archive *fout, int *numTables)
 	i_toastreloptions = PQfnumber(res, "toast_reloptions");
 	i_reloftype = PQfnumber(res, "reloftype");
 	i_changed_acl = PQfnumber(res, "changed_acl");
+	i_partkeydef = PQfnumber(res, "partkeydef");
 
 	if (dopt->lockWaitTimeout && fout->remoteVersion >= 70300)
 	{
@@ -5947,6 +5955,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 */
 
@@ -5991,7 +6000,9 @@ getTables(Archive *fout, int *numTables)
 		 * We only need to lock the table for certain components; see
 		 * pg_dump.h
 		 */
-		if (tblinfo[i].dobj.dump && tblinfo[i].relkind == RELKIND_RELATION &&
+		if (tblinfo[i].dobj.dump &&
+			(tblinfo[i].relkind == RELKIND_RELATION ||
+			 tblinfo->relkind == RELKIND_PARTITIONED_TABLE) &&
 			(tblinfo[i].dobj.dump & DUMP_COMPONENTS_REQUIRING_LOCK))
 		{
 			resetPQExpBuffer(query);
@@ -6093,7 +6104,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);
 
@@ -15446,6 +15460,9 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 				appendPQExpBufferChar(q, ')');
 			}
 
+			if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+				appendPQExpBuffer(q, "\nPARTITION BY %s", tbinfo->partkeydef);
+
 			if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 				appendPQExpBuffer(q, "\nSERVER %s", fmtId(srvname));
 		}
@@ -15506,6 +15523,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 		 */
 		if (dopt->binary_upgrade &&
 			(tbinfo->relkind == RELKIND_RELATION ||
+			 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 			 tbinfo->relkind == RELKIND_FOREIGN_TABLE))
 		{
 			for (j = 0; j < tbinfo->numatts; j++)
@@ -15524,7 +15542,8 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 					appendStringLiteralAH(q, fmtId(tbinfo->dobj.name), fout);
 					appendPQExpBufferStr(q, "::pg_catalog.regclass;\n");
 
-					if (tbinfo->relkind == RELKIND_RELATION)
+					if (tbinfo->relkind == RELKIND_RELATION ||
+						tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
 						appendPQExpBuffer(q, "ALTER TABLE ONLY %s ",
 										  fmtId(tbinfo->dobj.name));
 					else
@@ -15741,6 +15760,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 	 * dump properties we only have ALTER TABLE syntax for
 	 */
 	if ((tbinfo->relkind == RELKIND_RELATION ||
+		 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 		 tbinfo->relkind == RELKIND_MATVIEW) &&
 		tbinfo->relreplident != REPLICA_IDENTITY_DEFAULT)
 	{
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 2bfa2d9..0292859 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -310,6 +310,7 @@ typedef struct _tableInfo
 	bool	   *inhNotNull;		/* true if NOT NULL is inherited */
 	struct _attrDefInfo **attrdefs;		/* DEFAULT expressions */
 	struct _constraintInfo *checkexprs; /* CHECK constraints */
+	char	   *partkeydef;		/* partition key definition */
 
 	/*
 	 * Stuff computed only for dumpable tables.
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 6275a68..10d924a 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -861,6 +861,7 @@ permissionsList(const char *pattern)
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'S' THEN '%s'"
@@ -870,6 +871,7 @@ permissionsList(const char *pattern)
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("sequence"),
@@ -920,7 +922,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
@@ -1567,8 +1569,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)");
 	}
 
@@ -1633,6 +1635,14 @@ describeOneTableDetails(const char *schemaname,
 			printfPQExpBuffer(&title, _("Foreign table \"%s.%s\""),
 							  schemaname, relationname);
 			break;
+		case 'P':
+			if (tableinfo.relpersistence == 'u')
+				printfPQExpBuffer(&title, _("Unlogged table \"%s.%s\""),
+								  schemaname, relationname);
+			else
+				printfPQExpBuffer(&title, _("Table \"%s.%s\""),
+								  schemaname, relationname);
+			break;
 		default:
 			/* untranslated unknown relkind */
 			printfPQExpBuffer(&title, "?%c? \"%s.%s\"",
@@ -1646,8 +1656,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");
@@ -1667,12 +1677,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");
 	}
 
@@ -1772,7 +1782,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);
@@ -1780,14 +1790,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 (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 */
@@ -1926,7 +1955,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;
@@ -2485,7 +2514,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;
@@ -2696,7 +2725,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
@@ -3024,6 +3053,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'i' THEN '%s'"
@@ -3035,6 +3065,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("index"),
@@ -3079,7 +3110,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 019f75a..5e5c370 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/pg_proc.h b/src/include/catalog/pg_proc.h
index e2d08ba..b45688b 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -1980,6 +1980,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/utils/builtins.h b/src/include/utils/builtins.h
index 2ae212a..e800647 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -729,6 +729,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/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 708232d..2fec847 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -408,3 +408,29 @@ CREATE TABLE no_inh_con_parted (
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
 ERROR:  cannot add NO INHERIT constraint to partitioned table "no_inh_con_parted"
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+Table "public.describe_range_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition Key: RANGE ((a + b))
+
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+Table "public.describe_list_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition Key: LIST (a)
+
+DROP TABLE describe_range_key, describe_list_key;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 6e4a8be..4dd6a0a 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -402,3 +402,16 @@ CREATE TABLE no_inh_con_parted (
 	a int,
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
+
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+DROP TABLE describe_range_key, describe_list_key;
-- 
1.7.1


--------------4012470FC48539D6F6714CEA
Content-Type: text/x-diff;
 name="0003-Catalog-and-DDL-for-partitions-4.patch"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
 filename="0003-Catalog-and-DDL-for-partitions-4.patch"



^ permalink  raw  reply  [nested|flat] 110+ messages in thread

* [PATCH 2/9] psql and pg_dump support for partitioned tables.
@ 2016-07-12 08:20  amit <[email protected]>
  0 siblings, 0 replies; 110+ messages in thread

From: amit @ 2016-07-12 08:20 UTC (permalink / raw)

Takes care of both the partition key deparse stuff and the new relkind.
---
 src/backend/utils/adt/ruleutils.c          |  140 ++++++++++++++++++++++++++++
 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/pg_proc.h              |    2 +
 src/include/utils/builtins.h               |    1 +
 src/test/regress/expected/create_table.out |   26 +++++
 src/test/regress/sql/create_table.sql      |   13 +++
 9 files changed, 260 insertions(+), 26 deletions(-)

diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 8a81d7a..03be202 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_table.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
@@ -315,6 +316,7 @@ static char *pg_get_indexdef_worker(Oid indexrelid, int colno,
 					   const Oid *excludeOps,
 					   bool attrsOnly, bool showTblSpc,
 					   int prettyFlags, bool missing_ok);
+static char *pg_get_partkeydef_worker(Oid relid, int prettyFlags);
 static char *pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 							int prettyFlags, bool missing_ok);
 static text *pg_get_expr_worker(text *expr, Oid relid, const char *relname,
@@ -1389,6 +1391,144 @@ 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 [ opclass_name ] [, ...])
+ */
+Datum
+pg_get_partkeydef(PG_FUNCTION_ARGS)
+{
+	Oid			relid = PG_GETARG_OID(0);
+	int			prettyFlags;
+
+	prettyFlags = PRETTYFLAG_INDENT;
+	PG_RETURN_TEXT_P(string_to_text(pg_get_partkeydef_worker(relid,
+									prettyFlags)));
+}
+
+/*
+ * Internal workhorse to decompile a partition key definition.
+ */
+static char *
+pg_get_partkeydef_worker(Oid relid, int prettyFlags)
+{
+	Form_pg_partitioned_table	form;
+	HeapTuple	tuple;
+	oidvector  *partclass;
+	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_table) GETSTRUCT(tuple);
+
+	Assert(form->partrelid == relid);
+
+	/* Must get partclass, and partexprs the hard way */
+	datum = SysCacheGetAttr(PARTEDRELID, tuple,
+							Anum_pg_partitioned_table_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_table_partexprbin))
+	{
+		Datum		exprsDatum;
+		bool		isnull;
+		char	   *exprsString;
+
+		exprsDatum = SysCacheGetAttr(PARTEDRELID, tuple,
+									 Anum_pg_partitioned_table_partexprbin, &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);
+
+	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,
+											0, 0);
+
+			appendStringInfoString(&buf, 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/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index ba9c276..c805a84 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -1253,9 +1253,10 @@ expand_table_name_patterns(Archive *fout,
 						  "SELECT c.oid"
 						  "\nFROM pg_catalog.pg_class c"
 		"\n     LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace"
-					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c')\n",
+					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c')\n",
 						  RELKIND_RELATION, RELKIND_SEQUENCE, RELKIND_VIEW,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 		processSQLNamePattern(GetConnection(fout), query, cell->val, true,
 							  false, "n.nspname", "c.relname", NULL,
 							  "pg_catalog.pg_table_is_visible(c.oid)");
@@ -2125,6 +2126,9 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo, bool oids)
 	/* Skip FOREIGN TABLEs (no data to dump) */
 	if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 		return;
+	/* Skip partitioned tables (data in partitions) */
+	if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+		return;
 
 	/* Don't dump data in unlogged tables, if so requested */
 	if (tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED &&
@@ -5204,6 +5208,7 @@ getTables(Archive *fout, int *numTables)
 	int			i_reloftype;
 	int			i_relpages;
 	int			i_changed_acl;
+	int			i_partkeydef;
 
 	/* Make sure we are in proper schema */
 	selectSourceSchema(fout, "pg_catalog");
@@ -5289,7 +5294,8 @@ getTables(Archive *fout, int *numTables)
 						  "OR %s IS NOT NULL "
 						  "OR %s IS NOT NULL"
 						  "))"
-						  "AS changed_acl "
+						  "AS changed_acl, "
+						  "CASE WHEN c.relkind = 'P' THEN pg_catalog.pg_get_partkeydef(c.oid) ELSE NULL END AS partkeydef "
 						  "FROM pg_class c "
 						  "LEFT JOIN pg_depend d ON "
 						  "(c.relkind = '%c' AND "
@@ -5301,7 +5307,7 @@ getTables(Archive *fout, int *numTables)
 						  "(c.oid = pip.objoid "
 						  "AND pip.classoid = 'pg_class'::regclass "
 						  "AND pip.objsubid = 0) "
-				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c') "
+				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c', '%c') "
 						  "ORDER BY c.oid",
 						  acl_subquery->data,
 						  racl_subquery->data,
@@ -5315,7 +5321,8 @@ getTables(Archive *fout, int *numTables)
 						  RELKIND_SEQUENCE,
 						  RELKIND_RELATION, RELKIND_SEQUENCE,
 						  RELKIND_VIEW, RELKIND_COMPOSITE_TYPE,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 
 		destroyPQExpBuffer(acl_subquery);
 		destroyPQExpBuffer(racl_subquery);
@@ -5877,6 +5884,7 @@ getTables(Archive *fout, int *numTables)
 	i_toastreloptions = PQfnumber(res, "toast_reloptions");
 	i_reloftype = PQfnumber(res, "reloftype");
 	i_changed_acl = PQfnumber(res, "changed_acl");
+	i_partkeydef = PQfnumber(res, "partkeydef");
 
 	if (dopt->lockWaitTimeout && fout->remoteVersion >= 70300)
 	{
@@ -5947,6 +5955,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 */
 
@@ -5991,7 +6000,9 @@ getTables(Archive *fout, int *numTables)
 		 * We only need to lock the table for certain components; see
 		 * pg_dump.h
 		 */
-		if (tblinfo[i].dobj.dump && tblinfo[i].relkind == RELKIND_RELATION &&
+		if (tblinfo[i].dobj.dump &&
+			(tblinfo[i].relkind == RELKIND_RELATION ||
+			 tblinfo->relkind == RELKIND_PARTITIONED_TABLE) &&
 			(tblinfo[i].dobj.dump & DUMP_COMPONENTS_REQUIRING_LOCK))
 		{
 			resetPQExpBuffer(query);
@@ -6093,7 +6104,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);
 
@@ -15446,6 +15460,9 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 				appendPQExpBufferChar(q, ')');
 			}
 
+			if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+				appendPQExpBuffer(q, "\nPARTITION BY %s", tbinfo->partkeydef);
+
 			if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 				appendPQExpBuffer(q, "\nSERVER %s", fmtId(srvname));
 		}
@@ -15506,6 +15523,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 		 */
 		if (dopt->binary_upgrade &&
 			(tbinfo->relkind == RELKIND_RELATION ||
+			 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 			 tbinfo->relkind == RELKIND_FOREIGN_TABLE))
 		{
 			for (j = 0; j < tbinfo->numatts; j++)
@@ -15524,7 +15542,8 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 					appendStringLiteralAH(q, fmtId(tbinfo->dobj.name), fout);
 					appendPQExpBufferStr(q, "::pg_catalog.regclass;\n");
 
-					if (tbinfo->relkind == RELKIND_RELATION)
+					if (tbinfo->relkind == RELKIND_RELATION ||
+						tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
 						appendPQExpBuffer(q, "ALTER TABLE ONLY %s ",
 										  fmtId(tbinfo->dobj.name));
 					else
@@ -15741,6 +15760,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 	 * dump properties we only have ALTER TABLE syntax for
 	 */
 	if ((tbinfo->relkind == RELKIND_RELATION ||
+		 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 		 tbinfo->relkind == RELKIND_MATVIEW) &&
 		tbinfo->relreplident != REPLICA_IDENTITY_DEFAULT)
 	{
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 2bfa2d9..0292859 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -310,6 +310,7 @@ typedef struct _tableInfo
 	bool	   *inhNotNull;		/* true if NOT NULL is inherited */
 	struct _attrDefInfo **attrdefs;		/* DEFAULT expressions */
 	struct _constraintInfo *checkexprs; /* CHECK constraints */
+	char	   *partkeydef;		/* partition key definition */
 
 	/*
 	 * Stuff computed only for dumpable tables.
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 6275a68..10d924a 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -861,6 +861,7 @@ permissionsList(const char *pattern)
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'S' THEN '%s'"
@@ -870,6 +871,7 @@ permissionsList(const char *pattern)
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("sequence"),
@@ -920,7 +922,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
@@ -1567,8 +1569,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)");
 	}
 
@@ -1633,6 +1635,14 @@ describeOneTableDetails(const char *schemaname,
 			printfPQExpBuffer(&title, _("Foreign table \"%s.%s\""),
 							  schemaname, relationname);
 			break;
+		case 'P':
+			if (tableinfo.relpersistence == 'u')
+				printfPQExpBuffer(&title, _("Unlogged table \"%s.%s\""),
+								  schemaname, relationname);
+			else
+				printfPQExpBuffer(&title, _("Table \"%s.%s\""),
+								  schemaname, relationname);
+			break;
 		default:
 			/* untranslated unknown relkind */
 			printfPQExpBuffer(&title, "?%c? \"%s.%s\"",
@@ -1646,8 +1656,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");
@@ -1667,12 +1677,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");
 	}
 
@@ -1772,7 +1782,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);
@@ -1780,14 +1790,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 (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 */
@@ -1926,7 +1955,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;
@@ -2485,7 +2514,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;
@@ -2696,7 +2725,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
@@ -3024,6 +3053,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'i' THEN '%s'"
@@ -3035,6 +3065,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("index"),
@@ -3079,7 +3110,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 50a45eb..8284a9c 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/pg_proc.h b/src/include/catalog/pg_proc.h
index e2d08ba..b45688b 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -1980,6 +1980,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/utils/builtins.h b/src/include/utils/builtins.h
index 2ae212a..e800647 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -729,6 +729,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/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 0af1422..91ba1be 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -412,3 +412,29 @@ CREATE TABLE no_inh_con_parted (
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
 ERROR:  cannot add NO INHERIT constraint to partitioned table "no_inh_con_parted"
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+Table "public.describe_range_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition Key: RANGE ((a + b))
+
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+Table "public.describe_list_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition Key: LIST (a)
+
+DROP TABLE describe_range_key, describe_list_key;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 418ac55..b75542d 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -406,3 +406,16 @@ CREATE TABLE no_inh_con_parted (
 	a int,
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
+
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+DROP TABLE describe_range_key, describe_list_key;
-- 
1.7.1


--------------D008BA677674C4836D7B65AD
Content-Type: text/x-diff;
 name="0003-Catalog-and-DDL-for-partitions-5.patch"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
 filename="0003-Catalog-and-DDL-for-partitions-5.patch"



^ permalink  raw  reply  [nested|flat] 110+ messages in thread

* [PATCH 2/9] psql and pg_dump support for partitioned tables.
@ 2016-07-12 08:20  amit <[email protected]>
  0 siblings, 0 replies; 110+ messages in thread

From: amit @ 2016-07-12 08:20 UTC (permalink / raw)

Takes care of both the partition key deparse stuff and the new relkind.
---
 src/backend/utils/adt/ruleutils.c          |  140 ++++++++++++++++++++++++++++
 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/pg_proc.h              |    2 +
 src/include/utils/builtins.h               |    1 +
 src/test/regress/expected/create_table.out |   26 +++++
 src/test/regress/sql/create_table.sql      |   13 +++
 9 files changed, 260 insertions(+), 26 deletions(-)

diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 8a81d7a..03be202 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_table.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
@@ -315,6 +316,7 @@ static char *pg_get_indexdef_worker(Oid indexrelid, int colno,
 					   const Oid *excludeOps,
 					   bool attrsOnly, bool showTblSpc,
 					   int prettyFlags, bool missing_ok);
+static char *pg_get_partkeydef_worker(Oid relid, int prettyFlags);
 static char *pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 							int prettyFlags, bool missing_ok);
 static text *pg_get_expr_worker(text *expr, Oid relid, const char *relname,
@@ -1389,6 +1391,144 @@ 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 [ opclass_name ] [, ...])
+ */
+Datum
+pg_get_partkeydef(PG_FUNCTION_ARGS)
+{
+	Oid			relid = PG_GETARG_OID(0);
+	int			prettyFlags;
+
+	prettyFlags = PRETTYFLAG_INDENT;
+	PG_RETURN_TEXT_P(string_to_text(pg_get_partkeydef_worker(relid,
+									prettyFlags)));
+}
+
+/*
+ * Internal workhorse to decompile a partition key definition.
+ */
+static char *
+pg_get_partkeydef_worker(Oid relid, int prettyFlags)
+{
+	Form_pg_partitioned_table	form;
+	HeapTuple	tuple;
+	oidvector  *partclass;
+	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_table) GETSTRUCT(tuple);
+
+	Assert(form->partrelid == relid);
+
+	/* Must get partclass, and partexprs the hard way */
+	datum = SysCacheGetAttr(PARTEDRELID, tuple,
+							Anum_pg_partitioned_table_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_table_partexprbin))
+	{
+		Datum		exprsDatum;
+		bool		isnull;
+		char	   *exprsString;
+
+		exprsDatum = SysCacheGetAttr(PARTEDRELID, tuple,
+									 Anum_pg_partitioned_table_partexprbin, &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);
+
+	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,
+											0, 0);
+
+			appendStringInfoString(&buf, 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/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 51b8a1a..854dddc 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -1253,9 +1253,10 @@ expand_table_name_patterns(Archive *fout,
 						  "SELECT c.oid"
 						  "\nFROM pg_catalog.pg_class c"
 		"\n     LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace"
-					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c')\n",
+					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c')\n",
 						  RELKIND_RELATION, RELKIND_SEQUENCE, RELKIND_VIEW,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 		processSQLNamePattern(GetConnection(fout), query, cell->val, true,
 							  false, "n.nspname", "c.relname", NULL,
 							  "pg_catalog.pg_table_is_visible(c.oid)");
@@ -2125,6 +2126,9 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo, bool oids)
 	/* Skip FOREIGN TABLEs (no data to dump) */
 	if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 		return;
+	/* Skip partitioned tables (data in partitions) */
+	if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+		return;
 
 	/* Don't dump data in unlogged tables, if so requested */
 	if (tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED &&
@@ -5211,6 +5215,7 @@ getTables(Archive *fout, int *numTables)
 	int			i_reloftype;
 	int			i_relpages;
 	int			i_changed_acl;
+	int			i_partkeydef;
 
 	/* Make sure we are in proper schema */
 	selectSourceSchema(fout, "pg_catalog");
@@ -5296,7 +5301,8 @@ getTables(Archive *fout, int *numTables)
 						  "OR %s IS NOT NULL "
 						  "OR %s IS NOT NULL"
 						  "))"
-						  "AS changed_acl "
+						  "AS changed_acl, "
+						  "CASE WHEN c.relkind = 'P' THEN pg_catalog.pg_get_partkeydef(c.oid) ELSE NULL END AS partkeydef "
 						  "FROM pg_class c "
 						  "LEFT JOIN pg_depend d ON "
 						  "(c.relkind = '%c' AND "
@@ -5308,7 +5314,7 @@ getTables(Archive *fout, int *numTables)
 						  "(c.oid = pip.objoid "
 						  "AND pip.classoid = 'pg_class'::regclass "
 						  "AND pip.objsubid = 0) "
-				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c') "
+				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c', '%c') "
 						  "ORDER BY c.oid",
 						  acl_subquery->data,
 						  racl_subquery->data,
@@ -5322,7 +5328,8 @@ getTables(Archive *fout, int *numTables)
 						  RELKIND_SEQUENCE,
 						  RELKIND_RELATION, RELKIND_SEQUENCE,
 						  RELKIND_VIEW, RELKIND_COMPOSITE_TYPE,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 
 		destroyPQExpBuffer(acl_subquery);
 		destroyPQExpBuffer(racl_subquery);
@@ -5884,6 +5891,7 @@ getTables(Archive *fout, int *numTables)
 	i_toastreloptions = PQfnumber(res, "toast_reloptions");
 	i_reloftype = PQfnumber(res, "reloftype");
 	i_changed_acl = PQfnumber(res, "changed_acl");
+	i_partkeydef = PQfnumber(res, "partkeydef");
 
 	if (dopt->lockWaitTimeout && fout->remoteVersion >= 70300)
 	{
@@ -5954,6 +5962,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 */
 
@@ -5998,7 +6007,9 @@ getTables(Archive *fout, int *numTables)
 		 * We only need to lock the table for certain components; see
 		 * pg_dump.h
 		 */
-		if (tblinfo[i].dobj.dump && tblinfo[i].relkind == RELKIND_RELATION &&
+		if (tblinfo[i].dobj.dump &&
+			(tblinfo[i].relkind == RELKIND_RELATION ||
+			 tblinfo->relkind == RELKIND_PARTITIONED_TABLE) &&
 			(tblinfo[i].dobj.dump & DUMP_COMPONENTS_REQUIRING_LOCK))
 		{
 			resetPQExpBuffer(query);
@@ -6100,7 +6111,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);
 
@@ -15453,6 +15467,9 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 				appendPQExpBufferChar(q, ')');
 			}
 
+			if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+				appendPQExpBuffer(q, "\nPARTITION BY %s", tbinfo->partkeydef);
+
 			if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 				appendPQExpBuffer(q, "\nSERVER %s", fmtId(srvname));
 		}
@@ -15513,6 +15530,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 		 */
 		if (dopt->binary_upgrade &&
 			(tbinfo->relkind == RELKIND_RELATION ||
+			 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 			 tbinfo->relkind == RELKIND_FOREIGN_TABLE))
 		{
 			for (j = 0; j < tbinfo->numatts; j++)
@@ -15531,7 +15549,8 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 					appendStringLiteralAH(q, fmtId(tbinfo->dobj.name), fout);
 					appendPQExpBufferStr(q, "::pg_catalog.regclass;\n");
 
-					if (tbinfo->relkind == RELKIND_RELATION)
+					if (tbinfo->relkind == RELKIND_RELATION ||
+						tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
 						appendPQExpBuffer(q, "ALTER TABLE ONLY %s ",
 										  fmtId(tbinfo->dobj.name));
 					else
@@ -15748,6 +15767,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 	 * dump properties we only have ALTER TABLE syntax for
 	 */
 	if ((tbinfo->relkind == RELKIND_RELATION ||
+		 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 		 tbinfo->relkind == RELKIND_MATVIEW) &&
 		tbinfo->relreplident != REPLICA_IDENTITY_DEFAULT)
 	{
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 2bfa2d9..0292859 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -310,6 +310,7 @@ typedef struct _tableInfo
 	bool	   *inhNotNull;		/* true if NOT NULL is inherited */
 	struct _attrDefInfo **attrdefs;		/* DEFAULT expressions */
 	struct _constraintInfo *checkexprs; /* CHECK constraints */
+	char	   *partkeydef;		/* partition key definition */
 
 	/*
 	 * Stuff computed only for dumpable tables.
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 6275a68..db6dc5c 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -861,6 +861,7 @@ permissionsList(const char *pattern)
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'S' THEN '%s'"
@@ -870,6 +871,7 @@ permissionsList(const char *pattern)
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("sequence"),
@@ -920,7 +922,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
@@ -1567,8 +1569,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)");
 	}
 
@@ -1633,6 +1635,14 @@ describeOneTableDetails(const char *schemaname,
 			printfPQExpBuffer(&title, _("Foreign table \"%s.%s\""),
 							  schemaname, relationname);
 			break;
+		case 'P':
+			if (tableinfo.relpersistence == 'u')
+				printfPQExpBuffer(&title, _("Unlogged table \"%s.%s\""),
+								  schemaname, relationname);
+			else
+				printfPQExpBuffer(&title, _("Table \"%s.%s\""),
+								  schemaname, relationname);
+			break;
 		default:
 			/* untranslated unknown relkind */
 			printfPQExpBuffer(&title, "?%c? \"%s.%s\"",
@@ -1646,8 +1656,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");
@@ -1667,12 +1677,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");
 	}
 
@@ -1772,7 +1782,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);
@@ -1780,14 +1790,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 (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 */
@@ -1926,7 +1955,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;
@@ -2485,7 +2514,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;
@@ -2696,7 +2725,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
@@ -3024,6 +3053,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'i' THEN '%s'"
@@ -3035,6 +3065,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("index"),
@@ -3079,7 +3110,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 50a45eb..8284a9c 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/pg_proc.h b/src/include/catalog/pg_proc.h
index e2d08ba..b45688b 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -1980,6 +1980,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/utils/builtins.h b/src/include/utils/builtins.h
index 2ae212a..e800647 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -729,6 +729,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/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 1394362..260ec34 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -407,3 +407,29 @@ CREATE TABLE no_inh_con_parted (
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
 ERROR:  cannot add NO INHERIT constraint to partitioned table "no_inh_con_parted"
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+Table "public.describe_range_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition key: RANGE ((a + b))
+
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+Table "public.describe_list_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition key: LIST (a)
+
+DROP TABLE describe_range_key, describe_list_key;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index d383f58..7a1f8de 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -401,3 +401,16 @@ CREATE TABLE no_inh_con_parted (
 	a int,
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
+
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+DROP TABLE describe_range_key, describe_list_key;
-- 
1.7.1


--------------C3235EE96C69D8DAE4DB543E
Content-Type: text/x-diff;
 name="0003-Catalog-and-DDL-for-partitions-6.patch"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
 filename="0003-Catalog-and-DDL-for-partitions-6.patch"



^ permalink  raw  reply  [nested|flat] 110+ messages in thread

* [PATCH 2/8] psql and pg_dump support for partitioned tables.
@ 2016-07-12 08:20  amit <[email protected]>
  0 siblings, 0 replies; 110+ messages in thread

From: amit @ 2016-07-12 08:20 UTC (permalink / raw)

Takes care of both the partition key deparse stuff and the new relkind.
---
 src/backend/utils/adt/ruleutils.c          |  147 ++++++++++++++++++++++++++++
 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/pg_proc.h              |    2 +
 src/include/utils/builtins.h               |    1 +
 src/test/regress/expected/create_table.out |   26 +++++
 src/test/regress/sql/create_table.sql      |   13 +++
 9 files changed, 267 insertions(+), 26 deletions(-)

diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index ec966c7..4691b5b 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, bool missing_ok);
+static char *pg_get_partkeydef_worker(Oid relId, int prettyFlags);
 static char *pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 							int prettyFlags, bool missing_ok);
 static text *pg_get_expr_worker(text *expr, Oid relid, const char *relname,
@@ -1389,6 +1391,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 [ 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/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 4ee10fc..a127672 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -1253,9 +1253,10 @@ expand_table_name_patterns(Archive *fout,
 						  "SELECT c.oid"
 						  "\nFROM pg_catalog.pg_class c"
 		"\n     LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace"
-					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c')\n",
+					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c')\n",
 						  RELKIND_RELATION, RELKIND_SEQUENCE, RELKIND_VIEW,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_REL);
 		processSQLNamePattern(GetConnection(fout), query, cell->val, true,
 							  false, "n.nspname", "c.relname", NULL,
 							  "pg_catalog.pg_table_is_visible(c.oid)");
@@ -2117,6 +2118,9 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo, bool oids)
 	/* Skip FOREIGN TABLEs (no data to dump) */
 	if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 		return;
+	/* Skip partitioned tables (data in partitions) */
+	if (tbinfo->relkind == RELKIND_PARTITIONED_REL)
+		return;
 
 	/* Don't dump data in unlogged tables, if so requested */
 	if (tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED &&
@@ -5195,6 +5199,7 @@ getTables(Archive *fout, int *numTables)
 	int			i_reloftype;
 	int			i_relpages;
 	int			i_changed_acl;
+	int			i_partkeydef;
 
 	/* Make sure we are in proper schema */
 	selectSourceSchema(fout, "pg_catalog");
@@ -5280,7 +5285,8 @@ getTables(Archive *fout, int *numTables)
 						  "OR %s IS NOT NULL "
 						  "OR %s IS NOT NULL"
 						  "))"
-						  "AS changed_acl "
+						  "AS changed_acl, "
+						  "CASE WHEN c.relkind = 'P' THEN pg_catalog.pg_get_partkeydef(c.oid) ELSE NULL END AS partkeydef "
 						  "FROM pg_class c "
 						  "LEFT JOIN pg_depend d ON "
 						  "(c.relkind = '%c' AND "
@@ -5292,7 +5298,7 @@ getTables(Archive *fout, int *numTables)
 						  "(c.oid = pip.objoid "
 						  "AND pip.classoid = 'pg_class'::regclass "
 						  "AND pip.objsubid = 0) "
-				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c') "
+				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c', '%c') "
 						  "ORDER BY c.oid",
 						  acl_subquery->data,
 						  racl_subquery->data,
@@ -5306,7 +5312,8 @@ getTables(Archive *fout, int *numTables)
 						  RELKIND_SEQUENCE,
 						  RELKIND_RELATION, RELKIND_SEQUENCE,
 						  RELKIND_VIEW, RELKIND_COMPOSITE_TYPE,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_REL);
 
 		destroyPQExpBuffer(acl_subquery);
 		destroyPQExpBuffer(racl_subquery);
@@ -5868,6 +5875,7 @@ getTables(Archive *fout, int *numTables)
 	i_toastreloptions = PQfnumber(res, "toast_reloptions");
 	i_reloftype = PQfnumber(res, "reloftype");
 	i_changed_acl = PQfnumber(res, "changed_acl");
+	i_partkeydef = PQfnumber(res, "partkeydef");
 
 	if (dopt->lockWaitTimeout && fout->remoteVersion >= 70300)
 	{
@@ -5938,6 +5946,7 @@ getTables(Archive *fout, int *numTables)
 		else
 			tblinfo[i].checkoption = pg_strdup(PQgetvalue(res, i, i_checkoption));
 		tblinfo[i].toast_reloptions = pg_strdup(PQgetvalue(res, i, i_toastreloptions));
+		tblinfo[i].partkeydef = pg_strdup(PQgetvalue(res, i, i_partkeydef));
 
 		/* other fields were zeroed above */
 
@@ -5982,7 +5991,9 @@ getTables(Archive *fout, int *numTables)
 		 * We only need to lock the table for certain components; see
 		 * pg_dump.h
 		 */
-		if (tblinfo[i].dobj.dump && tblinfo[i].relkind == RELKIND_RELATION &&
+		if (tblinfo[i].dobj.dump &&
+			(tblinfo[i].relkind == RELKIND_RELATION ||
+			 tblinfo->relkind == RELKIND_PARTITIONED_REL) &&
 			(tblinfo[i].dobj.dump & DUMP_COMPONENTS_REQUIRING_LOCK))
 		{
 			resetPQExpBuffer(query);
@@ -6083,7 +6094,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);
 
@@ -15436,6 +15450,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));
 		}
@@ -15496,6 +15513,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++)
@@ -15514,7 +15532,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
@@ -15731,6 +15750,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 	 * dump properties we only have ALTER TABLE syntax for
 	 */
 	if ((tbinfo->relkind == RELKIND_RELATION ||
+		 tbinfo->relkind == RELKIND_PARTITIONED_REL ||
 		 tbinfo->relkind == RELKIND_MATVIEW) &&
 		tbinfo->relreplident != REPLICA_IDENTITY_DEFAULT)
 	{
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 2bfa2d9..0292859 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -310,6 +310,7 @@ typedef struct _tableInfo
 	bool	   *inhNotNull;		/* true if NOT NULL is inherited */
 	struct _attrDefInfo **attrdefs;		/* DEFAULT expressions */
 	struct _constraintInfo *checkexprs; /* CHECK constraints */
+	char	   *partkeydef;		/* partition key definition */
 
 	/*
 	 * Stuff computed only for dumpable tables.
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 27be102..3ba8a90 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -856,6 +856,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"),
@@ -865,6 +866,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");
@@ -911,7 +913,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
@@ -1555,8 +1557,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)");
 	}
 
@@ -1621,6 +1623,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\"",
@@ -1634,8 +1644,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");
@@ -1655,12 +1665,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");
 	}
 
@@ -1760,7 +1770,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);
@@ -1768,14 +1778,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 */
@@ -1914,7 +1943,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;
@@ -2473,7 +2502,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;
@@ -2684,7 +2713,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
@@ -3018,6 +3047,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"),
@@ -3029,6 +3059,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"));
 
@@ -3067,7 +3098,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 
 	appendPQExpBufferStr(&buf, "\nWHERE c.relkind IN (");
 	if (showTables)
-		appendPQExpBufferStr(&buf, "'r',");
+		appendPQExpBufferStr(&buf, "'r', 'P',");
 	if (showViews)
 		appendPQExpBufferStr(&buf, "'v',");
 	if (showMatViews)
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 8469d9f..cd4eb7c 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -427,7 +427,7 @@ static const SchemaQuery Query_for_list_of_tables = {
 	/* catname */
 	"pg_catalog.pg_class c",
 	/* selcondition */
-	"c.relkind IN ('r')",
+	"c.relkind IN ('r', 'P')",
 	/* viscondition */
 	"pg_catalog.pg_table_is_visible(c.oid)",
 	/* namespace */
@@ -458,7 +458,7 @@ static const SchemaQuery Query_for_list_of_updatables = {
 	/* catname */
 	"pg_catalog.pg_class c",
 	/* selcondition */
-	"c.relkind IN ('r', 'f', 'v')",
+	"c.relkind IN ('r', 'f', 'v', 'P')",
 	/* viscondition */
 	"pg_catalog.pg_table_is_visible(c.oid)",
 	/* namespace */
@@ -488,7 +488,7 @@ static const SchemaQuery Query_for_list_of_tsvmf = {
 	/* catname */
 	"pg_catalog.pg_class c",
 	/* selcondition */
-	"c.relkind IN ('r', 'S', 'v', 'm', 'f')",
+	"c.relkind IN ('r', 'S', 'v', 'm', 'f', 'P')",
 	/* viscondition */
 	"pg_catalog.pg_table_is_visible(c.oid)",
 	/* namespace */
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index 270dd21..5a444d9 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/utils/builtins.h b/src/include/utils/builtins.h
index 8cebc86..351bfd3 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -722,6 +722,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/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index fb7a1b3..4fc8c06 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -413,3 +413,29 @@ SELECT connoinherit FROM pg_constraint WHERE conrelid = 'no_inh_con_parted'::reg
 (1 row)
 
 DROP TABLE no_inh_con_parted;
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+Partitioned table "public.describe_range_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition Key: PARTITION BY RANGE (((a + b)))
+
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+Partitioned table "public.describe_list_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition Key: PARTITION BY LIST (a)
+
+DROP TABLE describe_range_key, describe_list_key;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index cb17517..7c7a18a 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -404,3 +404,16 @@ CREATE TABLE no_inh_con_parted (
 ) PARTITION BY RANGE (a);
 SELECT connoinherit FROM pg_constraint WHERE conrelid = 'no_inh_con_parted'::regclass;
 DROP TABLE no_inh_con_parted;
+
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+DROP TABLE describe_range_key, describe_list_key;
-- 
1.7.1


--------------8C4C48E640631A327D3D2826
Content-Type: text/x-diff;
 name="0003-Catalog-and-DDL-for-partitions.patch"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
 filename="0003-Catalog-and-DDL-for-partitions.patch"



^ permalink  raw  reply  [nested|flat] 110+ messages in thread

* [PATCH 2/9] psql and pg_dump support for partitioned tables.
@ 2016-07-12 08:20  amit <[email protected]>
  0 siblings, 0 replies; 110+ messages in thread

From: amit @ 2016-07-12 08:20 UTC (permalink / raw)

Takes care of both the partition key deparse stuff and the new relkind.
---
 src/backend/utils/adt/ruleutils.c          |  140 ++++++++++++++++++++++++++++
 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/pg_proc.h              |    2 +
 src/include/utils/builtins.h               |    1 +
 src/test/regress/expected/create_table.out |   26 +++++
 src/test/regress/sql/create_table.sql      |   13 +++
 9 files changed, 260 insertions(+), 26 deletions(-)

diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 8a81d7a..77ce807 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_table.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
@@ -315,6 +316,7 @@ static char *pg_get_indexdef_worker(Oid indexrelid, int colno,
 					   const Oid *excludeOps,
 					   bool attrsOnly, bool showTblSpc,
 					   int prettyFlags, bool missing_ok);
+static char *pg_get_partkeydef_worker(Oid relId, int prettyFlags);
 static char *pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 							int prettyFlags, bool missing_ok);
 static text *pg_get_expr_worker(text *expr, Oid relid, const char *relname,
@@ -1389,6 +1391,144 @@ 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 [ 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_table	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_table) GETSTRUCT(tuple);
+
+	Assert(form->partedrelid == relId);
+
+	/* Must get partclass, and partexprs the hard way */
+	datum = SysCacheGetAttr(PARTEDRELID, tuple,
+							Anum_pg_partitioned_table_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_table_partexprbin))
+	{
+		Datum		exprsDatum;
+		bool		isnull;
+		char	   *exprsString;
+
+		exprsDatum = SysCacheGetAttr(PARTEDRELID, tuple,
+									 Anum_pg_partitioned_table_partexprbin, &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);
+
+	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,
+											0, 0);
+
+			appendStringInfoString(&buf, 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/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index a5c2d09..c418ba7 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -1253,9 +1253,10 @@ expand_table_name_patterns(Archive *fout,
 						  "SELECT c.oid"
 						  "\nFROM pg_catalog.pg_class c"
 		"\n     LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace"
-					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c')\n",
+					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c')\n",
 						  RELKIND_RELATION, RELKIND_SEQUENCE, RELKIND_VIEW,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 		processSQLNamePattern(GetConnection(fout), query, cell->val, true,
 							  false, "n.nspname", "c.relname", NULL,
 							  "pg_catalog.pg_table_is_visible(c.oid)");
@@ -2117,6 +2118,9 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo, bool oids)
 	/* Skip FOREIGN TABLEs (no data to dump) */
 	if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 		return;
+	/* Skip partitioned tables (data in partitions) */
+	if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+		return;
 
 	/* Don't dump data in unlogged tables, if so requested */
 	if (tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED &&
@@ -5195,6 +5199,7 @@ getTables(Archive *fout, int *numTables)
 	int			i_reloftype;
 	int			i_relpages;
 	int			i_changed_acl;
+	int			i_partkeydef;
 
 	/* Make sure we are in proper schema */
 	selectSourceSchema(fout, "pg_catalog");
@@ -5280,7 +5285,8 @@ getTables(Archive *fout, int *numTables)
 						  "OR %s IS NOT NULL "
 						  "OR %s IS NOT NULL"
 						  "))"
-						  "AS changed_acl "
+						  "AS changed_acl, "
+						  "CASE WHEN c.relkind = 'P' THEN pg_catalog.pg_get_partkeydef(c.oid) ELSE NULL END AS partkeydef "
 						  "FROM pg_class c "
 						  "LEFT JOIN pg_depend d ON "
 						  "(c.relkind = '%c' AND "
@@ -5292,7 +5298,7 @@ getTables(Archive *fout, int *numTables)
 						  "(c.oid = pip.objoid "
 						  "AND pip.classoid = 'pg_class'::regclass "
 						  "AND pip.objsubid = 0) "
-				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c') "
+				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c', '%c') "
 						  "ORDER BY c.oid",
 						  acl_subquery->data,
 						  racl_subquery->data,
@@ -5306,7 +5312,8 @@ getTables(Archive *fout, int *numTables)
 						  RELKIND_SEQUENCE,
 						  RELKIND_RELATION, RELKIND_SEQUENCE,
 						  RELKIND_VIEW, RELKIND_COMPOSITE_TYPE,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 
 		destroyPQExpBuffer(acl_subquery);
 		destroyPQExpBuffer(racl_subquery);
@@ -5868,6 +5875,7 @@ getTables(Archive *fout, int *numTables)
 	i_toastreloptions = PQfnumber(res, "toast_reloptions");
 	i_reloftype = PQfnumber(res, "reloftype");
 	i_changed_acl = PQfnumber(res, "changed_acl");
+	i_partkeydef = PQfnumber(res, "partkeydef");
 
 	if (dopt->lockWaitTimeout && fout->remoteVersion >= 70300)
 	{
@@ -5938,6 +5946,7 @@ getTables(Archive *fout, int *numTables)
 		else
 			tblinfo[i].checkoption = pg_strdup(PQgetvalue(res, i, i_checkoption));
 		tblinfo[i].toast_reloptions = pg_strdup(PQgetvalue(res, i, i_toastreloptions));
+		tblinfo[i].partkeydef = pg_strdup(PQgetvalue(res, i, i_partkeydef));
 
 		/* other fields were zeroed above */
 
@@ -5982,7 +5991,9 @@ getTables(Archive *fout, int *numTables)
 		 * We only need to lock the table for certain components; see
 		 * pg_dump.h
 		 */
-		if (tblinfo[i].dobj.dump && tblinfo[i].relkind == RELKIND_RELATION &&
+		if (tblinfo[i].dobj.dump &&
+			(tblinfo[i].relkind == RELKIND_RELATION ||
+			 tblinfo->relkind == RELKIND_PARTITIONED_TABLE) &&
 			(tblinfo[i].dobj.dump & DUMP_COMPONENTS_REQUIRING_LOCK))
 		{
 			resetPQExpBuffer(query);
@@ -6084,7 +6095,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);
 
@@ -15437,6 +15451,9 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 				appendPQExpBufferChar(q, ')');
 			}
 
+			if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+				appendPQExpBuffer(q, "\nPARTITION BY %s", tbinfo->partkeydef);
+
 			if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 				appendPQExpBuffer(q, "\nSERVER %s", fmtId(srvname));
 		}
@@ -15497,6 +15514,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 		 */
 		if (dopt->binary_upgrade &&
 			(tbinfo->relkind == RELKIND_RELATION ||
+			 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 			 tbinfo->relkind == RELKIND_FOREIGN_TABLE))
 		{
 			for (j = 0; j < tbinfo->numatts; j++)
@@ -15515,7 +15533,8 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 					appendStringLiteralAH(q, fmtId(tbinfo->dobj.name), fout);
 					appendPQExpBufferStr(q, "::pg_catalog.regclass;\n");
 
-					if (tbinfo->relkind == RELKIND_RELATION)
+					if (tbinfo->relkind == RELKIND_RELATION ||
+						tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
 						appendPQExpBuffer(q, "ALTER TABLE ONLY %s ",
 										  fmtId(tbinfo->dobj.name));
 					else
@@ -15732,6 +15751,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 	 * dump properties we only have ALTER TABLE syntax for
 	 */
 	if ((tbinfo->relkind == RELKIND_RELATION ||
+		 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 		 tbinfo->relkind == RELKIND_MATVIEW) &&
 		tbinfo->relreplident != REPLICA_IDENTITY_DEFAULT)
 	{
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 2bfa2d9..0292859 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -310,6 +310,7 @@ typedef struct _tableInfo
 	bool	   *inhNotNull;		/* true if NOT NULL is inherited */
 	struct _attrDefInfo **attrdefs;		/* DEFAULT expressions */
 	struct _constraintInfo *checkexprs; /* CHECK constraints */
+	char	   *partkeydef;		/* partition key definition */
 
 	/*
 	 * Stuff computed only for dumpable tables.
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 6275a68..10d924a 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -861,6 +861,7 @@ permissionsList(const char *pattern)
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'S' THEN '%s'"
@@ -870,6 +871,7 @@ permissionsList(const char *pattern)
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("sequence"),
@@ -920,7 +922,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
@@ -1567,8 +1569,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)");
 	}
 
@@ -1633,6 +1635,14 @@ describeOneTableDetails(const char *schemaname,
 			printfPQExpBuffer(&title, _("Foreign table \"%s.%s\""),
 							  schemaname, relationname);
 			break;
+		case 'P':
+			if (tableinfo.relpersistence == 'u')
+				printfPQExpBuffer(&title, _("Unlogged table \"%s.%s\""),
+								  schemaname, relationname);
+			else
+				printfPQExpBuffer(&title, _("Table \"%s.%s\""),
+								  schemaname, relationname);
+			break;
 		default:
 			/* untranslated unknown relkind */
 			printfPQExpBuffer(&title, "?%c? \"%s.%s\"",
@@ -1646,8 +1656,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");
@@ -1667,12 +1677,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");
 	}
 
@@ -1772,7 +1782,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);
@@ -1780,14 +1790,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 (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 */
@@ -1926,7 +1955,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;
@@ -2485,7 +2514,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;
@@ -2696,7 +2725,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
@@ -3024,6 +3053,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'i' THEN '%s'"
@@ -3035,6 +3065,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("index"),
@@ -3079,7 +3110,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 1345e4e..4686cd1 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/pg_proc.h b/src/include/catalog/pg_proc.h
index e2d08ba..b45688b 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -1980,6 +1980,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/utils/builtins.h b/src/include/utils/builtins.h
index 2ae212a..e800647 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -729,6 +729,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/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 708232d..2fec847 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -408,3 +408,29 @@ CREATE TABLE no_inh_con_parted (
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
 ERROR:  cannot add NO INHERIT constraint to partitioned table "no_inh_con_parted"
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+Table "public.describe_range_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition Key: RANGE ((a + b))
+
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+Table "public.describe_list_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition Key: LIST (a)
+
+DROP TABLE describe_range_key, describe_list_key;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 6e4a8be..4dd6a0a 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -402,3 +402,16 @@ CREATE TABLE no_inh_con_parted (
 	a int,
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
+
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+DROP TABLE describe_range_key, describe_list_key;
-- 
1.7.1


--------------954458C17BBEB1BF6FC758E7
Content-Type: text/x-diff;
 name="0003-Catalog-and-DDL-for-partitions-2.patch"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
 filename="0003-Catalog-and-DDL-for-partitions-2.patch"



^ permalink  raw  reply  [nested|flat] 110+ messages in thread

* [PATCH 2/9] psql and pg_dump support for partitioned tables.
@ 2016-07-12 08:20  amit <[email protected]>
  0 siblings, 0 replies; 110+ messages in thread

From: amit @ 2016-07-12 08:20 UTC (permalink / raw)

Takes care of both the partition key deparse stuff and the new relkind.
---
 src/backend/utils/adt/ruleutils.c          |  140 ++++++++++++++++++++++++++++
 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/pg_proc.h              |    2 +
 src/include/utils/builtins.h               |    1 +
 src/test/regress/expected/create_table.out |   26 +++++
 src/test/regress/sql/create_table.sql      |   13 +++
 9 files changed, 260 insertions(+), 26 deletions(-)

diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 8a81d7a..77ce807 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_table.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
@@ -315,6 +316,7 @@ static char *pg_get_indexdef_worker(Oid indexrelid, int colno,
 					   const Oid *excludeOps,
 					   bool attrsOnly, bool showTblSpc,
 					   int prettyFlags, bool missing_ok);
+static char *pg_get_partkeydef_worker(Oid relId, int prettyFlags);
 static char *pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 							int prettyFlags, bool missing_ok);
 static text *pg_get_expr_worker(text *expr, Oid relid, const char *relname,
@@ -1389,6 +1391,144 @@ 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 [ 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_table	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_table) GETSTRUCT(tuple);
+
+	Assert(form->partedrelid == relId);
+
+	/* Must get partclass, and partexprs the hard way */
+	datum = SysCacheGetAttr(PARTEDRELID, tuple,
+							Anum_pg_partitioned_table_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_table_partexprbin))
+	{
+		Datum		exprsDatum;
+		bool		isnull;
+		char	   *exprsString;
+
+		exprsDatum = SysCacheGetAttr(PARTEDRELID, tuple,
+									 Anum_pg_partitioned_table_partexprbin, &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);
+
+	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,
+											0, 0);
+
+			appendStringInfoString(&buf, 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/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index a5c2d09..c418ba7 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -1253,9 +1253,10 @@ expand_table_name_patterns(Archive *fout,
 						  "SELECT c.oid"
 						  "\nFROM pg_catalog.pg_class c"
 		"\n     LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace"
-					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c')\n",
+					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c')\n",
 						  RELKIND_RELATION, RELKIND_SEQUENCE, RELKIND_VIEW,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 		processSQLNamePattern(GetConnection(fout), query, cell->val, true,
 							  false, "n.nspname", "c.relname", NULL,
 							  "pg_catalog.pg_table_is_visible(c.oid)");
@@ -2117,6 +2118,9 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo, bool oids)
 	/* Skip FOREIGN TABLEs (no data to dump) */
 	if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 		return;
+	/* Skip partitioned tables (data in partitions) */
+	if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+		return;
 
 	/* Don't dump data in unlogged tables, if so requested */
 	if (tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED &&
@@ -5195,6 +5199,7 @@ getTables(Archive *fout, int *numTables)
 	int			i_reloftype;
 	int			i_relpages;
 	int			i_changed_acl;
+	int			i_partkeydef;
 
 	/* Make sure we are in proper schema */
 	selectSourceSchema(fout, "pg_catalog");
@@ -5280,7 +5285,8 @@ getTables(Archive *fout, int *numTables)
 						  "OR %s IS NOT NULL "
 						  "OR %s IS NOT NULL"
 						  "))"
-						  "AS changed_acl "
+						  "AS changed_acl, "
+						  "CASE WHEN c.relkind = 'P' THEN pg_catalog.pg_get_partkeydef(c.oid) ELSE NULL END AS partkeydef "
 						  "FROM pg_class c "
 						  "LEFT JOIN pg_depend d ON "
 						  "(c.relkind = '%c' AND "
@@ -5292,7 +5298,7 @@ getTables(Archive *fout, int *numTables)
 						  "(c.oid = pip.objoid "
 						  "AND pip.classoid = 'pg_class'::regclass "
 						  "AND pip.objsubid = 0) "
-				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c') "
+				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c', '%c') "
 						  "ORDER BY c.oid",
 						  acl_subquery->data,
 						  racl_subquery->data,
@@ -5306,7 +5312,8 @@ getTables(Archive *fout, int *numTables)
 						  RELKIND_SEQUENCE,
 						  RELKIND_RELATION, RELKIND_SEQUENCE,
 						  RELKIND_VIEW, RELKIND_COMPOSITE_TYPE,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 
 		destroyPQExpBuffer(acl_subquery);
 		destroyPQExpBuffer(racl_subquery);
@@ -5868,6 +5875,7 @@ getTables(Archive *fout, int *numTables)
 	i_toastreloptions = PQfnumber(res, "toast_reloptions");
 	i_reloftype = PQfnumber(res, "reloftype");
 	i_changed_acl = PQfnumber(res, "changed_acl");
+	i_partkeydef = PQfnumber(res, "partkeydef");
 
 	if (dopt->lockWaitTimeout && fout->remoteVersion >= 70300)
 	{
@@ -5938,6 +5946,7 @@ getTables(Archive *fout, int *numTables)
 		else
 			tblinfo[i].checkoption = pg_strdup(PQgetvalue(res, i, i_checkoption));
 		tblinfo[i].toast_reloptions = pg_strdup(PQgetvalue(res, i, i_toastreloptions));
+		tblinfo[i].partkeydef = pg_strdup(PQgetvalue(res, i, i_partkeydef));
 
 		/* other fields were zeroed above */
 
@@ -5982,7 +5991,9 @@ getTables(Archive *fout, int *numTables)
 		 * We only need to lock the table for certain components; see
 		 * pg_dump.h
 		 */
-		if (tblinfo[i].dobj.dump && tblinfo[i].relkind == RELKIND_RELATION &&
+		if (tblinfo[i].dobj.dump &&
+			(tblinfo[i].relkind == RELKIND_RELATION ||
+			 tblinfo->relkind == RELKIND_PARTITIONED_TABLE) &&
 			(tblinfo[i].dobj.dump & DUMP_COMPONENTS_REQUIRING_LOCK))
 		{
 			resetPQExpBuffer(query);
@@ -6084,7 +6095,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);
 
@@ -15437,6 +15451,9 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 				appendPQExpBufferChar(q, ')');
 			}
 
+			if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+				appendPQExpBuffer(q, "\nPARTITION BY %s", tbinfo->partkeydef);
+
 			if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 				appendPQExpBuffer(q, "\nSERVER %s", fmtId(srvname));
 		}
@@ -15497,6 +15514,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 		 */
 		if (dopt->binary_upgrade &&
 			(tbinfo->relkind == RELKIND_RELATION ||
+			 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 			 tbinfo->relkind == RELKIND_FOREIGN_TABLE))
 		{
 			for (j = 0; j < tbinfo->numatts; j++)
@@ -15515,7 +15533,8 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 					appendStringLiteralAH(q, fmtId(tbinfo->dobj.name), fout);
 					appendPQExpBufferStr(q, "::pg_catalog.regclass;\n");
 
-					if (tbinfo->relkind == RELKIND_RELATION)
+					if (tbinfo->relkind == RELKIND_RELATION ||
+						tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
 						appendPQExpBuffer(q, "ALTER TABLE ONLY %s ",
 										  fmtId(tbinfo->dobj.name));
 					else
@@ -15732,6 +15751,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 	 * dump properties we only have ALTER TABLE syntax for
 	 */
 	if ((tbinfo->relkind == RELKIND_RELATION ||
+		 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 		 tbinfo->relkind == RELKIND_MATVIEW) &&
 		tbinfo->relreplident != REPLICA_IDENTITY_DEFAULT)
 	{
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 2bfa2d9..0292859 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -310,6 +310,7 @@ typedef struct _tableInfo
 	bool	   *inhNotNull;		/* true if NOT NULL is inherited */
 	struct _attrDefInfo **attrdefs;		/* DEFAULT expressions */
 	struct _constraintInfo *checkexprs; /* CHECK constraints */
+	char	   *partkeydef;		/* partition key definition */
 
 	/*
 	 * Stuff computed only for dumpable tables.
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 6275a68..10d924a 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -861,6 +861,7 @@ permissionsList(const char *pattern)
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'S' THEN '%s'"
@@ -870,6 +871,7 @@ permissionsList(const char *pattern)
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("sequence"),
@@ -920,7 +922,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
@@ -1567,8 +1569,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)");
 	}
 
@@ -1633,6 +1635,14 @@ describeOneTableDetails(const char *schemaname,
 			printfPQExpBuffer(&title, _("Foreign table \"%s.%s\""),
 							  schemaname, relationname);
 			break;
+		case 'P':
+			if (tableinfo.relpersistence == 'u')
+				printfPQExpBuffer(&title, _("Unlogged table \"%s.%s\""),
+								  schemaname, relationname);
+			else
+				printfPQExpBuffer(&title, _("Table \"%s.%s\""),
+								  schemaname, relationname);
+			break;
 		default:
 			/* untranslated unknown relkind */
 			printfPQExpBuffer(&title, "?%c? \"%s.%s\"",
@@ -1646,8 +1656,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");
@@ -1667,12 +1677,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");
 	}
 
@@ -1772,7 +1782,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);
@@ -1780,14 +1790,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 (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 */
@@ -1926,7 +1955,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;
@@ -2485,7 +2514,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;
@@ -2696,7 +2725,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
@@ -3024,6 +3053,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'i' THEN '%s'"
@@ -3035,6 +3065,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("index"),
@@ -3079,7 +3110,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 019f75a..5e5c370 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/pg_proc.h b/src/include/catalog/pg_proc.h
index e2d08ba..b45688b 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -1980,6 +1980,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/utils/builtins.h b/src/include/utils/builtins.h
index 2ae212a..e800647 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -729,6 +729,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/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 708232d..2fec847 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -408,3 +408,29 @@ CREATE TABLE no_inh_con_parted (
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
 ERROR:  cannot add NO INHERIT constraint to partitioned table "no_inh_con_parted"
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+Table "public.describe_range_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition Key: RANGE ((a + b))
+
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+Table "public.describe_list_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition Key: LIST (a)
+
+DROP TABLE describe_range_key, describe_list_key;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 6e4a8be..4dd6a0a 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -402,3 +402,16 @@ CREATE TABLE no_inh_con_parted (
 	a int,
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
+
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+DROP TABLE describe_range_key, describe_list_key;
-- 
1.7.1


--------------6EB5B318125CB7DB1A4FD229
Content-Type: text/x-diff;
 name="0003-Catalog-and-DDL-for-partitions-3.patch"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
 filename="0003-Catalog-and-DDL-for-partitions-3.patch"



^ permalink  raw  reply  [nested|flat] 110+ messages in thread

* [PATCH 2/9] psql and pg_dump support for partitioned tables.
@ 2016-07-12 08:20  amit <[email protected]>
  0 siblings, 0 replies; 110+ messages in thread

From: amit @ 2016-07-12 08:20 UTC (permalink / raw)

Takes care of both the partition key deparse stuff and the new relkind.
---
 src/backend/utils/adt/ruleutils.c          |  140 ++++++++++++++++++++++++++++
 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/pg_proc.h              |    2 +
 src/include/utils/builtins.h               |    1 +
 src/test/regress/expected/create_table.out |   26 +++++
 src/test/regress/sql/create_table.sql      |   13 +++
 9 files changed, 260 insertions(+), 26 deletions(-)

diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 8a81d7a..03be202 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_table.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
@@ -315,6 +316,7 @@ static char *pg_get_indexdef_worker(Oid indexrelid, int colno,
 					   const Oid *excludeOps,
 					   bool attrsOnly, bool showTblSpc,
 					   int prettyFlags, bool missing_ok);
+static char *pg_get_partkeydef_worker(Oid relid, int prettyFlags);
 static char *pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 							int prettyFlags, bool missing_ok);
 static text *pg_get_expr_worker(text *expr, Oid relid, const char *relname,
@@ -1389,6 +1391,144 @@ 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 [ opclass_name ] [, ...])
+ */
+Datum
+pg_get_partkeydef(PG_FUNCTION_ARGS)
+{
+	Oid			relid = PG_GETARG_OID(0);
+	int			prettyFlags;
+
+	prettyFlags = PRETTYFLAG_INDENT;
+	PG_RETURN_TEXT_P(string_to_text(pg_get_partkeydef_worker(relid,
+									prettyFlags)));
+}
+
+/*
+ * Internal workhorse to decompile a partition key definition.
+ */
+static char *
+pg_get_partkeydef_worker(Oid relid, int prettyFlags)
+{
+	Form_pg_partitioned_table	form;
+	HeapTuple	tuple;
+	oidvector  *partclass;
+	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_table) GETSTRUCT(tuple);
+
+	Assert(form->partrelid == relid);
+
+	/* Must get partclass, and partexprs the hard way */
+	datum = SysCacheGetAttr(PARTEDRELID, tuple,
+							Anum_pg_partitioned_table_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_table_partexprbin))
+	{
+		Datum		exprsDatum;
+		bool		isnull;
+		char	   *exprsString;
+
+		exprsDatum = SysCacheGetAttr(PARTEDRELID, tuple,
+									 Anum_pg_partitioned_table_partexprbin, &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);
+
+	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,
+											0, 0);
+
+			appendStringInfoString(&buf, 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/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index ba9c276..c805a84 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -1253,9 +1253,10 @@ expand_table_name_patterns(Archive *fout,
 						  "SELECT c.oid"
 						  "\nFROM pg_catalog.pg_class c"
 		"\n     LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace"
-					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c')\n",
+					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c')\n",
 						  RELKIND_RELATION, RELKIND_SEQUENCE, RELKIND_VIEW,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 		processSQLNamePattern(GetConnection(fout), query, cell->val, true,
 							  false, "n.nspname", "c.relname", NULL,
 							  "pg_catalog.pg_table_is_visible(c.oid)");
@@ -2125,6 +2126,9 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo, bool oids)
 	/* Skip FOREIGN TABLEs (no data to dump) */
 	if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 		return;
+	/* Skip partitioned tables (data in partitions) */
+	if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+		return;
 
 	/* Don't dump data in unlogged tables, if so requested */
 	if (tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED &&
@@ -5204,6 +5208,7 @@ getTables(Archive *fout, int *numTables)
 	int			i_reloftype;
 	int			i_relpages;
 	int			i_changed_acl;
+	int			i_partkeydef;
 
 	/* Make sure we are in proper schema */
 	selectSourceSchema(fout, "pg_catalog");
@@ -5289,7 +5294,8 @@ getTables(Archive *fout, int *numTables)
 						  "OR %s IS NOT NULL "
 						  "OR %s IS NOT NULL"
 						  "))"
-						  "AS changed_acl "
+						  "AS changed_acl, "
+						  "CASE WHEN c.relkind = 'P' THEN pg_catalog.pg_get_partkeydef(c.oid) ELSE NULL END AS partkeydef "
 						  "FROM pg_class c "
 						  "LEFT JOIN pg_depend d ON "
 						  "(c.relkind = '%c' AND "
@@ -5301,7 +5307,7 @@ getTables(Archive *fout, int *numTables)
 						  "(c.oid = pip.objoid "
 						  "AND pip.classoid = 'pg_class'::regclass "
 						  "AND pip.objsubid = 0) "
-				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c') "
+				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c', '%c') "
 						  "ORDER BY c.oid",
 						  acl_subquery->data,
 						  racl_subquery->data,
@@ -5315,7 +5321,8 @@ getTables(Archive *fout, int *numTables)
 						  RELKIND_SEQUENCE,
 						  RELKIND_RELATION, RELKIND_SEQUENCE,
 						  RELKIND_VIEW, RELKIND_COMPOSITE_TYPE,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 
 		destroyPQExpBuffer(acl_subquery);
 		destroyPQExpBuffer(racl_subquery);
@@ -5877,6 +5884,7 @@ getTables(Archive *fout, int *numTables)
 	i_toastreloptions = PQfnumber(res, "toast_reloptions");
 	i_reloftype = PQfnumber(res, "reloftype");
 	i_changed_acl = PQfnumber(res, "changed_acl");
+	i_partkeydef = PQfnumber(res, "partkeydef");
 
 	if (dopt->lockWaitTimeout && fout->remoteVersion >= 70300)
 	{
@@ -5947,6 +5955,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 */
 
@@ -5991,7 +6000,9 @@ getTables(Archive *fout, int *numTables)
 		 * We only need to lock the table for certain components; see
 		 * pg_dump.h
 		 */
-		if (tblinfo[i].dobj.dump && tblinfo[i].relkind == RELKIND_RELATION &&
+		if (tblinfo[i].dobj.dump &&
+			(tblinfo[i].relkind == RELKIND_RELATION ||
+			 tblinfo->relkind == RELKIND_PARTITIONED_TABLE) &&
 			(tblinfo[i].dobj.dump & DUMP_COMPONENTS_REQUIRING_LOCK))
 		{
 			resetPQExpBuffer(query);
@@ -6093,7 +6104,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);
 
@@ -15446,6 +15460,9 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 				appendPQExpBufferChar(q, ')');
 			}
 
+			if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+				appendPQExpBuffer(q, "\nPARTITION BY %s", tbinfo->partkeydef);
+
 			if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 				appendPQExpBuffer(q, "\nSERVER %s", fmtId(srvname));
 		}
@@ -15506,6 +15523,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 		 */
 		if (dopt->binary_upgrade &&
 			(tbinfo->relkind == RELKIND_RELATION ||
+			 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 			 tbinfo->relkind == RELKIND_FOREIGN_TABLE))
 		{
 			for (j = 0; j < tbinfo->numatts; j++)
@@ -15524,7 +15542,8 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 					appendStringLiteralAH(q, fmtId(tbinfo->dobj.name), fout);
 					appendPQExpBufferStr(q, "::pg_catalog.regclass;\n");
 
-					if (tbinfo->relkind == RELKIND_RELATION)
+					if (tbinfo->relkind == RELKIND_RELATION ||
+						tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
 						appendPQExpBuffer(q, "ALTER TABLE ONLY %s ",
 										  fmtId(tbinfo->dobj.name));
 					else
@@ -15741,6 +15760,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 	 * dump properties we only have ALTER TABLE syntax for
 	 */
 	if ((tbinfo->relkind == RELKIND_RELATION ||
+		 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 		 tbinfo->relkind == RELKIND_MATVIEW) &&
 		tbinfo->relreplident != REPLICA_IDENTITY_DEFAULT)
 	{
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 2bfa2d9..0292859 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -310,6 +310,7 @@ typedef struct _tableInfo
 	bool	   *inhNotNull;		/* true if NOT NULL is inherited */
 	struct _attrDefInfo **attrdefs;		/* DEFAULT expressions */
 	struct _constraintInfo *checkexprs; /* CHECK constraints */
+	char	   *partkeydef;		/* partition key definition */
 
 	/*
 	 * Stuff computed only for dumpable tables.
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 6275a68..10d924a 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -861,6 +861,7 @@ permissionsList(const char *pattern)
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'S' THEN '%s'"
@@ -870,6 +871,7 @@ permissionsList(const char *pattern)
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("sequence"),
@@ -920,7 +922,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
@@ -1567,8 +1569,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)");
 	}
 
@@ -1633,6 +1635,14 @@ describeOneTableDetails(const char *schemaname,
 			printfPQExpBuffer(&title, _("Foreign table \"%s.%s\""),
 							  schemaname, relationname);
 			break;
+		case 'P':
+			if (tableinfo.relpersistence == 'u')
+				printfPQExpBuffer(&title, _("Unlogged table \"%s.%s\""),
+								  schemaname, relationname);
+			else
+				printfPQExpBuffer(&title, _("Table \"%s.%s\""),
+								  schemaname, relationname);
+			break;
 		default:
 			/* untranslated unknown relkind */
 			printfPQExpBuffer(&title, "?%c? \"%s.%s\"",
@@ -1646,8 +1656,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");
@@ -1667,12 +1677,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");
 	}
 
@@ -1772,7 +1782,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);
@@ -1780,14 +1790,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 (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 */
@@ -1926,7 +1955,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;
@@ -2485,7 +2514,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;
@@ -2696,7 +2725,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
@@ -3024,6 +3053,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'i' THEN '%s'"
@@ -3035,6 +3065,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("index"),
@@ -3079,7 +3110,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 019f75a..5e5c370 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/pg_proc.h b/src/include/catalog/pg_proc.h
index e2d08ba..b45688b 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -1980,6 +1980,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/utils/builtins.h b/src/include/utils/builtins.h
index 2ae212a..e800647 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -729,6 +729,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/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 708232d..2fec847 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -408,3 +408,29 @@ CREATE TABLE no_inh_con_parted (
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
 ERROR:  cannot add NO INHERIT constraint to partitioned table "no_inh_con_parted"
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+Table "public.describe_range_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition Key: RANGE ((a + b))
+
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+Table "public.describe_list_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition Key: LIST (a)
+
+DROP TABLE describe_range_key, describe_list_key;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 6e4a8be..4dd6a0a 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -402,3 +402,16 @@ CREATE TABLE no_inh_con_parted (
 	a int,
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
+
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+DROP TABLE describe_range_key, describe_list_key;
-- 
1.7.1


--------------4012470FC48539D6F6714CEA
Content-Type: text/x-diff;
 name="0003-Catalog-and-DDL-for-partitions-4.patch"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
 filename="0003-Catalog-and-DDL-for-partitions-4.patch"



^ permalink  raw  reply  [nested|flat] 110+ messages in thread

* [PATCH 2/9] psql and pg_dump support for partitioned tables.
@ 2016-07-12 08:20  amit <[email protected]>
  0 siblings, 0 replies; 110+ messages in thread

From: amit @ 2016-07-12 08:20 UTC (permalink / raw)

Takes care of both the partition key deparse stuff and the new relkind.
---
 src/backend/utils/adt/ruleutils.c          |  140 ++++++++++++++++++++++++++++
 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/pg_proc.h              |    2 +
 src/include/utils/builtins.h               |    1 +
 src/test/regress/expected/create_table.out |   26 +++++
 src/test/regress/sql/create_table.sql      |   13 +++
 9 files changed, 260 insertions(+), 26 deletions(-)

diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 8a81d7a..77ce807 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_table.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
@@ -315,6 +316,7 @@ static char *pg_get_indexdef_worker(Oid indexrelid, int colno,
 					   const Oid *excludeOps,
 					   bool attrsOnly, bool showTblSpc,
 					   int prettyFlags, bool missing_ok);
+static char *pg_get_partkeydef_worker(Oid relId, int prettyFlags);
 static char *pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 							int prettyFlags, bool missing_ok);
 static text *pg_get_expr_worker(text *expr, Oid relid, const char *relname,
@@ -1389,6 +1391,144 @@ 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 [ 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_table	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_table) GETSTRUCT(tuple);
+
+	Assert(form->partedrelid == relId);
+
+	/* Must get partclass, and partexprs the hard way */
+	datum = SysCacheGetAttr(PARTEDRELID, tuple,
+							Anum_pg_partitioned_table_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_table_partexprbin))
+	{
+		Datum		exprsDatum;
+		bool		isnull;
+		char	   *exprsString;
+
+		exprsDatum = SysCacheGetAttr(PARTEDRELID, tuple,
+									 Anum_pg_partitioned_table_partexprbin, &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);
+
+	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,
+											0, 0);
+
+			appendStringInfoString(&buf, 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/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index a5c2d09..c418ba7 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -1253,9 +1253,10 @@ expand_table_name_patterns(Archive *fout,
 						  "SELECT c.oid"
 						  "\nFROM pg_catalog.pg_class c"
 		"\n     LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace"
-					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c')\n",
+					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c')\n",
 						  RELKIND_RELATION, RELKIND_SEQUENCE, RELKIND_VIEW,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 		processSQLNamePattern(GetConnection(fout), query, cell->val, true,
 							  false, "n.nspname", "c.relname", NULL,
 							  "pg_catalog.pg_table_is_visible(c.oid)");
@@ -2117,6 +2118,9 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo, bool oids)
 	/* Skip FOREIGN TABLEs (no data to dump) */
 	if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 		return;
+	/* Skip partitioned tables (data in partitions) */
+	if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+		return;
 
 	/* Don't dump data in unlogged tables, if so requested */
 	if (tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED &&
@@ -5195,6 +5199,7 @@ getTables(Archive *fout, int *numTables)
 	int			i_reloftype;
 	int			i_relpages;
 	int			i_changed_acl;
+	int			i_partkeydef;
 
 	/* Make sure we are in proper schema */
 	selectSourceSchema(fout, "pg_catalog");
@@ -5280,7 +5285,8 @@ getTables(Archive *fout, int *numTables)
 						  "OR %s IS NOT NULL "
 						  "OR %s IS NOT NULL"
 						  "))"
-						  "AS changed_acl "
+						  "AS changed_acl, "
+						  "CASE WHEN c.relkind = 'P' THEN pg_catalog.pg_get_partkeydef(c.oid) ELSE NULL END AS partkeydef "
 						  "FROM pg_class c "
 						  "LEFT JOIN pg_depend d ON "
 						  "(c.relkind = '%c' AND "
@@ -5292,7 +5298,7 @@ getTables(Archive *fout, int *numTables)
 						  "(c.oid = pip.objoid "
 						  "AND pip.classoid = 'pg_class'::regclass "
 						  "AND pip.objsubid = 0) "
-				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c') "
+				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c', '%c') "
 						  "ORDER BY c.oid",
 						  acl_subquery->data,
 						  racl_subquery->data,
@@ -5306,7 +5312,8 @@ getTables(Archive *fout, int *numTables)
 						  RELKIND_SEQUENCE,
 						  RELKIND_RELATION, RELKIND_SEQUENCE,
 						  RELKIND_VIEW, RELKIND_COMPOSITE_TYPE,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 
 		destroyPQExpBuffer(acl_subquery);
 		destroyPQExpBuffer(racl_subquery);
@@ -5868,6 +5875,7 @@ getTables(Archive *fout, int *numTables)
 	i_toastreloptions = PQfnumber(res, "toast_reloptions");
 	i_reloftype = PQfnumber(res, "reloftype");
 	i_changed_acl = PQfnumber(res, "changed_acl");
+	i_partkeydef = PQfnumber(res, "partkeydef");
 
 	if (dopt->lockWaitTimeout && fout->remoteVersion >= 70300)
 	{
@@ -5938,6 +5946,7 @@ getTables(Archive *fout, int *numTables)
 		else
 			tblinfo[i].checkoption = pg_strdup(PQgetvalue(res, i, i_checkoption));
 		tblinfo[i].toast_reloptions = pg_strdup(PQgetvalue(res, i, i_toastreloptions));
+		tblinfo[i].partkeydef = pg_strdup(PQgetvalue(res, i, i_partkeydef));
 
 		/* other fields were zeroed above */
 
@@ -5982,7 +5991,9 @@ getTables(Archive *fout, int *numTables)
 		 * We only need to lock the table for certain components; see
 		 * pg_dump.h
 		 */
-		if (tblinfo[i].dobj.dump && tblinfo[i].relkind == RELKIND_RELATION &&
+		if (tblinfo[i].dobj.dump &&
+			(tblinfo[i].relkind == RELKIND_RELATION ||
+			 tblinfo->relkind == RELKIND_PARTITIONED_TABLE) &&
 			(tblinfo[i].dobj.dump & DUMP_COMPONENTS_REQUIRING_LOCK))
 		{
 			resetPQExpBuffer(query);
@@ -6084,7 +6095,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);
 
@@ -15437,6 +15451,9 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 				appendPQExpBufferChar(q, ')');
 			}
 
+			if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+				appendPQExpBuffer(q, "\nPARTITION BY %s", tbinfo->partkeydef);
+
 			if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 				appendPQExpBuffer(q, "\nSERVER %s", fmtId(srvname));
 		}
@@ -15497,6 +15514,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 		 */
 		if (dopt->binary_upgrade &&
 			(tbinfo->relkind == RELKIND_RELATION ||
+			 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 			 tbinfo->relkind == RELKIND_FOREIGN_TABLE))
 		{
 			for (j = 0; j < tbinfo->numatts; j++)
@@ -15515,7 +15533,8 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 					appendStringLiteralAH(q, fmtId(tbinfo->dobj.name), fout);
 					appendPQExpBufferStr(q, "::pg_catalog.regclass;\n");
 
-					if (tbinfo->relkind == RELKIND_RELATION)
+					if (tbinfo->relkind == RELKIND_RELATION ||
+						tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
 						appendPQExpBuffer(q, "ALTER TABLE ONLY %s ",
 										  fmtId(tbinfo->dobj.name));
 					else
@@ -15732,6 +15751,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 	 * dump properties we only have ALTER TABLE syntax for
 	 */
 	if ((tbinfo->relkind == RELKIND_RELATION ||
+		 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 		 tbinfo->relkind == RELKIND_MATVIEW) &&
 		tbinfo->relreplident != REPLICA_IDENTITY_DEFAULT)
 	{
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 2bfa2d9..0292859 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -310,6 +310,7 @@ typedef struct _tableInfo
 	bool	   *inhNotNull;		/* true if NOT NULL is inherited */
 	struct _attrDefInfo **attrdefs;		/* DEFAULT expressions */
 	struct _constraintInfo *checkexprs; /* CHECK constraints */
+	char	   *partkeydef;		/* partition key definition */
 
 	/*
 	 * Stuff computed only for dumpable tables.
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 6275a68..10d924a 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -861,6 +861,7 @@ permissionsList(const char *pattern)
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'S' THEN '%s'"
@@ -870,6 +871,7 @@ permissionsList(const char *pattern)
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("sequence"),
@@ -920,7 +922,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
@@ -1567,8 +1569,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)");
 	}
 
@@ -1633,6 +1635,14 @@ describeOneTableDetails(const char *schemaname,
 			printfPQExpBuffer(&title, _("Foreign table \"%s.%s\""),
 							  schemaname, relationname);
 			break;
+		case 'P':
+			if (tableinfo.relpersistence == 'u')
+				printfPQExpBuffer(&title, _("Unlogged table \"%s.%s\""),
+								  schemaname, relationname);
+			else
+				printfPQExpBuffer(&title, _("Table \"%s.%s\""),
+								  schemaname, relationname);
+			break;
 		default:
 			/* untranslated unknown relkind */
 			printfPQExpBuffer(&title, "?%c? \"%s.%s\"",
@@ -1646,8 +1656,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");
@@ -1667,12 +1677,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");
 	}
 
@@ -1772,7 +1782,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);
@@ -1780,14 +1790,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 (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 */
@@ -1926,7 +1955,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;
@@ -2485,7 +2514,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;
@@ -2696,7 +2725,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
@@ -3024,6 +3053,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'i' THEN '%s'"
@@ -3035,6 +3065,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("index"),
@@ -3079,7 +3110,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 019f75a..5e5c370 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/pg_proc.h b/src/include/catalog/pg_proc.h
index e2d08ba..b45688b 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -1980,6 +1980,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/utils/builtins.h b/src/include/utils/builtins.h
index 2ae212a..e800647 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -729,6 +729,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/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 708232d..2fec847 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -408,3 +408,29 @@ CREATE TABLE no_inh_con_parted (
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
 ERROR:  cannot add NO INHERIT constraint to partitioned table "no_inh_con_parted"
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+Table "public.describe_range_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition Key: RANGE ((a + b))
+
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+Table "public.describe_list_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition Key: LIST (a)
+
+DROP TABLE describe_range_key, describe_list_key;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 6e4a8be..4dd6a0a 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -402,3 +402,16 @@ CREATE TABLE no_inh_con_parted (
 	a int,
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
+
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+DROP TABLE describe_range_key, describe_list_key;
-- 
1.7.1


--------------6EB5B318125CB7DB1A4FD229
Content-Type: text/x-diff;
 name="0003-Catalog-and-DDL-for-partitions-3.patch"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
 filename="0003-Catalog-and-DDL-for-partitions-3.patch"



^ permalink  raw  reply  [nested|flat] 110+ messages in thread

* [PATCH 2/9] psql and pg_dump support for partitioned tables.
@ 2016-07-12 08:20  amit <[email protected]>
  0 siblings, 0 replies; 110+ messages in thread

From: amit @ 2016-07-12 08:20 UTC (permalink / raw)

Takes care of both the partition key deparse stuff and the new relkind.
---
 src/backend/utils/adt/ruleutils.c          |  140 ++++++++++++++++++++++++++++
 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/pg_proc.h              |    2 +
 src/include/utils/builtins.h               |    1 +
 src/test/regress/expected/create_table.out |   26 +++++
 src/test/regress/sql/create_table.sql      |   13 +++
 9 files changed, 260 insertions(+), 26 deletions(-)

diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 8a81d7a..03be202 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_table.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
@@ -315,6 +316,7 @@ static char *pg_get_indexdef_worker(Oid indexrelid, int colno,
 					   const Oid *excludeOps,
 					   bool attrsOnly, bool showTblSpc,
 					   int prettyFlags, bool missing_ok);
+static char *pg_get_partkeydef_worker(Oid relid, int prettyFlags);
 static char *pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 							int prettyFlags, bool missing_ok);
 static text *pg_get_expr_worker(text *expr, Oid relid, const char *relname,
@@ -1389,6 +1391,144 @@ 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 [ opclass_name ] [, ...])
+ */
+Datum
+pg_get_partkeydef(PG_FUNCTION_ARGS)
+{
+	Oid			relid = PG_GETARG_OID(0);
+	int			prettyFlags;
+
+	prettyFlags = PRETTYFLAG_INDENT;
+	PG_RETURN_TEXT_P(string_to_text(pg_get_partkeydef_worker(relid,
+									prettyFlags)));
+}
+
+/*
+ * Internal workhorse to decompile a partition key definition.
+ */
+static char *
+pg_get_partkeydef_worker(Oid relid, int prettyFlags)
+{
+	Form_pg_partitioned_table	form;
+	HeapTuple	tuple;
+	oidvector  *partclass;
+	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_table) GETSTRUCT(tuple);
+
+	Assert(form->partrelid == relid);
+
+	/* Must get partclass, and partexprs the hard way */
+	datum = SysCacheGetAttr(PARTEDRELID, tuple,
+							Anum_pg_partitioned_table_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_table_partexprbin))
+	{
+		Datum		exprsDatum;
+		bool		isnull;
+		char	   *exprsString;
+
+		exprsDatum = SysCacheGetAttr(PARTEDRELID, tuple,
+									 Anum_pg_partitioned_table_partexprbin, &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);
+
+	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,
+											0, 0);
+
+			appendStringInfoString(&buf, 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/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index ba9c276..c805a84 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -1253,9 +1253,10 @@ expand_table_name_patterns(Archive *fout,
 						  "SELECT c.oid"
 						  "\nFROM pg_catalog.pg_class c"
 		"\n     LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace"
-					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c')\n",
+					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c')\n",
 						  RELKIND_RELATION, RELKIND_SEQUENCE, RELKIND_VIEW,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 		processSQLNamePattern(GetConnection(fout), query, cell->val, true,
 							  false, "n.nspname", "c.relname", NULL,
 							  "pg_catalog.pg_table_is_visible(c.oid)");
@@ -2125,6 +2126,9 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo, bool oids)
 	/* Skip FOREIGN TABLEs (no data to dump) */
 	if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 		return;
+	/* Skip partitioned tables (data in partitions) */
+	if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+		return;
 
 	/* Don't dump data in unlogged tables, if so requested */
 	if (tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED &&
@@ -5204,6 +5208,7 @@ getTables(Archive *fout, int *numTables)
 	int			i_reloftype;
 	int			i_relpages;
 	int			i_changed_acl;
+	int			i_partkeydef;
 
 	/* Make sure we are in proper schema */
 	selectSourceSchema(fout, "pg_catalog");
@@ -5289,7 +5294,8 @@ getTables(Archive *fout, int *numTables)
 						  "OR %s IS NOT NULL "
 						  "OR %s IS NOT NULL"
 						  "))"
-						  "AS changed_acl "
+						  "AS changed_acl, "
+						  "CASE WHEN c.relkind = 'P' THEN pg_catalog.pg_get_partkeydef(c.oid) ELSE NULL END AS partkeydef "
 						  "FROM pg_class c "
 						  "LEFT JOIN pg_depend d ON "
 						  "(c.relkind = '%c' AND "
@@ -5301,7 +5307,7 @@ getTables(Archive *fout, int *numTables)
 						  "(c.oid = pip.objoid "
 						  "AND pip.classoid = 'pg_class'::regclass "
 						  "AND pip.objsubid = 0) "
-				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c') "
+				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c', '%c') "
 						  "ORDER BY c.oid",
 						  acl_subquery->data,
 						  racl_subquery->data,
@@ -5315,7 +5321,8 @@ getTables(Archive *fout, int *numTables)
 						  RELKIND_SEQUENCE,
 						  RELKIND_RELATION, RELKIND_SEQUENCE,
 						  RELKIND_VIEW, RELKIND_COMPOSITE_TYPE,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 
 		destroyPQExpBuffer(acl_subquery);
 		destroyPQExpBuffer(racl_subquery);
@@ -5877,6 +5884,7 @@ getTables(Archive *fout, int *numTables)
 	i_toastreloptions = PQfnumber(res, "toast_reloptions");
 	i_reloftype = PQfnumber(res, "reloftype");
 	i_changed_acl = PQfnumber(res, "changed_acl");
+	i_partkeydef = PQfnumber(res, "partkeydef");
 
 	if (dopt->lockWaitTimeout && fout->remoteVersion >= 70300)
 	{
@@ -5947,6 +5955,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 */
 
@@ -5991,7 +6000,9 @@ getTables(Archive *fout, int *numTables)
 		 * We only need to lock the table for certain components; see
 		 * pg_dump.h
 		 */
-		if (tblinfo[i].dobj.dump && tblinfo[i].relkind == RELKIND_RELATION &&
+		if (tblinfo[i].dobj.dump &&
+			(tblinfo[i].relkind == RELKIND_RELATION ||
+			 tblinfo->relkind == RELKIND_PARTITIONED_TABLE) &&
 			(tblinfo[i].dobj.dump & DUMP_COMPONENTS_REQUIRING_LOCK))
 		{
 			resetPQExpBuffer(query);
@@ -6093,7 +6104,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);
 
@@ -15446,6 +15460,9 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 				appendPQExpBufferChar(q, ')');
 			}
 
+			if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+				appendPQExpBuffer(q, "\nPARTITION BY %s", tbinfo->partkeydef);
+
 			if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 				appendPQExpBuffer(q, "\nSERVER %s", fmtId(srvname));
 		}
@@ -15506,6 +15523,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 		 */
 		if (dopt->binary_upgrade &&
 			(tbinfo->relkind == RELKIND_RELATION ||
+			 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 			 tbinfo->relkind == RELKIND_FOREIGN_TABLE))
 		{
 			for (j = 0; j < tbinfo->numatts; j++)
@@ -15524,7 +15542,8 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 					appendStringLiteralAH(q, fmtId(tbinfo->dobj.name), fout);
 					appendPQExpBufferStr(q, "::pg_catalog.regclass;\n");
 
-					if (tbinfo->relkind == RELKIND_RELATION)
+					if (tbinfo->relkind == RELKIND_RELATION ||
+						tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
 						appendPQExpBuffer(q, "ALTER TABLE ONLY %s ",
 										  fmtId(tbinfo->dobj.name));
 					else
@@ -15741,6 +15760,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 	 * dump properties we only have ALTER TABLE syntax for
 	 */
 	if ((tbinfo->relkind == RELKIND_RELATION ||
+		 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 		 tbinfo->relkind == RELKIND_MATVIEW) &&
 		tbinfo->relreplident != REPLICA_IDENTITY_DEFAULT)
 	{
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 2bfa2d9..0292859 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -310,6 +310,7 @@ typedef struct _tableInfo
 	bool	   *inhNotNull;		/* true if NOT NULL is inherited */
 	struct _attrDefInfo **attrdefs;		/* DEFAULT expressions */
 	struct _constraintInfo *checkexprs; /* CHECK constraints */
+	char	   *partkeydef;		/* partition key definition */
 
 	/*
 	 * Stuff computed only for dumpable tables.
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 6275a68..10d924a 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -861,6 +861,7 @@ permissionsList(const char *pattern)
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'S' THEN '%s'"
@@ -870,6 +871,7 @@ permissionsList(const char *pattern)
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("sequence"),
@@ -920,7 +922,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
@@ -1567,8 +1569,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)");
 	}
 
@@ -1633,6 +1635,14 @@ describeOneTableDetails(const char *schemaname,
 			printfPQExpBuffer(&title, _("Foreign table \"%s.%s\""),
 							  schemaname, relationname);
 			break;
+		case 'P':
+			if (tableinfo.relpersistence == 'u')
+				printfPQExpBuffer(&title, _("Unlogged table \"%s.%s\""),
+								  schemaname, relationname);
+			else
+				printfPQExpBuffer(&title, _("Table \"%s.%s\""),
+								  schemaname, relationname);
+			break;
 		default:
 			/* untranslated unknown relkind */
 			printfPQExpBuffer(&title, "?%c? \"%s.%s\"",
@@ -1646,8 +1656,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");
@@ -1667,12 +1677,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");
 	}
 
@@ -1772,7 +1782,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);
@@ -1780,14 +1790,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 (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 */
@@ -1926,7 +1955,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;
@@ -2485,7 +2514,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;
@@ -2696,7 +2725,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
@@ -3024,6 +3053,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'i' THEN '%s'"
@@ -3035,6 +3065,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("index"),
@@ -3079,7 +3110,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 50a45eb..8284a9c 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/pg_proc.h b/src/include/catalog/pg_proc.h
index e2d08ba..b45688b 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -1980,6 +1980,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/utils/builtins.h b/src/include/utils/builtins.h
index 2ae212a..e800647 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -729,6 +729,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/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 0af1422..91ba1be 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -412,3 +412,29 @@ CREATE TABLE no_inh_con_parted (
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
 ERROR:  cannot add NO INHERIT constraint to partitioned table "no_inh_con_parted"
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+Table "public.describe_range_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition Key: RANGE ((a + b))
+
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+Table "public.describe_list_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition Key: LIST (a)
+
+DROP TABLE describe_range_key, describe_list_key;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 418ac55..b75542d 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -406,3 +406,16 @@ CREATE TABLE no_inh_con_parted (
 	a int,
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
+
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+DROP TABLE describe_range_key, describe_list_key;
-- 
1.7.1


--------------D008BA677674C4836D7B65AD
Content-Type: text/x-diff;
 name="0003-Catalog-and-DDL-for-partitions-5.patch"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
 filename="0003-Catalog-and-DDL-for-partitions-5.patch"



^ permalink  raw  reply  [nested|flat] 110+ messages in thread

* [PATCH 2/9] psql and pg_dump support for partitioned tables.
@ 2016-07-12 08:20  amit <[email protected]>
  0 siblings, 0 replies; 110+ messages in thread

From: amit @ 2016-07-12 08:20 UTC (permalink / raw)

Takes care of both the partition key deparse stuff and the new relkind.
---
 src/backend/utils/adt/ruleutils.c          |  140 ++++++++++++++++++++++++++++
 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/pg_proc.h              |    2 +
 src/include/utils/builtins.h               |    1 +
 src/test/regress/expected/create_table.out |   26 +++++
 src/test/regress/sql/create_table.sql      |   13 +++
 9 files changed, 260 insertions(+), 26 deletions(-)

diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 8a81d7a..03be202 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_table.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
@@ -315,6 +316,7 @@ static char *pg_get_indexdef_worker(Oid indexrelid, int colno,
 					   const Oid *excludeOps,
 					   bool attrsOnly, bool showTblSpc,
 					   int prettyFlags, bool missing_ok);
+static char *pg_get_partkeydef_worker(Oid relid, int prettyFlags);
 static char *pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 							int prettyFlags, bool missing_ok);
 static text *pg_get_expr_worker(text *expr, Oid relid, const char *relname,
@@ -1389,6 +1391,144 @@ 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 [ opclass_name ] [, ...])
+ */
+Datum
+pg_get_partkeydef(PG_FUNCTION_ARGS)
+{
+	Oid			relid = PG_GETARG_OID(0);
+	int			prettyFlags;
+
+	prettyFlags = PRETTYFLAG_INDENT;
+	PG_RETURN_TEXT_P(string_to_text(pg_get_partkeydef_worker(relid,
+									prettyFlags)));
+}
+
+/*
+ * Internal workhorse to decompile a partition key definition.
+ */
+static char *
+pg_get_partkeydef_worker(Oid relid, int prettyFlags)
+{
+	Form_pg_partitioned_table	form;
+	HeapTuple	tuple;
+	oidvector  *partclass;
+	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_table) GETSTRUCT(tuple);
+
+	Assert(form->partrelid == relid);
+
+	/* Must get partclass, and partexprs the hard way */
+	datum = SysCacheGetAttr(PARTEDRELID, tuple,
+							Anum_pg_partitioned_table_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_table_partexprbin))
+	{
+		Datum		exprsDatum;
+		bool		isnull;
+		char	   *exprsString;
+
+		exprsDatum = SysCacheGetAttr(PARTEDRELID, tuple,
+									 Anum_pg_partitioned_table_partexprbin, &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);
+
+	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,
+											0, 0);
+
+			appendStringInfoString(&buf, 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/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index ba9c276..c805a84 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -1253,9 +1253,10 @@ expand_table_name_patterns(Archive *fout,
 						  "SELECT c.oid"
 						  "\nFROM pg_catalog.pg_class c"
 		"\n     LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace"
-					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c')\n",
+					 "\nWHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c')\n",
 						  RELKIND_RELATION, RELKIND_SEQUENCE, RELKIND_VIEW,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 		processSQLNamePattern(GetConnection(fout), query, cell->val, true,
 							  false, "n.nspname", "c.relname", NULL,
 							  "pg_catalog.pg_table_is_visible(c.oid)");
@@ -2125,6 +2126,9 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo, bool oids)
 	/* Skip FOREIGN TABLEs (no data to dump) */
 	if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 		return;
+	/* Skip partitioned tables (data in partitions) */
+	if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+		return;
 
 	/* Don't dump data in unlogged tables, if so requested */
 	if (tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED &&
@@ -5204,6 +5208,7 @@ getTables(Archive *fout, int *numTables)
 	int			i_reloftype;
 	int			i_relpages;
 	int			i_changed_acl;
+	int			i_partkeydef;
 
 	/* Make sure we are in proper schema */
 	selectSourceSchema(fout, "pg_catalog");
@@ -5289,7 +5294,8 @@ getTables(Archive *fout, int *numTables)
 						  "OR %s IS NOT NULL "
 						  "OR %s IS NOT NULL"
 						  "))"
-						  "AS changed_acl "
+						  "AS changed_acl, "
+						  "CASE WHEN c.relkind = 'P' THEN pg_catalog.pg_get_partkeydef(c.oid) ELSE NULL END AS partkeydef "
 						  "FROM pg_class c "
 						  "LEFT JOIN pg_depend d ON "
 						  "(c.relkind = '%c' AND "
@@ -5301,7 +5307,7 @@ getTables(Archive *fout, int *numTables)
 						  "(c.oid = pip.objoid "
 						  "AND pip.classoid = 'pg_class'::regclass "
 						  "AND pip.objsubid = 0) "
-				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c') "
+				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c', '%c') "
 						  "ORDER BY c.oid",
 						  acl_subquery->data,
 						  racl_subquery->data,
@@ -5315,7 +5321,8 @@ getTables(Archive *fout, int *numTables)
 						  RELKIND_SEQUENCE,
 						  RELKIND_RELATION, RELKIND_SEQUENCE,
 						  RELKIND_VIEW, RELKIND_COMPOSITE_TYPE,
-						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+						  RELKIND_PARTITIONED_TABLE);
 
 		destroyPQExpBuffer(acl_subquery);
 		destroyPQExpBuffer(racl_subquery);
@@ -5877,6 +5884,7 @@ getTables(Archive *fout, int *numTables)
 	i_toastreloptions = PQfnumber(res, "toast_reloptions");
 	i_reloftype = PQfnumber(res, "reloftype");
 	i_changed_acl = PQfnumber(res, "changed_acl");
+	i_partkeydef = PQfnumber(res, "partkeydef");
 
 	if (dopt->lockWaitTimeout && fout->remoteVersion >= 70300)
 	{
@@ -5947,6 +5955,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 */
 
@@ -5991,7 +6000,9 @@ getTables(Archive *fout, int *numTables)
 		 * We only need to lock the table for certain components; see
 		 * pg_dump.h
 		 */
-		if (tblinfo[i].dobj.dump && tblinfo[i].relkind == RELKIND_RELATION &&
+		if (tblinfo[i].dobj.dump &&
+			(tblinfo[i].relkind == RELKIND_RELATION ||
+			 tblinfo->relkind == RELKIND_PARTITIONED_TABLE) &&
 			(tblinfo[i].dobj.dump & DUMP_COMPONENTS_REQUIRING_LOCK))
 		{
 			resetPQExpBuffer(query);
@@ -6093,7 +6104,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);
 
@@ -15446,6 +15460,9 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 				appendPQExpBufferChar(q, ')');
 			}
 
+			if (tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
+				appendPQExpBuffer(q, "\nPARTITION BY %s", tbinfo->partkeydef);
+
 			if (tbinfo->relkind == RELKIND_FOREIGN_TABLE)
 				appendPQExpBuffer(q, "\nSERVER %s", fmtId(srvname));
 		}
@@ -15506,6 +15523,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 		 */
 		if (dopt->binary_upgrade &&
 			(tbinfo->relkind == RELKIND_RELATION ||
+			 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 			 tbinfo->relkind == RELKIND_FOREIGN_TABLE))
 		{
 			for (j = 0; j < tbinfo->numatts; j++)
@@ -15524,7 +15542,8 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 					appendStringLiteralAH(q, fmtId(tbinfo->dobj.name), fout);
 					appendPQExpBufferStr(q, "::pg_catalog.regclass;\n");
 
-					if (tbinfo->relkind == RELKIND_RELATION)
+					if (tbinfo->relkind == RELKIND_RELATION ||
+						tbinfo->relkind == RELKIND_PARTITIONED_TABLE)
 						appendPQExpBuffer(q, "ALTER TABLE ONLY %s ",
 										  fmtId(tbinfo->dobj.name));
 					else
@@ -15741,6 +15760,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 	 * dump properties we only have ALTER TABLE syntax for
 	 */
 	if ((tbinfo->relkind == RELKIND_RELATION ||
+		 tbinfo->relkind == RELKIND_PARTITIONED_TABLE ||
 		 tbinfo->relkind == RELKIND_MATVIEW) &&
 		tbinfo->relreplident != REPLICA_IDENTITY_DEFAULT)
 	{
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 2bfa2d9..0292859 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -310,6 +310,7 @@ typedef struct _tableInfo
 	bool	   *inhNotNull;		/* true if NOT NULL is inherited */
 	struct _attrDefInfo **attrdefs;		/* DEFAULT expressions */
 	struct _constraintInfo *checkexprs; /* CHECK constraints */
+	char	   *partkeydef;		/* partition key definition */
 
 	/*
 	 * Stuff computed only for dumpable tables.
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 6275a68..10d924a 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -861,6 +861,7 @@ permissionsList(const char *pattern)
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'S' THEN '%s'"
@@ -870,6 +871,7 @@ permissionsList(const char *pattern)
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("sequence"),
@@ -920,7 +922,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
@@ -1567,8 +1569,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)");
 	}
 
@@ -1633,6 +1635,14 @@ describeOneTableDetails(const char *schemaname,
 			printfPQExpBuffer(&title, _("Foreign table \"%s.%s\""),
 							  schemaname, relationname);
 			break;
+		case 'P':
+			if (tableinfo.relpersistence == 'u')
+				printfPQExpBuffer(&title, _("Unlogged table \"%s.%s\""),
+								  schemaname, relationname);
+			else
+				printfPQExpBuffer(&title, _("Table \"%s.%s\""),
+								  schemaname, relationname);
+			break;
 		default:
 			/* untranslated unknown relkind */
 			printfPQExpBuffer(&title, "?%c? \"%s.%s\"",
@@ -1646,8 +1656,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");
@@ -1667,12 +1677,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");
 	}
 
@@ -1772,7 +1782,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);
@@ -1780,14 +1790,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 (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 */
@@ -1926,7 +1955,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;
@@ -2485,7 +2514,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;
@@ -2696,7 +2725,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
@@ -3024,6 +3053,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
 					  " WHEN 'r' THEN '%s'"
+					  " WHEN 'P' THEN '%s'"
 					  " WHEN 'v' THEN '%s'"
 					  " WHEN 'm' THEN '%s'"
 					  " WHEN 'i' THEN '%s'"
@@ -3035,6 +3065,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
 					  gettext_noop("table"),
+					  gettext_noop("table"),
 					  gettext_noop("view"),
 					  gettext_noop("materialized view"),
 					  gettext_noop("index"),
@@ -3079,7 +3110,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 50a45eb..8284a9c 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/pg_proc.h b/src/include/catalog/pg_proc.h
index e2d08ba..b45688b 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -1980,6 +1980,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/utils/builtins.h b/src/include/utils/builtins.h
index 2ae212a..e800647 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -729,6 +729,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/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 0af1422..91ba1be 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -412,3 +412,29 @@ CREATE TABLE no_inh_con_parted (
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
 ERROR:  cannot add NO INHERIT constraint to partitioned table "no_inh_con_parted"
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+Table "public.describe_range_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition Key: RANGE ((a + b))
+
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+Table "public.describe_list_key"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ a      | integer | 
+ b      | integer | 
+Partition Key: LIST (a)
+
+DROP TABLE describe_range_key, describe_list_key;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 418ac55..b75542d 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -406,3 +406,16 @@ CREATE TABLE no_inh_con_parted (
 	a int,
 	CONSTRAINT check_a CHECK (a > 0) NO INHERIT
 ) PARTITION BY RANGE (a);
+
+-- Partition key in describe output
+CREATE TABLE describe_range_key (
+	a int,
+	b int
+) PARTITION BY RANGE ((a+b));
+\d describe_range_key
+CREATE TABLE describe_list_key (
+	a int,
+	b int
+) PARTITION BY LIST (a);
+\d describe_list_key
+DROP TABLE describe_range_key, describe_list_key;
-- 
1.7.1


--------------D008BA677674C4836D7B65AD
Content-Type: text/x-diff;
 name="0003-Catalog-and-DDL-for-partitions-5.patch"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
 filename="0003-Catalog-and-DDL-for-partitions-5.patch"



^ permalink  raw  reply  [nested|flat] 110+ messages in thread

* [PATCH v21 1/3] Avoid orphaned objects dependencies
@ 2026-04-27 14:01  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 110+ messages in thread

From: Bertrand Drouvot @ 2026-04-27 14:01 UTC (permalink / raw)

Concurrent DDL can create orphaned dependencies in pg_depend, objects
referencing other objects that no longer exist. For example:

Scenario 1:

session 1: begin; drop schema schem;
session 2: create a function in the schema schem
session 1: commit;

With the above, the function created in session 2 would be linked to a non
existing schema.

Scenario 2:

session 1: begin; create a function in the schema schem
session 2: drop schema schem;
session 1: commit;

With the above, the function created in session 1 would be linked to a non
existing schema.

Fix by acquiring AccessShareLock on referenced objects when recording
dependencies. This conflicts with AccessExclusiveLock taken by DROP,
preventing the race. After acquiring the lock, verify the object still
exists, if it was dropped concurrently, report an error.

The lock and check is done in both recordMultipleDependencies() and
changeDependencyFor().

The patch adds a few tests for some dependency cases (that would currently produce
orphaned objects):

- schema and function (as the above scenarios)
- alter a dependency (function and schema)
- function and arg type
- function and return type
- function and function
- domain and domain
- table and type
- server and foreign data wrapper

Author: Bertrand Drouvot <[email protected]>
Reviewed-by:
Discussion: https://postgr.es/m/[email protected]
---
 src/backend/catalog/dependency.c              |  72 ++++++++++
 src/backend/catalog/objectaddress.c           |  65 +++++++++
 src/backend/catalog/pg_depend.c               |  16 ++-
 src/backend/utils/errcodes.txt                |   1 +
 src/include/catalog/dependency.h              |   2 +
 src/include/catalog/objectaddress.h           |   1 +
 .../expected/test_dependencies_locks.out      | 129 ++++++++++++++++++
 src/test/isolation/isolation_schedule         |   1 +
 .../specs/test_dependencies_locks.spec        |  96 +++++++++++++
 src/test/regress/expected/alter_table.out     |  11 +-
 10 files changed, 386 insertions(+), 8 deletions(-)
  26.4% src/backend/catalog/
  41.3% src/test/isolation/expected/
  27.7% src/test/isolation/specs/
   4.3% src/

diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index fdb8e67e1f5..7e26691393d 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -87,6 +87,7 @@
 #include "parser/parsetree.h"
 #include "rewrite/rewriteRemove.h"
 #include "storage/lmgr.h"
+#include "storage/lock.h"
 #include "utils/fmgroids.h"
 #include "utils/lsyscache.h"
 #include "utils/syscache.h"
@@ -1606,6 +1607,77 @@ ReleaseDeletionLock(const ObjectAddress *object)
 							 AccessExclusiveLock);
 }
 
+/*
+ * LockNotPinnedObject
+ *
+ * Lock the object that we are about to record a dependency on.
+ * After it's locked, verify that it hasn't been dropped while we
+ * weren't looking.  If the object has been dropped, this function
+ * does not return!
+ *
+ * If the caller already holds a lock that conflicts with DROP
+ * (AccessShareLock or stronger), skip the lock acquisition entirely.
+ */
+void
+LockNotPinnedObject(const ObjectAddress *object)
+{
+	if (isObjectPinned(object))
+		return;
+
+	if (object->classId == RelationRelationId)
+	{
+		/* skip shared relations as they are pinned */
+		if (IsSharedRelation(object->objectId))
+			return;
+
+		/*
+		 * We must be in one of the two following cases that would already
+		 * prevent the relation to be dropped: 1. The relation is already
+		 * locked (could be an existing relation or a relation that we are
+		 * creating). 2. The relation is protected indirectly (i.e an index
+		 * protected by a lock on its table, a table protected by a lock on a
+		 * function that depends of the table...). To avoid any risks, acquire
+		 * a lock if there is none. That may add unnecessary lock for 2. but
+		 * that's worth it.
+		 */
+		if (!CheckRelationOidLockedByMe(object->objectId, AccessShareLock, true))
+			LockRelationOid(object->objectId, AccessShareLock);
+		return;
+	}
+	else
+	{
+		LOCKTAG		tag;
+
+		SET_LOCKTAG_OBJECT(tag,
+						   MyDatabaseId,
+						   object->classId,
+						   object->objectId,
+						   0);
+
+		if (LockHeldByMe(&tag, AccessShareLock, true))
+			return;
+
+		/* assume we should lock the whole object not a sub-object */
+		LockDatabaseObject(object->classId, object->objectId, 0, AccessShareLock);
+	}
+
+	/* check if object still exists */
+	if (!ObjectByIdExist(object, false))
+	{
+		/*
+		 * It might be possible that we are creating it (for example creating
+		 * a composite type while creating a relation), so bypass the syscache
+		 * lookup and use a SnapshotSelf scan instead to cover this scenario.
+		 */
+		if (!ObjectByIdExist(object, true))
+			ereport(ERROR,
+					(errcode(ERRCODE_DEPENDENT_OBJECTS_DOES_NOT_EXIST),
+					 errmsg("dependent object does not exist"),
+					 errdetail("Class OID is %u and object OID is %u",
+							   object->classId, object->objectId)));
+	}
+}
+
 /*
  * recordDependencyOnExpr - find expression dependencies
  *
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index 050b7829eb0..b5786532af8 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -90,6 +90,7 @@
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
 #include "utils/regproc.h"
+#include "utils/snapmgr.h"
 #include "utils/syscache.h"
 
 /*
@@ -2696,6 +2697,70 @@ get_object_namespace(const ObjectAddress *address)
 	return oid;
 }
 
+/*
+ * ObjectByIdExist
+ *
+ * Return whether the given object exists.
+ *
+ * If use_snapshot_self is false, uses the syscache (which sees committed data).
+ * If use_snapshot_self is true, does a direct catalog scan with SnapshotSelf
+ * to also see objects created in the current transaction.
+ */
+bool
+ObjectByIdExist(const ObjectAddress *address, bool use_snapshot_self)
+{
+	HeapTuple	tuple;
+	SysCacheIdentifier cache = SYSCACHEID_INVALID;
+
+	if (!use_snapshot_self)
+	{
+		const ObjectPropertyType *property;
+
+		property = get_object_property_data(address->classId);
+		cache = property->oid_catcache_id;
+	}
+
+	if (cache != SYSCACHEID_INVALID)
+	{
+		tuple = SearchSysCache1(cache, ObjectIdGetDatum(address->objectId));
+
+		if (!HeapTupleIsValid(tuple))
+			return false;
+
+		ReleaseSysCache(tuple);
+		return true;
+	}
+	else
+	{
+		Relation	rel;
+		ScanKeyData skey[1];
+		SysScanDesc scan;
+		Snapshot	snapshot;
+
+		if (use_snapshot_self)
+			snapshot = SnapshotSelf;
+		else
+			snapshot = NULL;
+
+		rel = table_open(address->classId, AccessShareLock);
+
+		ScanKeyInit(&skey[0],
+					get_object_attnum_oid(address->classId),
+					BTEqualStrategyNumber, F_OIDEQ,
+					ObjectIdGetDatum(address->objectId));
+
+		scan = systable_beginscan(rel, get_object_oid_index(address->classId),
+								  true, snapshot, 1, skey);
+
+		tuple = systable_getnext(scan);
+
+		systable_endscan(scan);
+		table_close(rel, AccessShareLock);
+
+		return HeapTupleIsValid(tuple);
+	}
+}
+
 /*
  * Return ObjectType for the given object type as given by
  * getObjectTypeDescription; if no valid ObjectType code exists, but it's a
diff --git a/src/backend/catalog/pg_depend.c b/src/backend/catalog/pg_depend.c
index 07c2d41c189..5618e3d26fa 100644
--- a/src/backend/catalog/pg_depend.c
+++ b/src/backend/catalog/pg_depend.c
@@ -33,8 +33,6 @@
 #include "utils/syscache.h"
 
 
-static bool isObjectPinned(const ObjectAddress *object);
-
 
 /*
  * Record a dependency between 2 objects via their respective ObjectAddress.
@@ -109,6 +107,12 @@ recordMultipleDependencies(const ObjectAddress *depender,
 		if (isObjectPinned(referenced))
 			continue;
 
+		/*
+		 * Acquire a lock and check object still exists while recording the
+		 * dependency.
+		 */
+		LockNotPinnedObject(referenced);
+
 		if (slot_init_count < max_slots)
 		{
 			slot[slot_stored_count] = MakeSingleTupleTableSlot(RelationGetDescr(dependDesc),
@@ -507,6 +511,12 @@ changeDependencyFor(Oid classId, Oid objectId,
 		return 1;
 	}
 
+	/*
+	 * Acquire a lock and check object still exists while changing the
+	 * dependency.
+	 */
+	LockNotPinnedObject(&objAddr);
+
 	depRel = table_open(DependRelationId, RowExclusiveLock);
 
 	/* There should be existing dependency record(s), so search. */
@@ -707,7 +717,7 @@ changeDependenciesOn(Oid refClassId, Oid oldRefObjectId,
  * The passed subId, if any, is ignored; we assume that only whole objects
  * are pinned (and that this implies pinning their components).
  */
-static bool
+bool
 isObjectPinned(const ObjectAddress *object)
 {
 	return IsPinnedObject(object->classId, object->objectId);
diff --git a/src/backend/utils/errcodes.txt b/src/backend/utils/errcodes.txt
index 5b25402ebbe..58b498f68fc 100644
--- a/src/backend/utils/errcodes.txt
+++ b/src/backend/utils/errcodes.txt
@@ -278,6 +278,7 @@ Section: Class 2B - Dependent Privilege Descriptors Still Exist
 
 2B000    E    ERRCODE_DEPENDENT_PRIVILEGE_DESCRIPTORS_STILL_EXIST            dependent_privilege_descriptors_still_exist
 2BP01    E    ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST                          dependent_objects_still_exist
+2BP02    E    ERRCODE_DEPENDENT_OBJECTS_DOES_NOT_EXIST                       dependent_objects_does_not_exist
 
 Section: Class 2D - Invalid Transaction Termination
 
diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h
index 2f3c1eae3c7..fa508ea70c6 100644
--- a/src/include/catalog/dependency.h
+++ b/src/include/catalog/dependency.h
@@ -101,6 +101,8 @@ typedef struct ObjectAddresses ObjectAddresses;
 /* in dependency.c */
 
 extern void AcquireDeletionLock(const ObjectAddress *object, int flags);
+extern void LockNotPinnedObject(const ObjectAddress *object);
+extern bool isObjectPinned(const ObjectAddress *object);
 
 extern void ReleaseDeletionLock(const ObjectAddress *object);
 
diff --git a/src/include/catalog/objectaddress.h b/src/include/catalog/objectaddress.h
index 1f965e1faef..960b7e4bfa6 100644
--- a/src/include/catalog/objectaddress.h
+++ b/src/include/catalog/objectaddress.h
@@ -54,6 +54,7 @@ extern void check_object_ownership(Oid roleid,
 								   Node *object, Relation relation);
 
 extern Oid	get_object_namespace(const ObjectAddress *address);
+extern bool ObjectByIdExist(const ObjectAddress *address, bool use_snapshot_self);
 
 extern bool is_objectclass_supported(Oid class_id);
 extern const char *get_object_class_descr(Oid class_id);
diff --git a/src/test/isolation/expected/test_dependencies_locks.out b/src/test/isolation/expected/test_dependencies_locks.out
new file mode 100644
index 00000000000..820680f5e16
--- /dev/null
+++ b/src/test/isolation/expected/test_dependencies_locks.out
@@ -0,0 +1,129 @@
+Parsed test spec with 2 sessions
+
+starting permutation: s1_begin s1_create_function_in_schema s2_drop_schema s1_commit
+step s1_begin: BEGIN;
+step s1_create_function_in_schema: CREATE FUNCTION testschema.foo() RETURNS int AS 'select 1' LANGUAGE sql;
+step s2_drop_schema: DROP SCHEMA testschema; <waiting ...>
+step s1_commit: COMMIT;
+step s2_drop_schema: <... completed>
+ERROR:  cannot drop schema testschema because other objects depend on it
+
+starting permutation: s2_begin s2_drop_schema s1_create_function_in_schema s2_commit
+step s2_begin: BEGIN;
+step s2_drop_schema: DROP SCHEMA testschema;
+step s1_create_function_in_schema: CREATE FUNCTION testschema.foo() RETURNS int AS 'select 1' LANGUAGE sql; <waiting ...>
+step s2_commit: COMMIT;
+step s1_create_function_in_schema: <... completed>
+ERROR:  dependent object does not exist
+
+starting permutation: s1_begin s1_alter_function_schema s2_drop_alterschema s1_commit
+step s1_begin: BEGIN;
+step s1_alter_function_schema: ALTER FUNCTION public.falter() SET SCHEMA alterschema;
+step s2_drop_alterschema: DROP SCHEMA alterschema; <waiting ...>
+step s1_commit: COMMIT;
+step s2_drop_alterschema: <... completed>
+ERROR:  cannot drop schema alterschema because other objects depend on it
+
+starting permutation: s2_begin s2_drop_alterschema s1_alter_function_schema s2_commit
+step s2_begin: BEGIN;
+step s2_drop_alterschema: DROP SCHEMA alterschema;
+step s1_alter_function_schema: ALTER FUNCTION public.falter() SET SCHEMA alterschema; <waiting ...>
+step s2_commit: COMMIT;
+step s1_alter_function_schema: <... completed>
+ERROR:  dependent object does not exist
+
+starting permutation: s1_begin s1_create_function_with_argtype s2_drop_foo_type s1_commit
+step s1_begin: BEGIN;
+step s1_create_function_with_argtype: CREATE FUNCTION fooargtype(num foo) RETURNS int AS 'select 1' LANGUAGE sql;
+step s2_drop_foo_type: DROP TYPE public.foo; <waiting ...>
+step s1_commit: COMMIT;
+step s2_drop_foo_type: <... completed>
+ERROR:  cannot drop type foo because other objects depend on it
+
+starting permutation: s2_begin s2_drop_foo_type s1_create_function_with_argtype s2_commit
+step s2_begin: BEGIN;
+step s2_drop_foo_type: DROP TYPE public.foo;
+step s1_create_function_with_argtype: CREATE FUNCTION fooargtype(num foo) RETURNS int AS 'select 1' LANGUAGE sql; <waiting ...>
+step s2_commit: COMMIT;
+step s1_create_function_with_argtype: <... completed>
+ERROR:  dependent object does not exist
+
+starting permutation: s1_begin s1_create_function_with_rettype s2_drop_foo_rettype s1_commit
+step s1_begin: BEGIN;
+step s1_create_function_with_rettype: CREATE FUNCTION footrettype() RETURNS id LANGUAGE sql RETURN 1;
+step s2_drop_foo_rettype: DROP DOMAIN id; <waiting ...>
+step s1_commit: COMMIT;
+step s2_drop_foo_rettype: <... completed>
+ERROR:  cannot drop type id because other objects depend on it
+
+starting permutation: s2_begin s2_drop_foo_rettype s1_create_function_with_rettype s2_commit
+step s2_begin: BEGIN;
+step s2_drop_foo_rettype: DROP DOMAIN id;
+step s1_create_function_with_rettype: CREATE FUNCTION footrettype() RETURNS id LANGUAGE sql RETURN 1; <waiting ...>
+step s2_commit: COMMIT;
+step s1_create_function_with_rettype: <... completed>
+ERROR:  dependent object does not exist
+
+starting permutation: s1_begin s1_create_function_with_function s2_drop_function_f s1_commit
+step s1_begin: BEGIN;
+step s1_create_function_with_function: CREATE FUNCTION foofunc() RETURNS int LANGUAGE SQL RETURN f() + 1;
+step s2_drop_function_f: DROP FUNCTION f(); <waiting ...>
+step s1_commit: COMMIT;
+step s2_drop_function_f: <... completed>
+ERROR:  cannot drop function f() because other objects depend on it
+
+starting permutation: s2_begin s2_drop_function_f s1_create_function_with_function s2_commit
+step s2_begin: BEGIN;
+step s2_drop_function_f: DROP FUNCTION f();
+step s1_create_function_with_function: CREATE FUNCTION foofunc() RETURNS int LANGUAGE SQL RETURN f() + 1; <waiting ...>
+step s2_commit: COMMIT;
+step s1_create_function_with_function: <... completed>
+ERROR:  dependent object does not exist
+
+starting permutation: s1_begin s1_create_domain_with_domain s2_drop_domain_id s1_commit
+step s1_begin: BEGIN;
+step s1_create_domain_with_domain: CREATE DOMAIN idid as id;
+step s2_drop_domain_id: DROP DOMAIN id; <waiting ...>
+step s1_commit: COMMIT;
+step s2_drop_domain_id: <... completed>
+ERROR:  cannot drop type id because other objects depend on it
+
+starting permutation: s2_begin s2_drop_domain_id s1_create_domain_with_domain s2_commit
+step s2_begin: BEGIN;
+step s2_drop_domain_id: DROP DOMAIN id;
+step s1_create_domain_with_domain: CREATE DOMAIN idid as id; <waiting ...>
+step s2_commit: COMMIT;
+step s1_create_domain_with_domain: <... completed>
+ERROR:  dependent object does not exist
+
+starting permutation: s1_begin s1_create_table_with_type s2_drop_footab_type s1_commit
+step s1_begin: BEGIN;
+step s1_create_table_with_type: CREATE TABLE tabtype(a footab);
+step s2_drop_footab_type: DROP TYPE public.footab; <waiting ...>
+step s1_commit: COMMIT;
+step s2_drop_footab_type: <... completed>
+ERROR:  cannot drop type footab because other objects depend on it
+
+starting permutation: s2_begin s2_drop_footab_type s1_create_table_with_type s2_commit
+step s2_begin: BEGIN;
+step s2_drop_footab_type: DROP TYPE public.footab;
+step s1_create_table_with_type: CREATE TABLE tabtype(a footab); <waiting ...>
+step s2_commit: COMMIT;
+step s1_create_table_with_type: <... completed>
+ERROR:  dependent object does not exist
+
+starting permutation: s1_begin s1_create_server_with_fdw_wrapper s2_drop_fdw_wrapper s1_commit
+step s1_begin: BEGIN;
+step s1_create_server_with_fdw_wrapper: CREATE SERVER srv_fdw_wrapper FOREIGN DATA WRAPPER fdw_wrapper;
+step s2_drop_fdw_wrapper: DROP FOREIGN DATA WRAPPER fdw_wrapper RESTRICT; <waiting ...>
+step s1_commit: COMMIT;
+step s2_drop_fdw_wrapper: <... completed>
+ERROR:  cannot drop foreign-data wrapper fdw_wrapper because other objects depend on it
+
+starting permutation: s2_begin s2_drop_fdw_wrapper s1_create_server_with_fdw_wrapper s2_commit
+step s2_begin: BEGIN;
+step s2_drop_fdw_wrapper: DROP FOREIGN DATA WRAPPER fdw_wrapper RESTRICT;
+step s1_create_server_with_fdw_wrapper: CREATE SERVER srv_fdw_wrapper FOREIGN DATA WRAPPER fdw_wrapper; <waiting ...>
+step s2_commit: COMMIT;
+step s1_create_server_with_fdw_wrapper: <... completed>
+ERROR:  dependent object does not exist
diff --git a/src/test/isolation/isolation_schedule b/src/test/isolation/isolation_schedule
index 1578ba191c8..83f626d51b5 100644
--- a/src/test/isolation/isolation_schedule
+++ b/src/test/isolation/isolation_schedule
@@ -126,3 +126,4 @@ test: serializable-parallel-3
 test: matview-write-skew
 test: lock-nowait
 test: for-portion-of
+test: test_dependencies_locks
\ No newline at end of file
diff --git a/src/test/isolation/specs/test_dependencies_locks.spec b/src/test/isolation/specs/test_dependencies_locks.spec
new file mode 100644
index 00000000000..ee4130b2dc4
--- /dev/null
+++ b/src/test/isolation/specs/test_dependencies_locks.spec
@@ -0,0 +1,96 @@
+# Test that concurrent DDL properly prevents orphaned dependencies.
+#
+# When session 1 creates an object that depends on a referenced object,
+# and session 2 concurrently drops that referenced object, the lock
+# acquired during dependency recording must prevent the drop or the
+# create must fail with "dependent object does not exist".
+
+setup
+{
+	CREATE SCHEMA testschema;
+	CREATE SCHEMA alterschema;
+	CREATE TYPE public.foo as enum ('one', 'two');
+	CREATE TYPE public.footab as enum ('three', 'four');
+	CREATE DOMAIN id AS int;
+	CREATE FUNCTION f() RETURNS int LANGUAGE SQL RETURN 1;
+	CREATE FUNCTION public.falter() RETURNS int LANGUAGE SQL RETURN 1;
+	CREATE FOREIGN DATA WRAPPER fdw_wrapper;
+}
+
+teardown
+{
+	DROP FUNCTION IF EXISTS testschema.foo();
+	DROP FUNCTION IF EXISTS fooargtype(num foo);
+	DROP FUNCTION IF EXISTS footrettype();
+	DROP FUNCTION IF EXISTS foofunc();
+	DROP FUNCTION IF EXISTS public.falter();
+	DROP FUNCTION IF EXISTS alterschema.falter();
+	DROP DOMAIN IF EXISTS idid;
+	DROP SERVER IF EXISTS srv_fdw_wrapper;
+	DROP TABLE IF EXISTS tabtype;
+	DROP SCHEMA IF EXISTS testschema;
+	DROP SCHEMA IF EXISTS alterschema;
+	DROP TYPE IF EXISTS public.foo;
+	DROP TYPE IF EXISTS public.footab;
+	DROP DOMAIN IF EXISTS id;
+	DROP FUNCTION IF EXISTS f();
+	DROP FOREIGN DATA WRAPPER IF EXISTS fdw_wrapper;
+}
+
+session "s1"
+
+step "s1_begin" { BEGIN; }
+step "s1_create_function_in_schema" { CREATE FUNCTION testschema.foo() RETURNS int AS 'select 1' LANGUAGE sql; }
+step "s1_create_function_with_argtype" { CREATE FUNCTION fooargtype(num foo) RETURNS int AS 'select 1' LANGUAGE sql; }
+step "s1_create_function_with_rettype" { CREATE FUNCTION footrettype() RETURNS id LANGUAGE sql RETURN 1; }
+step "s1_create_function_with_function" { CREATE FUNCTION foofunc() RETURNS int LANGUAGE SQL RETURN f() + 1; }
+step "s1_alter_function_schema" { ALTER FUNCTION public.falter() SET SCHEMA alterschema; }
+step "s1_create_domain_with_domain" { CREATE DOMAIN idid as id; }
+step "s1_create_table_with_type" { CREATE TABLE tabtype(a footab); }
+step "s1_create_server_with_fdw_wrapper" { CREATE SERVER srv_fdw_wrapper FOREIGN DATA WRAPPER fdw_wrapper; }
+step "s1_commit" { COMMIT; }
+
+session "s2"
+
+step "s2_begin" { BEGIN; }
+step "s2_drop_schema" { DROP SCHEMA testschema; }
+step "s2_drop_alterschema" { DROP SCHEMA alterschema; }
+step "s2_drop_foo_type" { DROP TYPE public.foo; }
+step "s2_drop_foo_rettype" { DROP DOMAIN id; }
+step "s2_drop_footab_type" { DROP TYPE public.footab; }
+step "s2_drop_function_f" { DROP FUNCTION f(); }
+step "s2_drop_domain_id" { DROP DOMAIN id; }
+step "s2_drop_fdw_wrapper" { DROP FOREIGN DATA WRAPPER fdw_wrapper RESTRICT; }
+step "s2_commit" { COMMIT; }
+
+# function - schema
+permutation "s1_begin" "s1_create_function_in_schema" "s2_drop_schema" "s1_commit"
+permutation "s2_begin" "s2_drop_schema" "s1_create_function_in_schema" "s2_commit"
+
+# alter function - schema
+permutation "s1_begin" "s1_alter_function_schema" "s2_drop_alterschema" "s1_commit"
+permutation "s2_begin" "s2_drop_alterschema" "s1_alter_function_schema" "s2_commit"
+
+# function - argtype
+permutation "s1_begin" "s1_create_function_with_argtype" "s2_drop_foo_type" "s1_commit"
+permutation "s2_begin" "s2_drop_foo_type" "s1_create_function_with_argtype" "s2_commit"
+
+# function - rettype
+permutation "s1_begin" "s1_create_function_with_rettype" "s2_drop_foo_rettype" "s1_commit"
+permutation "s2_begin" "s2_drop_foo_rettype" "s1_create_function_with_rettype" "s2_commit"
+
+# function - function
+permutation "s1_begin" "s1_create_function_with_function" "s2_drop_function_f" "s1_commit"
+permutation "s2_begin" "s2_drop_function_f" "s1_create_function_with_function" "s2_commit"
+
+# domain - domain
+permutation "s1_begin" "s1_create_domain_with_domain" "s2_drop_domain_id" "s1_commit"
+permutation "s2_begin" "s2_drop_domain_id" "s1_create_domain_with_domain" "s2_commit"
+
+# table - type
+permutation "s1_begin" "s1_create_table_with_type" "s2_drop_footab_type" "s1_commit"
+permutation "s2_begin" "s2_drop_footab_type" "s1_create_table_with_type" "s2_commit"
+
+# server - foreign data wrapper
+permutation "s1_begin" "s1_create_server_with_fdw_wrapper" "s2_drop_fdw_wrapper" "s1_commit"
+permutation "s2_begin" "s2_drop_fdw_wrapper" "s1_create_server_with_fdw_wrapper" "s2_commit"
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index 6dd22be0e8d..b891d68d4a7 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -2949,11 +2949,12 @@ begin;
 alter table alterlock2
 add constraint alterlock2nv foreign key (f1) references alterlock (f1) NOT VALID;
 select * from my_locks order by 1;
-  relname   |     max_lockmode      
-------------+-----------------------
- alterlock  | ShareRowExclusiveLock
- alterlock2 | ShareRowExclusiveLock
-(2 rows)
+    relname     |     max_lockmode      
+----------------+-----------------------
+ alterlock      | ShareRowExclusiveLock
+ alterlock2     | ShareRowExclusiveLock
+ alterlock_pkey | AccessShareLock
+(3 rows)
 
 commit;
 begin;
-- 
2.34.1


--FjYZKkuBdH+F20sI
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
	filename="v21-0002-Lock-referenced-objects-before-permission-checks.patch"



^ permalink  raw  reply  [nested|flat] 110+ messages in thread


end of thread, other threads:[~2026-04-27 14:01 UTC | newest]

Thread overview: 110+ messages (download: mbox mbox.gz follow: Atom feed)
-- links below jump to the message on this page --
2016-07-12 08:20 [PATCH 2/9] psql and pg_dump support for partitioned tables. amit <[email protected]>
2016-07-12 08:20 [PATCH 2/9] psql and pg_dump support for partitioned tables. amit <[email protected]>
2016-07-12 08:20 [PATCH 2/9] psql and pg_dump support for partitioned tables. amit <[email protected]>
2016-07-12 08:20 [PATCH 2/9] psql and pg_dump support for partitioned tables. amit <[email protected]>
2016-07-12 08:20 [PATCH 2/9] psql and pg_dump support for partitioned tables. amit <[email protected]>
2016-07-12 08:20 [PATCH 2/8] psql and pg_dump support for partitioned tables. amit <[email protected]>
2016-07-12 08:20 [PATCH 2/9] psql and pg_dump support for partitioned tables. amit <[email protected]>
2016-07-12 08:20 [PATCH 2/8] psql and pg_dump support for partitioned tables. amit <[email protected]>
2016-07-12 08:20 [PATCH 2/9] psql and pg_dump support for partitioned tables. amit <[email protected]>
2016-07-12 08:20 [PATCH 2/9] psql and pg_dump support for partitioned tables. amit <[email protected]>
2016-07-12 08:20 [PATCH 2/9] psql and pg_dump support for partitioned tables. amit <[email protected]>
2016-07-12 08:20 [PATCH 2/9] psql and pg_dump support for partitioned tables. amit <[email protected]>
2016-07-12 08:20 [PATCH 2/9] psql and pg_dump support for partitioned tables. amit <[email protected]>
2016-07-12 08:20 [PATCH 2/9] psql and pg_dump support for partitioned tables. amit <[email protected]>
2016-07-12 08:20 [PATCH 2/8] psql and pg_dump support for partitioned tables. amit <[email protected]>
2016-07-12 08:20 [PATCH 2/9] psql and pg_dump support for partitioned tables. amit <[email protected]>
2016-07-12 08:20 [PATCH 2/9] psql and pg_dump support for partitioned tables. amit <[email protected]>
2016-07-12 08:20 [PATCH 2/8] psql and pg_dump support for partitioned tables. amit <[email protected]>
2016-07-12 08:20 [PATCH 2/9] psql and pg_dump support for partitioned tables. amit <[email protected]>
2016-07-12 08:20 [PATCH 2/9] psql and pg_dump support for partitioned tables. amit <[email protected]>
2016-07-12 08:20 [PATCH 2/9] psql and pg_dump support for partitioned tables. amit <[email protected]>
2016-07-12 08:20 [PATCH 2/9] psql and pg_dump support for partitioned tables. amit <[email protected]>
2016-07-12 08:20 [PATCH 2/9] psql and pg_dump support for partitioned tables. amit <[email protected]>
2016-07-12 08:20 [PATCH 2/9] psql and pg_dump support for partitioned tables. amit <[email protected]>
2016-07-12 08:20 [PATCH 2/8] psql and pg_dump support for partitioned tables. amit <[email protected]>
2016-07-12 08:20 [PATCH 2/9] psql and pg_dump support for partitioned tables. amit <[email protected]>
2016-07-12 08:20 [PATCH 2/9] psql and pg_dump support for partitioned tables. amit <[email protected]>
2016-07-12 08:20 [PATCH 2/9] psql and pg_dump support for partitioned tables. amit <[email protected]>
2016-07-12 08:20 [PATCH 2/9] psql and pg_dump support for partitioned tables. amit <[email protected]>
2016-07-12 08:20 [PATCH 2/8] psql and pg_dump support for partitioned tables. amit <[email protected]>
2016-07-12 08:20 [PATCH 2/8] psql and pg_dump support for partitioned tables. amit <[email protected]>
2016-07-12 08:20 [PATCH 2/9] psql and pg_dump support for partitioned tables. amit <[email protected]>
2016-07-12 08:20 [PATCH 2/9] psql and pg_dump support for partitioned tables. amit <[email protected]>
2016-07-12 08:20 [PATCH 2/9] psql and pg_dump support for partitioned tables. amit <[email protected]>
2016-07-12 08:20 [PATCH 2/9] psql and pg_dump support for partitioned tables. amit <[email protected]>
2016-07-12 08:20 [PATCH 2/9] psql and pg_dump support for partitioned tables. amit <[email protected]>
2016-07-12 08:20 [PATCH 2/9] psql and pg_dump support for partitioned tables. amit <[email protected]>
2016-07-12 08:20 [PATCH 2/9] psql and pg_dump support for partitioned tables. amit <[email protected]>
2016-07-12 08:20 [PATCH 2/9] psql and pg_dump support for partitioned tables. amit <[email protected]>
2016-07-12 08:20 [PATCH 2/9] psql and pg_dump support for partitioned tables. amit <[email protected]>
2016-07-12 08:20 [PATCH 2/8] psql and pg_dump support for partitioned tables. amit <[email protected]>
2016-07-12 08:20 [PATCH 2/9] psql and pg_dump support for partitioned tables. amit <[email protected]>
2016-07-12 08:20 [PATCH 2/8] psql and pg_dump support for partitioned tables. amit <[email protected]>
2016-07-12 08:20 [PATCH 2/9] psql and pg_dump support for partitioned tables. amit <[email protected]>
2016-07-12 08:20 [PATCH 2/9] psql and pg_dump support for partitioned tables. amit <[email protected]>
2016-07-12 08:20 [PATCH 2/9] psql and pg_dump support for partitioned tables. amit <[email protected]>
2016-07-12 08:20 [PATCH 2/8] psql and pg_dump support for partitioned tables. amit <[email protected]>
2016-07-12 08:20 [PATCH 2/9] psql and pg_dump support for partitioned tables. amit <[email protected]>
2016-07-12 08:20 [PATCH 2/9] psql and pg_dump support for partitioned tables. amit <[email protected]>
2016-07-12 08:20 [PATCH 2/9] psql and pg_dump support for partitioned tables. amit <[email protected]>
2016-07-12 08:20 [PATCH 2/9] psql and pg_dump support for partitioned tables. amit <[email protected]>
2016-07-12 08:20 [PATCH 2/9] psql and pg_dump support for partitioned tables. amit <[email protected]>
2016-07-12 08:20 [PATCH 2/8] psql and pg_dump support for partitioned tables. amit <[email protected]>
2016-07-12 08:20 [PATCH 2/9] psql and pg_dump support for partitioned tables. amit <[email protected]>
2016-07-12 08:20 [PATCH 2/8] psql and pg_dump support for partitioned tables. amit <[email protected]>
2016-07-12 08:20 [PATCH 2/9] psql and pg_dump support for partitioned tables. amit <[email protected]>
2016-07-12 08:20 [PATCH 2/8] psql and pg_dump support for partitioned tables. amit <[email protected]>
2016-07-12 08:20 [PATCH 2/9] psql and pg_dump support for partitioned tables. amit <[email protected]>
2016-07-12 08:20 [PATCH 2/8] psql and pg_dump support for partitioned tables. amit <[email protected]>
2016-07-12 08:20 [PATCH 2/9] psql and pg_dump support for partitioned tables. amit <[email protected]>
2016-07-12 08:20 [PATCH 2/9] psql and pg_dump support for partitioned tables. amit <[email protected]>
2016-07-12 08:20 [PATCH 2/9] psql and pg_dump support for partitioned tables. amit <[email protected]>
2016-07-12 08:20 [PATCH 2/9] psql and pg_dump support for partitioned tables. amit <[email protected]>
2016-07-12 08:20 [PATCH 2/9] psql and pg_dump support for partitioned tables. amit <[email protected]>
2016-07-12 08:20 [PATCH 2/9] psql and pg_dump support for partitioned tables. amit <[email protected]>
2016-07-12 08:20 [PATCH 2/9] psql and pg_dump support for partitioned tables. amit <[email protected]>
2016-07-12 08:20 [PATCH 2/9] psql and pg_dump support for partitioned tables. amit <[email protected]>
2016-07-12 08:20 [PATCH 2/9] psql and pg_dump support for partitioned tables. amit <[email protected]>
2016-07-12 08:20 [PATCH 2/8] psql and pg_dump support for partitioned tables. amit <[email protected]>
2016-07-12 08:20 [PATCH 2/9] psql and pg_dump support for partitioned tables. amit <[email protected]>
2016-07-12 08:20 [PATCH 2/9] psql and pg_dump support for partitioned tables. amit <[email protected]>
2016-07-12 08:20 [PATCH 2/9] psql and pg_dump support for partitioned tables. amit <[email protected]>
2016-07-12 08:20 [PATCH 2/9] psql and pg_dump support for partitioned tables. amit <[email protected]>
2016-07-12 08:20 [PATCH 2/9] psql and pg_dump support for partitioned tables. amit <[email protected]>
2016-07-12 08:20 [PATCH 2/9] psql and pg_dump support for partitioned tables. amit <[email protected]>
2016-07-12 08:20 [PATCH 2/8] psql and pg_dump support for partitioned tables. amit <[email protected]>
2016-07-12 08:20 [PATCH 2/9] psql and pg_dump support for partitioned tables. amit <[email protected]>
2016-07-12 08:20 [PATCH 2/9] psql and pg_dump support for partitioned tables. amit <[email protected]>
2016-07-12 08:20 [PATCH 2/9] psql and pg_dump support for partitioned tables. amit <[email protected]>
2016-07-12 08:20 [PATCH 2/9] psql and pg_dump support for partitioned tables. amit <[email protected]>
2016-07-12 08:20 [PATCH 2/9] psql and pg_dump support for partitioned tables. amit <[email protected]>
2016-07-12 08:20 [PATCH 2/9] psql and pg_dump support for partitioned tables. amit <[email protected]>
2016-07-12 08:20 [PATCH 2/9] psql and pg_dump support for partitioned tables. amit <[email protected]>
2016-07-12 08:20 [PATCH 2/9] psql and pg_dump support for partitioned tables. amit <[email protected]>
2016-07-12 08:20 [PATCH 2/9] psql and pg_dump support for partitioned tables. amit <[email protected]>
2016-07-12 08:20 [PATCH 2/8] psql and pg_dump support for partitioned tables. amit <[email protected]>
2016-07-12 08:20 [PATCH 2/9] psql and pg_dump support for partitioned tables. amit <[email protected]>
2016-07-12 08:20 [PATCH 2/9] psql and pg_dump support for partitioned tables. amit <[email protected]>
2016-07-12 08:20 [PATCH 2/9] psql and pg_dump support for partitioned tables. amit <[email protected]>
2016-07-12 08:20 [PATCH 2/8] psql and pg_dump support for partitioned tables. amit <[email protected]>
2016-07-12 08:20 [PATCH 2/9] psql and pg_dump support for partitioned tables. amit <[email protected]>
2016-07-12 08:20 [PATCH 2/9] psql and pg_dump support for partitioned tables. amit <[email protected]>
2016-07-12 08:20 [PATCH 2/9] psql and pg_dump support for partitioned tables. amit <[email protected]>
2016-07-12 08:20 [PATCH 2/9] psql and pg_dump support for partitioned tables. amit <[email protected]>
2016-07-12 08:20 [PATCH 2/9] psql and pg_dump support for partitioned tables. amit <[email protected]>
2016-07-12 08:20 [PATCH 2/9] psql and pg_dump support for partitioned tables. amit <[email protected]>
2016-07-12 08:20 [PATCH 2/9] psql and pg_dump support for partitioned tables. amit <[email protected]>
2016-07-12 08:20 [PATCH 2/9] psql and pg_dump support for partitioned tables. amit <[email protected]>
2016-07-12 08:20 [PATCH 2/9] psql and pg_dump support for partitioned tables. amit <[email protected]>
2016-07-12 08:20 [PATCH 2/9] psql and pg_dump support for partitioned tables. amit <[email protected]>
2016-07-12 08:20 [PATCH 2/9] psql and pg_dump support for partitioned tables. amit <[email protected]>
2016-07-12 08:20 [PATCH 2/9] psql and pg_dump support for partitioned tables. amit <[email protected]>
2016-07-12 08:20 [PATCH 2/9] psql and pg_dump support for partitioned tables. amit <[email protected]>
2016-07-12 08:20 [PATCH 2/9] psql and pg_dump support for partitioned tables. amit <[email protected]>
2016-07-12 08:20 [PATCH 2/9] psql and pg_dump support for partitioned tables. amit <[email protected]>
2016-07-12 08:20 [PATCH 2/9] psql and pg_dump support for partitioned tables. amit <[email protected]>
2016-07-12 08:20 [PATCH 2/9] psql and pg_dump support for partitioned tables. amit <[email protected]>
2016-07-12 08:20 [PATCH 2/9] psql and pg_dump support for partitioned tables. amit <[email protected]>
2016-07-12 08:20 [PATCH 2/9] psql and pg_dump support for partitioned tables. amit <[email protected]>
2026-04-27 14:01 [PATCH v21 1/3] Avoid orphaned objects dependencies Bertrand Drouvot <[email protected]>

This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox