From bb8455bcf9dbaf5ef9cdf2898efcfde6eb6edecf Mon Sep 17 00:00:00 2001
From: Craig Ringer <craig@2ndquadrant.com>
Date: Mon, 20 Jan 2014 13:45:56 +0800
Subject: [PATCH 05/10] RLS: pg_dump support for dumping rowsecurity catalogs

---
 src/bin/pg_dump/common.c             |   4 +
 src/bin/pg_dump/pg_backup_archiver.c |   1 +
 src/bin/pg_dump/pg_dump.c            | 151 ++++++++++++++++++++++++++++++++++-
 src/bin/pg_dump/pg_dump.h            |  13 ++-
 src/bin/pg_dump/pg_dump_sort.c       |   5 ++
 5 files changed, 169 insertions(+), 5 deletions(-)

diff --git a/src/bin/pg_dump/common.c b/src/bin/pg_dump/common.c
index 4d35ae5..fd9e6cd 100644
--- a/src/bin/pg_dump/common.c
+++ b/src/bin/pg_dump/common.c
@@ -244,6 +244,10 @@ getSchemaData(Archive *fout, int *numTablesPtr)
 		write_msg(NULL, "reading rewrite rules\n");
 	getRules(fout, &numRules);
 
+	if (g_verbose)
+		write_msg(NULL, "reading row-security policies\n");
+	getRowSecurity(fout, tblinfo, numTables);
+
 	*numTablesPtr = numTables;
 	return tblinfo;
 }
diff --git a/src/bin/pg_dump/pg_backup_archiver.c b/src/bin/pg_dump/pg_backup_archiver.c
index 7fc0288..6502d93 100644
--- a/src/bin/pg_dump/pg_backup_archiver.c
+++ b/src/bin/pg_dump/pg_backup_archiver.c
@@ -3137,6 +3137,7 @@ _printTocEntry(ArchiveHandle *AH, TocEntry *te, RestoreOptions *ropt, bool isDat
 				 strcmp(te->desc, "INDEX") == 0 ||
 				 strcmp(te->desc, "RULE") == 0 ||
 				 strcmp(te->desc, "TRIGGER") == 0 ||
+				 strcmp(te->desc, "ROW SECURITY") == 0 ||
 				 strcmp(te->desc, "USER MAPPING") == 0)
 		{
 			/* these object types don't have separate owners */
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 3862f05..6939852 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -250,6 +250,7 @@ static char *myFormatType(const char *typname, int32 typmod);
 static void getBlobs(Archive *fout);
 static void dumpBlob(Archive *fout, BlobInfo *binfo);
 static int	dumpBlobs(Archive *fout, void *arg);
+static void dumpRowSecurity(Archive *fout, RowSecurityInfo *rsinfo);
 static void dumpDatabase(Archive *AH);
 static void dumpEncoding(Archive *AH);
 static void dumpStdStrings(Archive *AH);
@@ -2714,6 +2715,134 @@ dumpBlobs(Archive *fout, void *arg)
 	return 1;
 }
 
+/*
+ * getRowSecurity
+ *    get information about every row-security policy on a dumpable table
+ */
+void
+getRowSecurity(Archive *fout, TableInfo tblinfo[], int numTables)
+{
+	PQExpBuffer query = createPQExpBuffer();
+	PGresult   *res;
+	RowSecurityInfo	*rsinfo;
+	int			i_oid;
+	int			i_tableoid;
+	int			i_rseccmd;
+	int			i_rsecqual;
+	int			i, j, ntups;
+
+	/* row-security is not supported prior to v9.4 */
+	if (fout->remoteVersion < 90400)
+		return;
+
+	for (i=0; i < numTables; i++)
+	{
+		TableInfo  *tbinfo = &tblinfo[i];
+
+		if (!tbinfo->hasrowsec || !tbinfo->dobj.dump)
+			continue;
+
+		if (g_verbose)
+			write_msg(NULL, "reading row-security policy for table \"%s\"\n",
+					  tbinfo->dobj.name);
+
+		/*
+		 * select table schema to ensure regproc name is qualified if needed
+		 */
+		selectSourceSchema(fout, tbinfo->dobj.namespace->dobj.name);
+
+		resetPQExpBuffer(query);
+
+		appendPQExpBuffer(query,
+						  "SELECT oid, tableoid, s.rseccmd, "
+						  "pg_get_expr(s.rsecqual, s.rsecrelid) AS rsecqual "
+						  "FROM pg_catalog.pg_rowsecurity s "
+						  "WHERE rsecrelid = '%u'",
+						  tbinfo->dobj.catId.oid);
+		res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
+
+		ntups = PQntuples(res);
+
+		i_oid = PQfnumber(res, "oid");
+		i_tableoid = PQfnumber(res, "tableoid");
+		i_rseccmd = PQfnumber(res, "rseccmd");
+		i_rsecqual = PQfnumber(res, "rsecqual");
+
+		rsinfo = pg_malloc(ntups * sizeof(RowSecurityInfo));
+		for (j=0; j < ntups; j++)
+		{
+			char	namebuf[NAMEDATALEN + 1];
+
+			rsinfo[j].dobj.objType = DO_ROW_SECURITY;
+			rsinfo[j].dobj.catId.tableoid =
+				atooid(PQgetvalue(res, j, i_tableoid));
+			rsinfo[j].dobj.catId.oid = atooid(PQgetvalue(res, j, i_oid));
+			AssignDumpId(&rsinfo[j].dobj);
+			snprintf(namebuf, sizeof(namebuf), "row-security of %s",
+					 tbinfo->rolname);
+			rsinfo[j].dobj.name = namebuf;
+			rsinfo[j].dobj.namespace = tbinfo->dobj.namespace;
+			rsinfo[j].rstable = tbinfo;
+			rsinfo[j].rseccmd = pg_strdup(PQgetvalue(res, j, i_rseccmd));
+			rsinfo[j].rsecqual = pg_strdup(PQgetvalue(res, j, i_rsecqual));
+		}
+		PQclear(res);
+	}
+	destroyPQExpBuffer(query);
+}
+
+/*
+ * dumpRowSecurity
+ *    dump the definition of the given row-security policy
+ */
+static void
+dumpRowSecurity(Archive *fout, RowSecurityInfo *rsinfo)
+{
+	TableInfo  *tbinfo = rsinfo->rstable;
+	PQExpBuffer	query;
+	PQExpBuffer	delqry;
+	const char *cmd;
+
+	if (dataOnly || !tbinfo->hasrowsec)
+		return;
+
+	query = createPQExpBuffer();
+	delqry = createPQExpBuffer();
+	appendPQExpBuffer(query, "ALTER TABLE %s SET ROW SECURITY ",
+					  fmtId(tbinfo->dobj.name));
+	appendPQExpBuffer(delqry, "ALTER TABLE %s RESET ROW SECURITY ",
+					  fmtId(tbinfo->dobj.name));
+	if (strcmp(rsinfo->rseccmd, "a") == 0)
+		cmd = "ALL";
+	else if (strcmp(rsinfo->rseccmd, "s") == 0)
+		cmd = "SELECT";
+	else if (strcmp(rsinfo->rseccmd, "i") == 0)
+		cmd = "INSERT";
+	else if (strcmp(rsinfo->rseccmd, "u") == 0)
+		cmd = "UPDATE";
+	else if (strcmp(rsinfo->rseccmd, "d") == 0)
+		cmd = "DELETE";
+	else
+	{
+		write_msg(NULL, "unexpected command type: '%s'\n", rsinfo->rseccmd);
+		exit_nicely(1);
+	}
+	appendPQExpBuffer(query, "FOR %s TO %s;\n", cmd, rsinfo->rsecqual);
+	appendPQExpBuffer(delqry, "FOR %s;\n", cmd);
+
+	ArchiveEntry(fout, rsinfo->dobj.catId, rsinfo->dobj.dumpId,
+				 rsinfo->dobj.name,
+				 rsinfo->dobj.namespace->dobj.name,
+				 NULL,
+				 tbinfo->rolname, false,
+				 "ROW SECURITY", SECTION_POST_DATA,
+				 query->data, delqry->data, NULL,
+				 NULL, 0,
+				 NULL, NULL);
+
+	destroyPQExpBuffer(query);
+}
+
 static void
 binary_upgrade_set_type_oids_by_type_oid(Archive *fout,
 										 PQExpBuffer upgrade_buffer,
@@ -4242,6 +4371,7 @@ getTables(Archive *fout, int *numTables)
 	int			i_relhastriggers;
 	int			i_relhasindex;
 	int			i_relhasrules;
+	int			i_relhasrowsec;
 	int			i_relhasoids;
 	int			i_relfrozenxid;
 	int			i_toastoid;
@@ -4293,10 +4423,7 @@ getTables(Archive *fout, int *numTables)
 						  "(%s c.relowner) AS rolname, "
 						  "c.relchecks, c.relhastriggers, "
 						  "c.relhasindex, c.relhasrules, c.relhasoids, "
-						  "c.relfrozenxid, tc.oid AS toid, "
-						  "tc.relfrozenxid AS tfrozenxid, "
-						  "c.relpersistence, c.relispopulated, "
-						  "c.relreplident, c.relpages, "
+						  "c.relhasrowsecurity, "
 						  "CASE WHEN c.reloftype <> 0 THEN c.reloftype::pg_catalog.regtype ELSE NULL END AS reloftype, "
 						  "d.refobjid AS owning_tab, "
 						  "d.refobjsubid AS owning_col, "
@@ -4332,6 +4459,7 @@ getTables(Archive *fout, int *numTables)
 						  "(%s c.relowner) AS rolname, "
 						  "c.relchecks, c.relhastriggers, "
 						  "c.relhasindex, c.relhasrules, c.relhasoids, "
+						  "'f'::bool AS relhasrowsecurity, "
 						  "c.relfrozenxid, tc.oid AS toid, "
 						  "tc.relfrozenxid AS tfrozenxid, "
 						  "c.relpersistence, c.relispopulated, "
@@ -4371,6 +4499,7 @@ getTables(Archive *fout, int *numTables)
 						  "(%s c.relowner) AS rolname, "
 						  "c.relchecks, c.relhastriggers, "
 						  "c.relhasindex, c.relhasrules, c.relhasoids, "
+						  "'f'::bool AS relhasrowsecurity, "
 						  "c.relfrozenxid, tc.oid AS toid, "
 						  "tc.relfrozenxid AS tfrozenxid, "
 						  "c.relpersistence, 't' as relispopulated, "
@@ -4408,6 +4537,7 @@ getTables(Archive *fout, int *numTables)
 						  "(%s c.relowner) AS rolname, "
 						  "c.relchecks, c.relhastriggers, "
 						  "c.relhasindex, c.relhasrules, c.relhasoids, "
+						  "'f'::bool AS relhasrowsecurity, "
 						  "c.relfrozenxid, tc.oid AS toid, "
 						  "tc.relfrozenxid AS tfrozenxid, "
 						  "'p' AS relpersistence, 't' as relispopulated, "
@@ -4444,6 +4574,7 @@ getTables(Archive *fout, int *numTables)
 						  "(%s c.relowner) AS rolname, "
 						  "c.relchecks, c.relhastriggers, "
 						  "c.relhasindex, c.relhasrules, c.relhasoids, "
+						  "'f'::bool AS relhasrowsecurity, "
 						  "c.relfrozenxid, tc.oid AS toid, "
 						  "tc.relfrozenxid AS tfrozenxid, "
 						  "'p' AS relpersistence, 't' as relispopulated, "
@@ -4480,6 +4611,7 @@ getTables(Archive *fout, int *numTables)
 						  "(%s c.relowner) AS rolname, "
 					  "c.relchecks, (c.reltriggers <> 0) AS relhastriggers, "
 						  "c.relhasindex, c.relhasrules, c.relhasoids, "
+						  "'f'::bool AS relhasrowsecurity, "
 						  "c.relfrozenxid, tc.oid AS toid, "
 						  "tc.relfrozenxid AS tfrozenxid, "
 						  "'p' AS relpersistence, 't' as relispopulated, "
@@ -4516,6 +4648,7 @@ getTables(Archive *fout, int *numTables)
 						  "(%s relowner) AS rolname, "
 						  "relchecks, (reltriggers <> 0) AS relhastriggers, "
 						  "relhasindex, relhasrules, relhasoids, "
+						  "'f'::bool AS relhasrowsecurity, "
 						  "0 AS relfrozenxid, "
 						  "0 AS toid, "
 						  "0 AS tfrozenxid, "
@@ -4552,6 +4685,7 @@ getTables(Archive *fout, int *numTables)
 						  "(%s relowner) AS rolname, "
 						  "relchecks, (reltriggers <> 0) AS relhastriggers, "
 						  "relhasindex, relhasrules, relhasoids, "
+						  "'f'::bool AS relhasrowsecurity, "
 						  "0 AS relfrozenxid, "
 						  "0 AS toid, "
 						  "0 AS tfrozenxid, "
@@ -4584,6 +4718,7 @@ getTables(Archive *fout, int *numTables)
 						  "(%s relowner) AS rolname, "
 						  "relchecks, (reltriggers <> 0) AS relhastriggers, "
 						  "relhasindex, relhasrules, relhasoids, "
+						  "'f'::bool AS relhasrowsecurity, "
 						  "0 AS relfrozenxid, "
 						  "0 AS toid, "
 						  "0 AS tfrozenxid, "
@@ -4611,6 +4746,7 @@ getTables(Archive *fout, int *numTables)
 						  "relchecks, (reltriggers <> 0) AS relhastriggers, "
 						  "relhasindex, relhasrules, "
 						  "'t'::bool AS relhasoids, "
+						  "'f'::bool AS relhasrowsecurity, "
 						  "0 AS relfrozenxid, "
 						  "0 AS toid, "
 						  "0 AS tfrozenxid, "
@@ -4648,6 +4784,7 @@ getTables(Archive *fout, int *numTables)
 						  "relchecks, (reltriggers <> 0) AS relhastriggers, "
 						  "relhasindex, relhasrules, "
 						  "'t'::bool AS relhasoids, "
+						  "'f'::bool AS relhasrowsecurity, "
 						  "0 as relfrozenxid, "
 						  "0 AS toid, "
 						  "0 AS tfrozenxid, "
@@ -4695,6 +4832,7 @@ getTables(Archive *fout, int *numTables)
 	i_relhastriggers = PQfnumber(res, "relhastriggers");
 	i_relhasindex = PQfnumber(res, "relhasindex");
 	i_relhasrules = PQfnumber(res, "relhasrules");
+	i_relhasrowsec = PQfnumber(res, "relhasrowsecurity");
 	i_relhasoids = PQfnumber(res, "relhasoids");
 	i_relfrozenxid = PQfnumber(res, "relfrozenxid");
 	i_toastoid = PQfnumber(res, "toid");
@@ -4744,6 +4882,7 @@ getTables(Archive *fout, int *numTables)
 		tblinfo[i].hasindex = (strcmp(PQgetvalue(res, i, i_relhasindex), "t") == 0);
 		tblinfo[i].hasrules = (strcmp(PQgetvalue(res, i, i_relhasrules), "t") == 0);
 		tblinfo[i].hastriggers = (strcmp(PQgetvalue(res, i, i_relhastriggers), "t") == 0);
+		tblinfo[i].hasrowsec = (strcmp(PQgetvalue(res, i, i_relhasrowsec), "t") == 0);
 		tblinfo[i].hasoids = (strcmp(PQgetvalue(res, i, i_relhasoids), "t") == 0);
 		tblinfo[i].relispopulated = (strcmp(PQgetvalue(res, i, i_relispopulated), "t") == 0);
 		tblinfo[i].relreplident = *(PQgetvalue(res, i, i_relreplident));
@@ -7861,6 +8000,9 @@ dumpDumpableObject(Archive *fout, DumpableObject *dobj)
 						 NULL, 0,
 						 dumpBlobs, NULL);
 			break;
+		case DO_ROW_SECURITY:
+			dumpRowSecurity(fout, (RowSecurityInfo *) dobj);
+			break;
 		case DO_PRE_DATA_BOUNDARY:
 		case DO_POST_DATA_BOUNDARY:
 			/* never dumped, nothing to do */
@@ -15147,6 +15289,7 @@ addBoundaryDependencies(DumpableObject **dobjs, int numObjs,
 			case DO_TRIGGER:
 			case DO_EVENT_TRIGGER:
 			case DO_DEFAULT_ACL:
+			case DO_ROW_SECURITY:
 				/* Post-data objects: must come after the post-data boundary */
 				addObjectDependency(dobj, postDataBound->dumpId);
 				break;
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index c8dac35..de10c95 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -111,7 +111,8 @@ typedef enum
 	DO_PRE_DATA_BOUNDARY,
 	DO_POST_DATA_BOUNDARY,
 	DO_EVENT_TRIGGER,
-	DO_REFRESH_MATVIEW
+	DO_REFRESH_MATVIEW,
+	DO_ROW_SECURITY,
 } DumpableObjectType;
 
 typedef struct _dumpableObject
@@ -245,6 +246,7 @@ typedef struct _tableInfo
 	bool		hasindex;		/* does it have any indexes? */
 	bool		hasrules;		/* does it have any rules? */
 	bool		hastriggers;	/* does it have any triggers? */
+	bool		hasrowsec;		/* does it have any row-security policy? */
 	bool		hasoids;		/* does it have OIDs? */
 	uint32		frozenxid;		/* for restore frozen xid */
 	Oid			toast_oid;		/* for restore toast frozen xid */
@@ -484,6 +486,14 @@ typedef struct _blobInfo
 	char	   *blobacl;
 } BlobInfo;
 
+typedef struct _rowSecurityInfo
+{
+	DumpableObject dobj;
+	TableInfo  *rstable;
+	char	   *rseccmd;
+	char	   *rsecqual;
+} RowSecurityInfo;
+
 /* global decls */
 extern bool force_quotes;		/* double-quotes for identifiers flag */
 extern bool g_verbose;			/* verbose flag */
@@ -575,5 +585,6 @@ extern DefaultACLInfo *getDefaultACLs(Archive *fout, int *numDefaultACLs);
 extern void getExtensionMembership(Archive *fout, ExtensionInfo extinfo[],
 					   int numExtensions);
 extern EventTriggerInfo *getEventTriggers(Archive *fout, int *numEventTriggers);
+extern void getRowSecurity(Archive *fout, TableInfo tblinfo[], int numTables);
 
 #endif   /* PG_DUMP_H */
diff --git a/src/bin/pg_dump/pg_dump_sort.c b/src/bin/pg_dump/pg_dump_sort.c
index bda3535..718007d 100644
--- a/src/bin/pg_dump/pg_dump_sort.c
+++ b/src/bin/pg_dump/pg_dump_sort.c
@@ -1342,6 +1342,11 @@ describeDumpableObject(DumpableObject *obj, char *buf, int bufsize)
 					 "BLOB DATA  (ID %d)",
 					 obj->dumpId);
 			return;
+		case DO_ROW_SECURITY:
+			snprintf(buf, bufsize,
+					 "ROW-SECURITY POLICY (ID %d OID %u)",
+					 obj->dumpId, obj->catId.oid);
+			return;
 		case DO_PRE_DATA_BOUNDARY:
 			snprintf(buf, bufsize,
 					 "PRE-DATA BOUNDARY  (ID %d)",
-- 
1.8.3.1