From 8a2a621c72464f06f0633b019f62d5dd13f8a10b Mon Sep 17 00:00:00 2001
From: Craig Ringer <craig@2ndquadrant.com>
Date: Mon, 20 Jan 2014 13:37:47 +0800
Subject: [PATCH 04/10] Add ALTER TABLE commands for row security

---
 doc/src/sgml/ref/alter_table.sgml    | 38 +++++++++++++++++++++++++++
 src/backend/catalog/pg_rowsecurity.c | 50 ++++++++++++++++++++++++++++++++++++
 src/backend/commands/event_trigger.c |  1 +
 src/backend/commands/tablecmds.c     | 27 +++++++++++++++++++
 src/backend/parser/gram.y            | 25 ++++++++++++++++++
 src/backend/parser/parse_agg.c       |  6 +++++
 src/backend/parser/parse_expr.c      |  3 +++
 src/include/catalog/pg_rowsecurity.h |  2 ++
 src/include/nodes/parsenodes.h       |  2 ++
 9 files changed, 154 insertions(+)

diff --git a/doc/src/sgml/ref/alter_table.sgml b/doc/src/sgml/ref/alter_table.sgml
index 89649a2..79f37d8 100644
--- a/doc/src/sgml/ref/alter_table.sgml
+++ b/doc/src/sgml/ref/alter_table.sgml
@@ -69,6 +69,8 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
     NOT OF
     OWNER TO <replaceable class="PARAMETER">new_owner</replaceable>
     SET TABLESPACE <replaceable class="PARAMETER">new_tablespace</replaceable>
+    SET ROW SECURITY FOR <replaceable class="PARAMETER">rowsec_command</replaceable> TO (<replaceable class="PARAMETER">condition</replaceable>)
+    RESET ROW SECURITY FOR <replaceable class="PARAMETER">rowsec_command</replaceable>
     REPLICA IDENTITY {DEFAULT | USING INDEX <replaceable class="PARAMETER">index_name</replaceable> | FULL | NOTHING}
 
 <phrase>and <replaceable class="PARAMETER">table_constraint_using_index</replaceable> is:</phrase>
@@ -76,6 +78,8 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
     [ CONSTRAINT <replaceable class="PARAMETER">constraint_name</replaceable> ]
     { UNIQUE | PRIMARY KEY } USING INDEX <replaceable class="PARAMETER">index_name</replaceable>
     [ DEFERRABLE | NOT DEFERRABLE ] [ INITIALLY DEFERRED | INITIALLY IMMEDIATE ]
+<phrase>and <replaceable class="PARAMETER">rowsec_command</replaceable> is:</phrase>
+    { ALL | SELECT | INSERT | UPDATE | DELETE }
 </synopsis>
  </refsynopsisdiv>
 
@@ -581,6 +585,26 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
    </varlistentry>
 
    <varlistentry>
+    <term><literal>SET ROW SECURITY FOR <replaceable class="PARAMETER">rowsec_command</replaceable> TO (<replaceable class="PARAMETER">condition</replaceable>)</literal></term>
+    <listitem>
+     <para>
+      This form set row-level security policy of the table.
+      Supplied <replaceable class="PARAMETER">condition</replaceable> performs
+      as if it is implicitly appended to the qualifiers of <literal>WHERE</literal>
+      clause, although mechanism guarantees to evaluate this condition earlier
+      than any other user given condition.
+      <literal>ALL</> is the only supported command type right now.
+      See also <xref linkend="ROW-SECURITY">.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>RESET ROW SECURITY FOR <replaceable class="PARAMETER">rowsec_command</replaceable></literal></term>
+    <listitem>
+     <para>
+      This form reset row-level security policy of the table, if exists.
+      <literal>ALL</> is the only supported command type right now.
     <term><literal>REPLICA IDENTITY</literal></term>
     <listitem>
      <para>
@@ -840,6 +864,20 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
       </listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><replaceable class="PARAMETER">condition</replaceable></term>
+      <listitem>
+       <para>
+        An expression that returns a value of type boolean. Expect for a case
+        when queries are executed with superuser privilege, only rows for which
+        this expression returns true will be fetched, updated or deleted.
+        This expression can reference columns of the relation being configured.
+        Sub-queries can be contained within expression tree, unless referenced
+        relation recursively references the same relation.
+       </para>
+      </listitem>
+     </varlistentry>
+
     </variablelist>
  </refsect1>
 
diff --git a/src/backend/catalog/pg_rowsecurity.c b/src/backend/catalog/pg_rowsecurity.c
index 7ee4e88..34d33e8 100644
--- a/src/backend/catalog/pg_rowsecurity.c
+++ b/src/backend/catalog/pg_rowsecurity.c
@@ -285,3 +285,53 @@ RemoveRowSecurityById(Oid rowsecId)
 	systable_endscan(sscan);
 	heap_close(catalog, RowExclusiveLock);
 }
+
+/*
+ * ALTER TABLE <name> SET ROW SECURITY (...) OR
+ *                    RESET ROW SECURITY
+ */
+void
+ATExecSetRowSecurity(Relation relation, const char *cmdname, Node *clause)
+{
+	Oid			relid = RelationGetRelid(relation);
+	char		rseccmd;
+
+	if (strcmp(cmdname, "all") == 0)
+		rseccmd = ROWSECURITY_CMD_ALL;
+	else
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("Row-security for \"%s\" is not implemented yet",
+						cmdname)));
+
+	if (clause != NULL)
+	{
+		InsertOrUpdatePolicyRow(relation, rseccmd, clause);
+
+		/*
+		 * Also, turn on relhasrowsecurity, if not.
+		 */
+		if (!RelationGetForm(relation)->relhasrowsecurity)
+		{
+			Relation	class_rel = heap_open(RelationRelationId,
+											  RowExclusiveLock);
+			HeapTuple	tuple;
+
+			tuple = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(relid));
+			if (!HeapTupleIsValid(tuple))
+				elog(ERROR, "cache lookup failed for relation %u", relid);
+
+			((Form_pg_class) GETSTRUCT(tuple))->relhasrowsecurity = true;
+
+			simple_heap_update(class_rel, &tuple->t_self, tuple);
+			CatalogUpdateIndexes(class_rel, tuple);
+
+			heap_freetuple(tuple);
+			heap_close(class_rel, RowExclusiveLock);
+		}
+	}
+	else
+		DeletePolicyRow(relation, rseccmd);
+
+	CacheInvalidateRelcache(relation);
+}
diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
index 59f0842..51a1c0f 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -995,6 +995,7 @@ EventTriggerSupportsObjectClass(ObjectClass objclass)
 		case OCLASS_USER_MAPPING:
 		case OCLASS_DEFACL:
 		case OCLASS_EXTENSION:
+		case OCLASS_ROWSECURITY:
 			return true;
 
 		case MAX_OCLASS:
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 26a4613..f02ce72 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -37,6 +37,7 @@
 #include "catalog/pg_inherits_fn.h"
 #include "catalog/pg_namespace.h"
 #include "catalog/pg_opclass.h"
+#include "catalog/pg_rowsecurity.h"
 #include "catalog/pg_tablespace.h"
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
@@ -2789,6 +2790,8 @@ AlterTableGetLockLevel(List *cmds)
 			case AT_SetTableSpace:		/* must rewrite heap */
 			case AT_DropNotNull:		/* may change some SQL plans */
 			case AT_SetNotNull:
+			case AT_SetRowSecurity:
+			case AT_ResetRowSecurity:
 			case AT_GenericOptions:
 			case AT_AlterColumnGenericOptions:
 				cmd_lockmode = AccessExclusiveLock;
@@ -3164,6 +3167,8 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
 		case AT_DropInherit:	/* NO INHERIT */
 		case AT_AddOf:			/* OF */
 		case AT_DropOf: /* NOT OF */
+		case AT_SetRowSecurity:
+		case AT_ResetRowSecurity:
 			ATSimplePermissions(rel, ATT_TABLE);
 			/* These commands never recurse */
 			/* No command-specific prep needed */
@@ -3449,6 +3454,12 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
 		case AT_DropOf:
 			ATExecDropOf(rel, lockmode);
 			break;
+		case AT_SetRowSecurity:
+			ATExecSetRowSecurity(rel, cmd->name, (Node *) cmd->def);
+			break;
+		case AT_ResetRowSecurity:
+			ATExecSetRowSecurity(rel, cmd->name, NULL);
+			break;
 		case AT_ReplicaIdentity:
 			ATExecReplicaIdentity(rel, (ReplicaIdentityStmt *) cmd->def, lockmode);
 			break;
@@ -7814,6 +7825,22 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
 				Assert(defaultexpr);
 				break;
 
+			case OCLASS_ROWSECURITY:
+				/*
+				 * A row-level security policy can depend on a column in case
+				 * when the policy clause references a particular column.
+				 * Due to same reason why TRIGGER ... WHEN does not support
+				 * to change column's type being referenced in clause, row-
+				 * level security policy also does not support it.
+				 */
+				ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("cannot alter type of a column used in a row-level security policy"),
+						 errdetail("%s depends on column \"%s\"",
+								   getObjectDescription(&foundObject),
+								   colName)));
+				break;
+
 			case OCLASS_PROC:
 			case OCLASS_TYPE:
 			case OCLASS_CAST:
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 12a6beb..ba1ac81 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -260,6 +260,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <node>	alter_table_cmd alter_type_cmd opt_collate_clause
 	   replica_identity
 %type <list>	alter_table_cmds alter_type_cmds
+%type <str>		row_security_cmd
 
 %type <dbehavior>	opt_drop_behavior
 
@@ -2191,6 +2192,24 @@ alter_table_cmd:
 					n->def = (Node *)$2;
 					$$ = (Node *)n;
 				}
+			/* ALTER TABLE <name> SET ROW SECURITY FOR <cmd> TO (<expr>) */
+			| SET ROW SECURITY FOR row_security_cmd TO '(' a_expr ')'
+				{
+					AlterTableCmd *n = makeNode(AlterTableCmd);
+					n->subtype = AT_SetRowSecurity;
+					n->name = $5;
+					n->def = (Node *) $8;
+					$$ = (Node *)n;
+				}
+			/* ALTER TABLE <name> RESET ROW SECURITY FOR <cmd> */
+			| RESET ROW SECURITY FOR row_security_cmd
+				{
+					AlterTableCmd *n = makeNode(AlterTableCmd);
+					n->subtype = AT_ResetRowSecurity;
+					n->name = $5;
+					n->def = NULL;
+					$$ = (Node *)n;
+				}
 			/* ALTER TABLE <name> REPLICA IDENTITY  */
 			| REPLICA IDENTITY_P replica_identity
 				{
@@ -2301,6 +2320,12 @@ reloption_elem:
 				}
 		;
 
+row_security_cmd: ALL	{ $$ = "all"; }
+			| SELECT	{ $$ = "select"; }
+			| INSERT	{ $$ = "insert"; }
+			| UPDATE	{ $$ = "update"; }
+			| DELETE_P	{ $$ = "delete"; }
+		;
 
 /*****************************************************************************
  *
diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c
index 9613e2a..333b557 100644
--- a/src/backend/parser/parse_agg.c
+++ b/src/backend/parser/parse_agg.c
@@ -333,6 +333,9 @@ transformAggregateCall(ParseState *pstate, Aggref *agg,
 		case EXPR_KIND_TRIGGER_WHEN:
 			err = _("aggregate functions are not allowed in trigger WHEN conditions");
 			break;
+		case EXPR_KIND_ROW_SECURITY:
+			err = _("aggregate functions are not allowed in row-security policy");
+			break;
 
 			/*
 			 * There is intentionally no default: case here, so that the
@@ -662,6 +665,9 @@ transformWindowFuncCall(ParseState *pstate, WindowFunc *wfunc,
 		case EXPR_KIND_TRIGGER_WHEN:
 			err = _("window functions are not allowed in trigger WHEN conditions");
 			break;
+		case EXPR_KIND_ROW_SECURITY:
+			err = _("window functions are not allowed in row-security policy");
+			break;
 
 			/*
 			 * There is intentionally no default: case here, so that the
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 81c9338..e289e77 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -1462,6 +1462,7 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
 		case EXPR_KIND_OFFSET:
 		case EXPR_KIND_RETURNING:
 		case EXPR_KIND_VALUES:
+		case EXPR_KIND_ROW_SECURITY:
 			/* okay */
 			break;
 		case EXPR_KIND_CHECK_CONSTRAINT:
@@ -2643,6 +2644,8 @@ ParseExprKindName(ParseExprKind exprKind)
 			return "EXECUTE";
 		case EXPR_KIND_TRIGGER_WHEN:
 			return "WHEN";
+		case EXPR_KIND_ROW_SECURITY:
+			return "ROW SECURITY";
 
 			/*
 			 * There is intentionally no default: case here, so that the
diff --git a/src/include/catalog/pg_rowsecurity.h b/src/include/catalog/pg_rowsecurity.h
index 10aed9a..798d556 100644
--- a/src/include/catalog/pg_rowsecurity.h
+++ b/src/include/catalog/pg_rowsecurity.h
@@ -69,6 +69,8 @@ typedef struct
 } RowSecurityDesc;
 
 extern void	RelationBuildRowSecurity(Relation relation);
+extern void	ATExecSetRowSecurity(Relation relation,
+								 const char *cmdname, Node *clause);
 extern void	RemoveRowSecurityById(Oid relationId);
 
 #endif  /* PG_ROWSECURITY_H */
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 48c6d0f..3a918bb 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -1322,6 +1322,8 @@ typedef enum AlterTableType
 	AT_AddOf,					/* OF <type_name> */
 	AT_DropOf,					/* NOT OF */
 	AT_ReplicaIdentity,			/* REPLICA IDENTITY */
+	AT_SetRowSecurity,			/* SET ROW SECURITY (...) */
+	AT_ResetRowSecurity,		/* RESET ROW SECURITY */
 	AT_GenericOptions			/* OPTIONS (...) */
 } AlterTableType;
 
-- 
1.8.3.1