From 6550b901fe7c47c03775400e0c790c6c1234a017 Mon Sep 17 00:00:00 2001
From: Stephen Frost <sfrost@snowman.net>
Date: Wed, 24 Sep 2014 16:32:22 -0400
Subject: [PATCH] Code review for row security.

Buildfarm member tick identified an issue where the policies in the
relcache for a relation were were being replaced underneath a running
query, leading to segfaults while processing the policies to be added
to a query.  Similar to how TupleDesc RuleLocks are handled, add in a
equalRSDesc() function to check if the policies have actually changed
and, if not, swap back the rsdesc field (using the original instead of
the temporairly built one; the whole structure is swapped and then
specific fields swapped back).  This now passes a CLOBBER_CACHE_ALWAYS
for me and should resolve the buildfarm error.

In addition to addressing this, add a new chapter in Data Definition
under Privileges which explains row security and provides examples of
its usage, change \d to always list policies (even if row security is
disabled- but note that it is disabled, or enabled with no policies),
rework check_role_for_policy (it really didn't need the entire policy,
but it did need to be using has_privs_of_role()), and change the field
in pg_class to relrowsecurity from relhasrowsecurity, based on
Heikki's suggestion.  Also from Heikki, only issue SET ROW_SECURITY in
pg_restore when talking to a 9.5+ server, list Bypass RLS in \du, and
document --enable-row-security options for pg_dump and pg_restore.

Lastly, fix a number of minor whitespace and typo issues from Heikki,
Dimitri, add a missing #include, per Peter E, fix a few minor
variable-assigned-but-not-used and resource leak issues from Coverity
and add tab completion for role attribute bypassrls as well.
---
 doc/src/sgml/catalogs.sgml           |  11 +-
 doc/src/sgml/config.sgml             |   4 +-
 doc/src/sgml/ddl.sgml                | 168 +++++++++++++++++++++++++++
 doc/src/sgml/ref/alter_table.sgml    |   2 +-
 doc/src/sgml/ref/create_policy.sgml  |   2 +-
 doc/src/sgml/ref/pg_dump.sgml        |  17 +++
 doc/src/sgml/ref/pg_restore.sgml     |  23 ++++
 src/backend/catalog/heap.c           |   2 +-
 src/backend/catalog/system_views.sql |   2 +-
 src/backend/commands/policy.c        |  17 ++-
 src/backend/commands/tablecmds.c     |   4 +-
 src/backend/rewrite/rowsecurity.c    |  31 ++---
 src/backend/utils/adt/ri_triggers.c  |   4 +-
 src/backend/utils/cache/relcache.c   |  91 ++++++++++++++-
 src/bin/pg_dump/pg_backup_archiver.c |  11 +-
 src/bin/pg_dump/pg_dump.c            |  36 +++---
 src/bin/pg_dump/pg_dump.h            |   2 +-
 src/bin/pg_dump/pg_restore.c         |   2 +-
 src/bin/psql/describe.c              |  79 ++++++++-----
 src/bin/psql/tab-complete.c          |  38 +++---
 src/include/catalog/catversion.h     |   2 +-
 src/include/catalog/pg_class.h       |   4 +-
 src/include/commands/policy.h        |   7 +-
 src/test/regress/expected/rules.out  |   2 +-
 24 files changed, 439 insertions(+), 122 deletions(-)

diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 76d64050618..a6ca290cb3d 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -1941,8 +1941,9 @@
      </row>
 
      <row>
-      <entry><structfield>relhasrowsecurity</structfield></entry>
+      <entry><structfield>relrowsecurity</structfield></entry>
       <entry><type>bool</type></entry>
+      <entry></entry>
       <entry>
        True if table has row-security enabled; see
        <link linkend="catalog-pg-rowsecurity"><structname>pg_rowsecurity</structname></link> catalog
@@ -5415,7 +5416,7 @@
 
   <note>
    <para>
-    <literal>pg_class.relhasrowsecurity</literal>
+    <literal>pg_class.relrowsecurity</literal>
     True if the table has row-security enabled.
     Must be true if the table has a row-security policy in this catalog.
    </para>
@@ -9228,10 +9229,10 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx
       <entry>True if table has (or once had) triggers</entry>
      </row>
      <row>
-      <entry><structfield>hasrowsecurity</structfield></entry>
+      <entry><structfield>rowsecurity</structfield></entry>
       <entry><type>boolean</type></entry>
-      <entry><literal><link linkend="catalog-pg-class"><structname>pg_class</structname></link>.relhasrowsecurity</literal></entry>
-      <entry>True if table has row security enabled</entry>
+      <entry><literal><link linkend="catalog-pg-class"><structname>pg_class</structname></link>.relrowsecurity</literal></entry>
+      <entry>True if row security is enabled on the table</entry>
      </row>
     </tbody>
    </tgroup>
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 70e47aaa3a1..949443931cd 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -5457,9 +5457,9 @@ COPY postgres_log FROM '/full/path/to/logfile.csv' WITH csv;
 
        <para>
         The allowed values of <varname>row_security</> are
-        <literal>on</> (apply normally- not to superuser or table owner),
+        <literal>on</> (apply normally - not to superuser or table owner),
         <literal>off</> (fail if row security would be applied), and
-        <literal>force</> (apply always- even to superuser and table owner).
+        <literal>force</> (apply always - even to superuser and table owner).
        </para>
 
        <para>
diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml
index c07f5a203dd..e5ee5910513 100644
--- a/doc/src/sgml/ddl.sgml
+++ b/doc/src/sgml/ddl.sgml
@@ -1508,6 +1508,174 @@ REVOKE ALL ON accounts FROM PUBLIC;
   </para>
  </sect1>
 
+ <sect1 id="ddl-rowsecurity">
+  <title>Row Security Policies</title>
+
+  <indexterm zone="ddl-rowsecurity">
+   <primary>rowsecurity</primary>
+  </indexterm>
+
+  <indexterm zone="ddl-rowsecurity">
+   <primary>rls</primary>
+  </indexterm>
+
+  <indexterm>
+   <primary>policies</primary>
+   <see>policy</see>
+  </indexterm>
+
+  <indexterm zone="ddl-rowsecurity">
+   <primary>POLICY</primary>
+  </indexterm>
+
+  <para>
+   In addition to the <xref linkend="ddl-priv"> system available through
+   <xref linkend="sql-grant">, tables can have row security policies
+   which limit the rows returned for normal queries and rows which can
+   be added through data modification commands.  By default, tables do
+   not have any policies and all rows are visible and able to be added,
+   subject to the regular <xref linkend="ddl-priv"> system.  This is
+   also known to as Row Level Security.
+  </para>
+
+  <para>
+   When row security is enabled on a table with
+   <xref linkend="sql-altertable">, all normal access to the table
+   (excluding the owner) for selecting rows or adding rows must be through
+   a policy.  If no policy exists for the table, a default-deny policy is
+   used and no rows are visible or can be added.  Privileges which operate
+   at the whole table level such as <literal>TRUNCATE</>, and
+   <literal>REFERENCES</> are not subject to row security.
+  </para>
+
+  <para>
+   Row security policies can be specific to commands, or to roles, or to
+   both.  The commands available are <literal>SELECT</>, <literal>INSERT</>,
+   <literal>UPDATE</>, and <literal>DELETE</>.  Multiple roles can be
+   assigned to a given policy and normal role membership and inheiritance
+   rules apply.
+  </para>
+
+  <para>
+   To specify which rows are visible and what rows can be added to the
+   table with row security, an expression is required which returns a
+   boolean result.  This expression will be evaluated for each row prior
+   to other conditionals or functions which are part of the query.  The
+   one exception to this rule are <literal>leakproof</literal> functions,
+   which are guaranteed to not leak information.  Two expressions may be
+   specified to provide independent control over the rows which are
+   visible and the rows which are allowed to be added.  The expression
+   is run as part of the query and with the privileges of the user
+   running the query, however, security definer functions can be used in
+   the expression.
+  </para>
+
+  <para>
+   Enabling and disabling row security, as well as adding policies to a
+   table, is always the privilege of the owner only.
+  </para>
+
+  <para>
+   Policies are created using the <xref linkend="sql-createpolicy">
+   command, altered using the <xref linkend="sql-alterpolicy"> command,
+   and dropped using the <xref linkend="sql-droppolicy"> command.  To
+   enable and disable row security for a given table, use the
+   <xref linkend="sql-altertable"> command.
+  </para>
+
+  <para>
+   The table owners and superusers bypass the row security system when
+   querying a table, by default.  Row security can be enabled for
+   superusers and table owners by setting
+   <xref linkend="guc-row-security"> to <literal>force</literal>.  Any
+   user can request that row security be bypassed by setting
+   <xref linkend="guc-row-security"> to <literal>off</literal>.  If
+   the user does not have privileges to bypass row security when
+   querying a given table then an error will be returned instead.  Other
+   users can be granted the ability to bypass the row security system
+   with the <literal>BYPASSRLS</literal> role attribute.  This
+   attribute can only be set by a superuser.
+  </para>
+
+  <para>
+   Each policy has a name and multiple policies can be defined for a
+   table.  As policies are table-specific, each policy for a table must
+   have a unique name.  Different tables may have policies with the
+   same name.
+  </para>
+
+  <para>
+   When multiple policies apply to a given query, they are combined using
+   <literal>OR</literal>, similar to how a given role has the privileges
+   of all roles which they are a member of.
+  </para>
+
+  <para>
+   Referential integrity checks, such as unique or primary key constraints
+   and foreign key references, will bypass row security to ensure that
+   data integrity is maintained.  Care must be taken when developing
+   schemas and row level policies to avoid a "covert channel" leak of
+   information through these referntial integrity checks.
+  </para>
+
+  <para>
+   To enable row security for a table,
+   the <command>ALTER TABLE</command> is used.  For example, to enable
+   row level security for the table accounts, use:
+  </para>
+
+<programlisting>
+-- Create the table first
+CREATE TABLE accounts (manager text, company text, contact_email text);
+ALTER TABLE accounts ENABLE ROW LEVEL SECURITY;
+</programlisting>
+
+  <para>
+   To create a policy on the account relation to allow the managers role
+   to view the rows of their accounts, the <command>CREATE POLICY</command>
+   command can be used:
+  </para>
+
+<programlisting>
+CREATE POLICY account_managers ON accounts TO managers
+    USING (manager = current_user);
+</programlisting>
+
+  <para>
+   If no role is specified, or the special <quote>user</quote> name
+   <literal>PUBLIC</literal> is used, then the policy applies to all
+   users on the system.  To allow all users to view their own row in
+   a user table, a simple policy can be used:
+  </para>
+
+<programlisting>
+CREATE POLICY user_policy ON users
+    USING (user = current_user);
+</programlisting>
+
+  <para>
+   To use a different policy for rows which are being added to the
+   table from those rows which are visible, the WITH CHECK clause
+   can be used.  This would allow all users to view all rows in the
+   users table, but only modify their own:
+  </para>
+
+<programlisting>
+CREATE POLICY user_policy ON users
+    USING (true)
+    WITH CHECK (user = current_user);
+</programlisting>
+
+  <para>
+   Row security can be disabled with the <command>ALTER TABLE</command>
+   also.  Note that disabling row security does not remove the
+   policies which are defined on the table, they are simply ignored
+   and all rows are visible and able to be added, subject to the
+   normal privileges system.
+  </para>
+
+ </sect1>
+
  <sect1 id="ddl-schemas">
   <title>Schemas</title>
 
diff --git a/doc/src/sgml/ref/alter_table.sgml b/doc/src/sgml/ref/alter_table.sgml
index 1b35756c295..b5ef09e6a4a 100644
--- a/doc/src/sgml/ref/alter_table.sgml
+++ b/doc/src/sgml/ref/alter_table.sgml
@@ -429,7 +429,7 @@ ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable>
       These forms control the application of row security policies belonging
       to the table.  If enabled and no policies exist for the table, then a
       default-deny policy is applied.  Note that policies can exist for a table
-      even if row level security is disabled- in this case, the policies will
+      even if row level security is disabled - in this case, the policies will
       NOT be applied and the policies will be ignored.
       See also
       <xref linkend="SQL-CREATEPOLICY">.
diff --git a/doc/src/sgml/ref/create_policy.sgml b/doc/src/sgml/ref/create_policy.sgml
index c6599eda1c0..3c5bdc69cdc 100644
--- a/doc/src/sgml/ref/create_policy.sgml
+++ b/doc/src/sgml/ref/create_policy.sgml
@@ -240,7 +240,7 @@ CREATE POLICY <replaceable class="parameter">name</replaceable> ON <replaceable
      </varlistentry>
 
      <varlistentry id="SQL-CREATEPOLICY-UPDATE">
-      <term><literal>DELETE</></term>
+      <term><literal>UPDATE</></term>
       <listitem>
        <para>
          Using <literal>UPDATE</literal> for a policy means that it will apply
diff --git a/doc/src/sgml/ref/pg_dump.sgml b/doc/src/sgml/ref/pg_dump.sgml
index eabdc62f820..c92c6eef5d3 100644
--- a/doc/src/sgml/ref/pg_dump.sgml
+++ b/doc/src/sgml/ref/pg_dump.sgml
@@ -687,6 +687,23 @@ PostgreSQL documentation
       </listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><option>--enable-row-security</></term>
+      <listitem>
+       <para>
+        This option is relevant only when dumping the contents of a table
+        which has row security.  By default, pg_dump will set
+        <literal>ROW_SECURITY</literal> to <literal>OFF</literal>, to ensure
+        that all data is dumped from the table.  If the user does not have
+        sufficient privileges to bypass row security, then an error is thrown.
+        This parameter instructs <application>pg_dump</application> to set
+        row_security to 'ON' instead, allowing the user to dump the contents
+        of the table which they have access to.
+       </para>
+
+      </listitem>
+     </varlistentry>
+
      <varlistentry>
       <term><option>--exclude-table-data=<replaceable class="parameter">table</replaceable></option></term>
       <listitem>
diff --git a/doc/src/sgml/ref/pg_restore.sgml b/doc/src/sgml/ref/pg_restore.sgml
index 4bc30ce679b..9f8dc00480c 100644
--- a/doc/src/sgml/ref/pg_restore.sgml
+++ b/doc/src/sgml/ref/pg_restore.sgml
@@ -490,6 +490,29 @@
       </listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><option>--enable-row-security</></term>                                                          <listitem>
+       <para>
+        This option is relevant only when restoring the contents of a table
+        which has row security.  By default, pg_restore will set
+        <literal>ROW_SECURITY</literal> to <literal>OFF</literal>, to ensure
+        that all data is restored in to the table.  If the user does not have
+        sufficient privileges to bypass row security, then an error is thrown.
+        This parameter instructs <application>pg_restore</application> to set
+        row_security to 'ON' instead, allowing the user to attempt to restore
+        the contents of the table with row security enabled.  This may still
+        fail if the user does not have the right to insert the rows from the
+        dump into the table.
+       </para>
+      
+       <para>
+        Note that this option currently also requires the dump be in INSERT
+        format as COPY TO does not support row security.
+       </para>
+
+      </listitem>
+     </varlistentry>
+
      <varlistentry>
       <term><option>--if-exists</option></term>
       <listitem>
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 8d9eeb9dd7f..55c1e79563b 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -799,7 +799,7 @@ InsertPgClassTuple(Relation pg_class_desc,
 	values[Anum_pg_class_relhaspkey - 1] = BoolGetDatum(rd_rel->relhaspkey);
 	values[Anum_pg_class_relhasrules - 1] = BoolGetDatum(rd_rel->relhasrules);
 	values[Anum_pg_class_relhastriggers - 1] = BoolGetDatum(rd_rel->relhastriggers);
-	values[Anum_pg_class_relhasrowsecurity - 1] = BoolGetDatum(rd_rel->relhasrowsecurity);
+	values[Anum_pg_class_relrowsecurity - 1] = BoolGetDatum(rd_rel->relrowsecurity);
 	values[Anum_pg_class_relhassubclass - 1] = BoolGetDatum(rd_rel->relhassubclass);
 	values[Anum_pg_class_relispopulated - 1] = BoolGetDatum(rd_rel->relispopulated);
 	values[Anum_pg_class_relreplident - 1] = CharGetDatum(rd_rel->relreplident);
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index f62ed2e17d8..9d9d2394848 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -119,7 +119,7 @@ CREATE VIEW pg_tables AS
         C.relhasindex AS hasindexes,
         C.relhasrules AS hasrules,
         C.relhastriggers AS hastriggers,
-        C.relhasrowsecurity AS hasrowsecurity
+        C.relrowsecurity AS rowsecurity
     FROM pg_class C LEFT JOIN pg_namespace N ON (N.oid = C.relnamespace)
          LEFT JOIN pg_tablespace T ON (T.oid = C.reltablespace)
     WHERE C.relkind = 'r';
diff --git a/src/backend/commands/policy.c b/src/backend/commands/policy.c
index 0cfba566d09..6bff9500c6b 100644
--- a/src/backend/commands/policy.c
+++ b/src/backend/commands/policy.c
@@ -108,7 +108,7 @@ parse_row_security_command(const char *cmd_name)
 	char cmd;
 
 	if (!cmd_name)
-		elog(ERROR, "Unregonized command.");
+		elog(ERROR, "unregonized command");
 
 	if (strcmp(cmd_name, "all") == 0)
 		cmd = 0;
@@ -121,8 +121,7 @@ parse_row_security_command(const char *cmd_name)
 	else if (strcmp(cmd_name, "delete") == 0)
 		cmd = ACL_DELETE_CHR;
 	else
-		elog(ERROR, "Unregonized command.");
-		/* error unrecognized command */
+		elog(ERROR, "unregonized command");
 
 	return cmd;
 }
@@ -422,8 +421,8 @@ RemovePolicyById(Oid policy_id)
 	heap_close(rel, AccessExclusiveLock);
 
 	/*
-	 * Note that, unlike some of the other flags in pg_class, relhasrowsecurity
-	 * is not just an indication of if policies exist.  When relhasrowsecurity
+	 * Note that, unlike some of the other flags in pg_class, relrowsecurity
+	 * is not just an indication of if policies exist.  When relrowsecurity
 	 * is set (which can be done directly by the user or indirectly by creating
 	 * a policy on the table), then all access to the relation must be through
 	 * a policy.  If no policy is defined for the relation then a default-deny
@@ -484,7 +483,7 @@ CreatePolicy(CreatePolicyStmt *stmt)
 	if (rseccmd == ACL_INSERT_CHR && stmt->qual != NULL)
 		ereport(ERROR,
 				(errcode(ERRCODE_SYNTAX_ERROR),
-				 errmsg("Only WITH CHECK expression allowed for INSERT")));
+				 errmsg("only WITH CHECK expression allowed for INSERT")));
 
 
 	/* Collect role ids */
@@ -731,7 +730,7 @@ AlterPolicy(AlterPolicyStmt *stmt)
 	if (!HeapTupleIsValid(rsec_tuple))
 		ereport(ERROR,
 				(errcode(ERRCODE_UNDEFINED_OBJECT),
-				 errmsg("policy '%s' for does not exist on table %s",
+				 errmsg("policy \"%s\" on table \"%s\" does not exist",
 						stmt->policy_name,
 						RelationGetRelationName(target_table))));
 
@@ -850,7 +849,7 @@ rename_policy(RenameStmt *stmt)
 
 	pg_rowsecurity_rel = heap_open(RowSecurityRelationId, RowExclusiveLock);
 
-	/* First pass- check for conflict */
+	/* First pass -- check for conflict */
 
 	/* Add key - row security relation id. */
 	ScanKeyInit(&skey[0],
@@ -868,7 +867,7 @@ rename_policy(RenameStmt *stmt)
 							   RowSecurityRelidPolnameIndexId, true, NULL, 2,
 							   skey);
 
-	if (HeapTupleIsValid(rsec_tuple = systable_getnext(sscan)))
+	if (HeapTupleIsValid(systable_getnext(sscan)))
 		ereport(ERROR,
 				(errcode(ERRCODE_DUPLICATE_OBJECT),
 				 errmsg("row-policy \"%s\" for table \"%s\" already exists",
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 0385404c578..cb16c53a607 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -10647,7 +10647,7 @@ ATExecEnableRowSecurity(Relation rel)
 	if (!HeapTupleIsValid(tuple))
 		elog(ERROR, "cache lookup failed for relation %u", relid);
 
-	((Form_pg_class) GETSTRUCT(tuple))->relhasrowsecurity = true;
+	((Form_pg_class) GETSTRUCT(tuple))->relrowsecurity = true;
 	simple_heap_update(pg_class, &tuple->t_self, tuple);
 
 	/* keep catalog indexes current */
@@ -10674,7 +10674,7 @@ ATExecDisableRowSecurity(Relation rel)
 	if (!HeapTupleIsValid(tuple))
 		elog(ERROR, "cache lookup failed for relation %u", relid);
 
-	((Form_pg_class) GETSTRUCT(tuple))->relhasrowsecurity = false;
+	((Form_pg_class) GETSTRUCT(tuple))->relrowsecurity = false;
 	simple_heap_update(pg_class, &tuple->t_self, tuple);
 
 	/* keep catalog indexes current */
diff --git a/src/backend/rewrite/rowsecurity.c b/src/backend/rewrite/rowsecurity.c
index e1ccd1295e6..bb95b367198 100644
--- a/src/backend/rewrite/rowsecurity.c
+++ b/src/backend/rewrite/rowsecurity.c
@@ -61,7 +61,7 @@ static void process_policies(List *policies, int rt_index,
 							 Expr **final_qual,
 							 Expr **final_with_check_qual,
 							 bool *hassublinks);
-static bool check_role_for_policy(RowSecurityPolicy *policy, Oid user_id);
+static bool check_role_for_policy(ArrayType *policy_roles, Oid user_id);
 
 /*
  * hook to allow extensions to apply their own security policy
@@ -177,7 +177,7 @@ prepend_row_security_policies(Query* root, RangeTblEntry* rte, int rt_index)
 	 * all of them OR'd together.  However, to avoid the situation of an
 	 * extension granting more access to a table than the internal policies
 	 * would allow, the extension's policies are AND'd with the internal
-	 * policies.  In other words- extensions can only provide further
+	 * policies.  In other words - extensions can only provide further
 	 * filtering of the result set (or further reduce the set of records
 	 * allowed to be added).
 	 *
@@ -305,7 +305,8 @@ pull_row_security_policies(CmdType cmd, Relation relation, Oid user_id)
 		policy = (RowSecurityPolicy *) lfirst(item);
 
 		/* Always add ALL policies, if they exist. */
-		if (policy->cmd == '\0' && check_role_for_policy(policy, user_id))
+		if (policy->cmd == '\0' &&
+				check_role_for_policy(policy->roles, user_id))
 			policies = lcons(policy, policies);
 
 		/* Build the list of policies to return. */
@@ -313,23 +314,23 @@ pull_row_security_policies(CmdType cmd, Relation relation, Oid user_id)
 		{
 			case CMD_SELECT:
 				if (policy->cmd == ACL_SELECT_CHR
-					&& check_role_for_policy(policy, user_id))
+					&& check_role_for_policy(policy->roles, user_id))
 					policies = lcons(policy, policies);
 				break;
 			case CMD_INSERT:
 				/* If INSERT then only need to add the WITH CHECK qual */
 				if (policy->cmd == ACL_INSERT_CHR
-					&& check_role_for_policy(policy, user_id))
+					&& check_role_for_policy(policy->roles, user_id))
 					policies = lcons(policy, policies);
 				break;
 			case CMD_UPDATE:
 				if (policy->cmd == ACL_UPDATE_CHR
-					&& check_role_for_policy(policy, user_id))
+					&& check_role_for_policy(policy->roles, user_id))
 					policies = lcons(policy, policies);
 				break;
 			case CMD_DELETE:
 				if (policy->cmd == ACL_DELETE_CHR
-					&& check_role_for_policy(policy, user_id))
+					&& check_role_for_policy(policy->roles, user_id))
 					policies = lcons(policy, policies);
 				break;
 			default:
@@ -473,7 +474,7 @@ check_enable_rls(Oid relid, Oid checkAsUser)
 {
 	HeapTuple		tuple;
 	Form_pg_class	classform;
-	bool			relhasrowsecurity;
+	bool			relrowsecurity;
 	Oid				user_id = checkAsUser ? checkAsUser : GetUserId();
 
 	tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relid));
@@ -482,12 +483,12 @@ check_enable_rls(Oid relid, Oid checkAsUser)
 
 	classform = (Form_pg_class) GETSTRUCT(tuple);
 
-	relhasrowsecurity = classform->relhasrowsecurity;
+	relrowsecurity = classform->relrowsecurity;
 
 	ReleaseSysCache(tuple);
 
 	/* Nothing to do if the relation does not have RLS */
-	if (!relhasrowsecurity)
+	if (!relrowsecurity)
 		return RLS_NONE;
 
 	/*
@@ -537,19 +538,19 @@ check_enable_rls(Oid relid, Oid checkAsUser)
  * check_role_for_policy -
  *   determines if the policy should be applied for the current role
  */
-bool
-check_role_for_policy(RowSecurityPolicy *policy, Oid user_id)
+static bool
+check_role_for_policy(ArrayType *policy_roles, Oid user_id)
 {
 	int			i;
-	Oid		   *roles = (Oid *) ARR_DATA_PTR(policy->roles);
+	Oid		   *roles = (Oid *) ARR_DATA_PTR(policy_roles);
 
 	/* Quick fall-thru for policies applied to all roles */
 	if (roles[0] == ACL_ID_PUBLIC)
 		return true;
 
-	for (i = 0; i < ARR_DIMS(policy->roles)[0]; i++)
+	for (i = 0; i < ARR_DIMS(policy_roles)[0]; i++)
 	{
-		if (is_member_of_role(user_id, roles[i]))
+		if (has_privs_of_role(user_id, roles[i]))
 			return true;
 	}
 
diff --git a/src/backend/utils/adt/ri_triggers.c b/src/backend/utils/adt/ri_triggers.c
index ed4a3769e45..c0156fab1f9 100644
--- a/src/backend/utils/adt/ri_triggers.c
+++ b/src/backend/utils/adt/ri_triggers.c
@@ -2309,9 +2309,9 @@ RI_Initial_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
 	 * have RLS enabled.
 	 */
 	if (!has_bypassrls_privilege(GetUserId()) &&
-		((pk_rel->rd_rel->relhasrowsecurity &&
+		((pk_rel->rd_rel->relrowsecurity &&
 		  !pg_class_ownercheck(pkrte->relid, GetUserId())) ||
-		 (fk_rel->rd_rel->relhasrowsecurity &&
+		 (fk_rel->rd_rel->relrowsecurity &&
 		  !pg_class_ownercheck(fkrte->relid, GetUserId()))))
 		return false;
 
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index e7f7129bd9d..c98e3132883 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -847,6 +847,87 @@ equalRuleLocks(RuleLock *rlock1, RuleLock *rlock2)
 	return true;
 }
 
+/*
+ *		equalPolicy
+ *
+ *		Determine whether two policies are equivalent
+ */
+static bool
+equalPolicy(RowSecurityPolicy *policy1, RowSecurityPolicy *policy2)
+{
+	int			i;
+	Oid        *r1,
+	           *r2;
+
+	if (policy1 != NULL)
+	{
+		if (policy2 == NULL)
+			return false;
+
+		if (policy1->rsecid != policy2->rsecid)
+			return false;
+		if (policy1->cmd != policy2->cmd)
+			return false;
+		if (policy1->hassublinks != policy2->hassublinks);
+			return false;
+		if (strcmp(policy1->policy_name,policy2->policy_name) != 0)
+			return false;
+		if (ARR_DIMS(policy1->roles)[0] != ARR_DIMS(policy2->roles)[0])
+			return false;
+
+		r1 = (Oid *) ARR_DATA_PTR(policy1->roles);
+		r2 = (Oid *) ARR_DATA_PTR(policy2->roles);
+
+		for (i = 0; i < ARR_DIMS(policy1->roles)[0]; i++)
+		{
+			if (r1[i] != r2[i])
+				return false;
+		}
+
+		if (!equal(policy1->qual, policy1->qual))
+			return false;
+		if (!equal(policy1->with_check_qual, policy2->with_check_qual))
+			return false;
+	}
+	else if (policy2 != NULL)
+		return false;
+
+	return true;
+}
+
+/*
+ *		equalRSDesc
+ *
+ *		Determine whether two RowSecurityDesc's are equivalent
+ */
+static bool
+equalRSDesc(RowSecurityDesc *rsdesc1, RowSecurityDesc *rsdesc2)
+{
+	ListCell 	*lc,
+				*rc;
+
+	if (rsdesc1 == NULL && rsdesc2 == NULL)
+		return true;
+
+	if ((rsdesc1 != NULL && rsdesc2 == NULL) ||
+		(rsdesc1 == NULL && rsdesc2 != NULL))
+		return false;
+
+	if (list_length(rsdesc1->policies) != list_length(rsdesc2->policies))
+		return false;
+
+	/* RelationBuildRowSecurity should build policies in order */
+	forboth(lc, rsdesc1->policies, rc, rsdesc2->policies)
+	{
+		RowSecurityPolicy	   *l = (RowSecurityPolicy *) lfirst(lc);
+		RowSecurityPolicy	   *r = (RowSecurityPolicy *) lfirst(rc);
+
+		if (!equalPolicy(l,r))
+			return false;
+	}
+
+	return false;
+}
 
 /*
  *		RelationBuildDesc
@@ -967,7 +1048,7 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
 	else
 		relation->trigdesc = NULL;
 
-	if (relation->rd_rel->relhasrowsecurity)
+	if (relation->rd_rel->relrowsecurity)
 		RelationBuildRowSecurity(relation);
 	else
 		relation->rsdesc = NULL;
@@ -2104,6 +2185,7 @@ RelationClearRelation(Relation relation, bool rebuild)
 		Oid			save_relid = RelationGetRelid(relation);
 		bool		keep_tupdesc;
 		bool		keep_rules;
+		bool		keep_policies;
 
 		/* Build temporary entry, but don't link it into hashtable */
 		newrel = RelationBuildDesc(save_relid, false);
@@ -2117,6 +2199,7 @@ RelationClearRelation(Relation relation, bool rebuild)
 
 		keep_tupdesc = equalTupleDescs(relation->rd_att, newrel->rd_att);
 		keep_rules = equalRuleLocks(relation->rd_rules, newrel->rd_rules);
+		keep_policies = equalRSDesc(relation->rsdesc, newrel->rsdesc);
 
 		/*
 		 * Perform swapping of the relcache entry contents.  Within this
@@ -2165,6 +2248,8 @@ RelationClearRelation(Relation relation, bool rebuild)
 			SWAPFIELD(RuleLock *, rd_rules);
 			SWAPFIELD(MemoryContext, rd_rulescxt);
 		}
+		if (keep_policies)
+			SWAPFIELD(RowSecurityDesc *, rsdesc);
 		/* toast OID override must be preserved */
 		SWAPFIELD(Oid, rd_toastoid);
 		/* pgstat_info must be preserved */
@@ -3345,11 +3430,11 @@ RelationCacheInitializePhase3(void)
 		/*
 		 * Re-load the row security policies if the relation has them, since
 		 * they are not preserved in the cache.  Note that we can never NOT
-		 * have a policy while relhasrowsecurity is true-
+		 * have a policy while relrowsecurity is true,
 		 * RelationBuildRowSecurity will create a single default-deny policy
 		 * if there is no policy defined in pg_rowsecurity.
 		 */
-		if (relation->rd_rel->relhasrowsecurity && relation->rsdesc == NULL)
+		if (relation->rd_rel->relrowsecurity && relation->rsdesc == NULL)
 		{
 			RelationBuildRowSecurity(relation);
 
diff --git a/src/bin/pg_dump/pg_backup_archiver.c b/src/bin/pg_dump/pg_backup_archiver.c
index 5476a1e7e2b..3b101d44835 100644
--- a/src/bin/pg_dump/pg_backup_archiver.c
+++ b/src/bin/pg_dump/pg_backup_archiver.c
@@ -376,10 +376,13 @@ RestoreArchive(Archive *AHX)
 	/*
 	 * Enable row-security if necessary.
 	 */
-	if (!ropt->enable_row_security)
-		ahprintf(AH, "SET row_security = off;\n");
-	else
-		ahprintf(AH, "SET row_security = on;\n");
+	if (PQserverVersion(AH->connection) >= 90500)
+	{
+		if (!ropt->enable_row_security)
+			ahprintf(AH, "SET row_security = off;\n");
+		else
+			ahprintf(AH, "SET row_security = on;\n");
+	}
 
 	/*
 	 * Establish important parameter values right away.
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 29153294e26..12811a801a3 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -2777,7 +2777,7 @@ dumpBlobs(Archive *fout, void *arg)
 void
 getRowSecurity(Archive *fout, TableInfo tblinfo[], int numTables)
 {
-	PQExpBuffer		query = createPQExpBuffer();
+	PQExpBuffer		query;
 	PGresult	   *res;
 	RowSecurityInfo *rsinfo;
 	int				i_oid;
@@ -2792,6 +2792,8 @@ getRowSecurity(Archive *fout, TableInfo tblinfo[], int numTables)
 	if (fout->remoteVersion < 90500)
 		return;
 
+	query = createPQExpBuffer();
+
 	for (i = 0; i < numTables; i++)
 	{
 		TableInfo *tbinfo = &tblinfo[i];
@@ -2809,7 +2811,7 @@ getRowSecurity(Archive *fout, TableInfo tblinfo[], int numTables)
 		 * We represent RLS enabled on a table by creating RowSecurityInfo
 		 * object with an empty policy.
 		 */
-		if (tbinfo->hasrowsec)
+		if (tbinfo->rowsec)
 		{
 			/*
 			 * Note: use tableoid 0 so that this object won't be mistaken for
@@ -4534,7 +4536,7 @@ getTables(Archive *fout, int *numTables)
 	int			i_relhastriggers;
 	int			i_relhasindex;
 	int			i_relhasrules;
-	int			i_relhasrowsec;
+	int			i_relrowsec;
 	int			i_relhasoids;
 	int			i_relfrozenxid;
 	int			i_relminmxid;
@@ -4588,7 +4590,7 @@ getTables(Archive *fout, int *numTables)
 						  "(%s c.relowner) AS rolname, "
 						  "c.relchecks, c.relhastriggers, "
 						  "c.relhasindex, c.relhasrules, c.relhasoids, "
-						  "c.relhasrowsecurity, "
+						  "c.relrowsecurity, "
 						  "c.relfrozenxid, c.relminmxid, tc.oid AS toid, "
 						  "tc.relfrozenxid AS tfrozenxid, "
 						  "tc.relminmxid AS tminmxid, "
@@ -4629,7 +4631,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, "
+						  "'f'::bool AS relrowsecurity, "
 						  "c.relfrozenxid, c.relminmxid, tc.oid AS toid, "
 						  "tc.relfrozenxid AS tfrozenxid, "
 						  "tc.relminmxid AS tminmxid, "
@@ -4670,7 +4672,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, "
+						  "'f'::bool AS relrowsecurity, "
 						  "c.relfrozenxid, c.relminmxid, tc.oid AS toid, "
 						  "tc.relfrozenxid AS tfrozenxid, "
 						  "tc.relminmxid AS tminmxid, "
@@ -4711,7 +4713,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, "
+						  "'f'::bool AS relrowsecurity, "
 						  "c.relfrozenxid, 0 AS relminmxid, tc.oid AS toid, "
 						  "tc.relfrozenxid AS tfrozenxid, "
 						  "0 AS tminmxid, "
@@ -4750,7 +4752,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, "
+						  "'f'::bool AS relrowsecurity, "
 						  "c.relfrozenxid, 0 AS relminmxid, tc.oid AS toid, "
 						  "tc.relfrozenxid AS tfrozenxid, "
 						  "0 AS tminmxid, "
@@ -4788,7 +4790,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, "
+						  "'f'::bool AS relrowsecurity, "
 						  "c.relfrozenxid, 0 AS relminmxid, tc.oid AS toid, "
 						  "tc.relfrozenxid AS tfrozenxid, "
 						  "0 AS tminmxid, "
@@ -4826,7 +4828,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, "
+						  "'f'::bool AS relrowsecurity, "
 						  "c.relfrozenxid, 0 AS relminmxid, tc.oid AS toid, "
 						  "tc.relfrozenxid AS tfrozenxid, "
 						  "0 AS tminmxid, "
@@ -4864,7 +4866,7 @@ getTables(Archive *fout, int *numTables)
 						  "(%s relowner) AS rolname, "
 						  "relchecks, (reltriggers <> 0) AS relhastriggers, "
 						  "relhasindex, relhasrules, relhasoids, "
-						  "'f'::bool AS relhasrowsecurity, "
+						  "'f'::bool AS relrowsecurity, "
 						  "0 AS relfrozenxid, 0 AS relminmxid,"
 						  "0 AS toid, "
 						  "0 AS tfrozenxid, 0 AS tminmxid,"
@@ -4901,7 +4903,7 @@ getTables(Archive *fout, int *numTables)
 						  "(%s relowner) AS rolname, "
 						  "relchecks, (reltriggers <> 0) AS relhastriggers, "
 						  "relhasindex, relhasrules, relhasoids, "
-						  "'f'::bool AS relhasrowsecurity, "
+						  "'f'::bool AS relrowsecurity, "
 						  "0 AS relfrozenxid, 0 AS relminmxid,"
 						  "0 AS toid, "
 						  "0 AS tfrozenxid, 0 AS tminmxid,"
@@ -4934,7 +4936,7 @@ getTables(Archive *fout, int *numTables)
 						  "(%s relowner) AS rolname, "
 						  "relchecks, (reltriggers <> 0) AS relhastriggers, "
 						  "relhasindex, relhasrules, relhasoids, "
-						  "'f'::bool AS relhasrowsecurity, "
+						  "'f'::bool AS relrowsecurity, "
 						  "0 AS relfrozenxid, 0 AS relminmxid,"
 						  "0 AS toid, "
 						  "0 AS tfrozenxid, 0 AS tminmxid,"
@@ -4962,7 +4964,7 @@ getTables(Archive *fout, int *numTables)
 						  "relchecks, (reltriggers <> 0) AS relhastriggers, "
 						  "relhasindex, relhasrules, "
 						  "'t'::bool AS relhasoids, "
-						  "'f'::bool AS relhasrowsecurity, "
+						  "'f'::bool AS relrowsecurity, "
 						  "0 AS relfrozenxid, 0 AS relminmxid,"
 						  "0 AS toid, "
 						  "0 AS tfrozenxid, 0 AS tminmxid,"
@@ -5000,7 +5002,7 @@ getTables(Archive *fout, int *numTables)
 						  "relchecks, (reltriggers <> 0) AS relhastriggers, "
 						  "relhasindex, relhasrules, "
 						  "'t'::bool AS relhasoids, "
-						  "'f'::bool AS relhasrowsecurity, "
+						  "'f'::bool AS relrowsecurity, "
 						  "0 AS relfrozenxid, 0 AS relminmxid,"
 						  "0 AS toid, "
 						  "0 AS tfrozenxid, 0 AS tminmxid,"
@@ -5048,7 +5050,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_relrowsec = PQfnumber(res, "relrowsecurity");
 	i_relhasoids = PQfnumber(res, "relhasoids");
 	i_relfrozenxid = PQfnumber(res, "relfrozenxid");
 	i_relminmxid = PQfnumber(res, "relminmxid");
@@ -5100,7 +5102,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].rowsec = (strcmp(PQgetvalue(res, i, i_relrowsec), "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));
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index b5d820e7616..646a2077a61 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -246,7 +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		rowsec;			/* does it have any row-security policy? */
 	bool		hasoids;		/* does it have OIDs? */
 	uint32		frozenxid;		/* for restore frozen xid */
 	uint32		minmxid;		/* for restore min multi xid */
diff --git a/src/bin/pg_dump/pg_restore.c b/src/bin/pg_dump/pg_restore.c
index 1c1b80f1374..21715dc9443 100644
--- a/src/bin/pg_dump/pg_restore.c
+++ b/src/bin/pg_dump/pg_restore.c
@@ -463,7 +463,7 @@ usage(const char *progname)
 	printf(_("  -x, --no-privileges          skip restoration of access privileges (grant/revoke)\n"));
 	printf(_("  -1, --single-transaction     restore as a single transaction\n"));
 	printf(_("  --disable-triggers           disable triggers during data-only restore\n"));
-	printf(_("  --enable-row-security         enable row level security\n"));
+	printf(_("  --enable-row-security        enable row level security\n"));
 	printf(_("  --if-exists                  use IF EXISTS when dropping objects\n"));
 	printf(_("  --no-data-for-failed-tables  do not restore data of tables that could not be\n"
 			 "                               created\n"));
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 97dc2dded2c..074be576966 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -1204,7 +1204,7 @@ describeOneTableDetails(const char *schemaname,
 		bool		hasindex;
 		bool		hasrules;
 		bool		hastriggers;
-		bool		hasrowsecurity;
+		bool		rowsecurity;
 		bool		hasoids;
 		Oid			tablespace;
 		char	   *reloptions;
@@ -1230,7 +1230,7 @@ describeOneTableDetails(const char *schemaname,
 	{
 		printfPQExpBuffer(&buf,
 			  "SELECT c.relchecks, c.relkind, c.relhasindex, c.relhasrules, "
-						  "c.relhastriggers, c.relhasrowsecurity, c.relhasoids, "
+						  "c.relhastriggers, c.relrowsecurity, c.relhasoids, "
 						  "%s, c.reltablespace, "
 						  "CASE WHEN c.reloftype = 0 THEN '' ELSE c.reloftype::pg_catalog.regtype::pg_catalog.text END, "
 						  "c.relpersistence, c.relreplident\n"
@@ -1355,7 +1355,7 @@ describeOneTableDetails(const char *schemaname,
 	tableinfo.hasindex = strcmp(PQgetvalue(res, 0, 2), "t") == 0;
 	tableinfo.hasrules = strcmp(PQgetvalue(res, 0, 3), "t") == 0;
 	tableinfo.hastriggers = strcmp(PQgetvalue(res, 0, 4), "t") == 0;
-	tableinfo.hasrowsecurity = strcmp(PQgetvalue(res, 0, 5), "t") == 0;
+	tableinfo.rowsecurity = strcmp(PQgetvalue(res, 0, 5), "t") == 0;
 	tableinfo.hasoids = strcmp(PQgetvalue(res, 0, 6), "t") == 0;
 	tableinfo.reloptions = (pset.sversion >= 80200) ?
 		pg_strdup(PQgetvalue(res, 0, 7)) : NULL;
@@ -1998,18 +1998,17 @@ describeOneTableDetails(const char *schemaname,
 			PQclear(result);
 		}
 
-
+		/* print any row-level policies */
 		if (pset.sversion >= 90500)
+		{
 			appendPQExpBuffer(&buf,
 				",\n pg_catalog.pg_get_expr(rs.rsecqual, c.oid) as \"%s\"",
 				gettext_noop("Row-security"));
-	if (verbose && pset.sversion >= 90500)
-		appendPQExpBuffer(&buf,
-			 "\n     LEFT JOIN pg_rowsecurity rs ON rs.rsecrelid = c.oid");
 
-		/* print any row-level policies */
-		if (tableinfo.hasrowsecurity)
-		{
+			if (verbose)
+				appendPQExpBuffer(&buf,
+					"\n     LEFT JOIN pg_rowsecurity rs ON rs.rsecrelid = c.oid");
+
 			printfPQExpBuffer(&buf,
 						   "SELECT rs.rsecpolname,\n"
 						   "CASE WHEN rs.rsecroles = '{0}' THEN NULL ELSE array(select rolname from pg_roles where oid = any (rs.rsecroles) order by 1) END,\n"
@@ -2019,41 +2018,53 @@ describeOneTableDetails(const char *schemaname,
 							  "FROM pg_catalog.pg_rowsecurity rs\n"
 				  "WHERE rs.rsecrelid = '%s' ORDER BY 1;",
 							  oid);
+
 			result = PSQLexec(buf.data, false);
 			if (!result)
 				goto error_return;
 			else
 				tuples = PQntuples(result);
 
-			if (tuples > 0)
-			{
+			/*
+			 * Handle cases where RLS is enabled and there are policies,
+			 * or there aren't policies, or RLS isn't enabled but there
+			 * are policies
+			 */
+			if (tableinfo.rowsecurity && tuples > 0)
 				printTableAddFooter(&cont, _("Policies:"));
-				for (i = 0; i < tuples; i++)
-				{
-					printfPQExpBuffer(&buf, "    POLICY \"%s\"",
-										  PQgetvalue(result, i, 0));
 
-					if (!PQgetisnull(result, i, 4))
-						appendPQExpBuffer(&buf, " (%s)",
-										  PQgetvalue(result, i, 4));
+			if (tableinfo.rowsecurity && tuples == 0)
+				printTableAddFooter(&cont, _("Policies (Row Security Enabled): (None)"));
 
-					if (!PQgetisnull(result, i, 2))
-						appendPQExpBuffer(&buf, " EXPRESSION %s",
-										  PQgetvalue(result, i, 2));
+			if (!tableinfo.rowsecurity && tuples > 0)
+				printTableAddFooter(&cont, _("Policies (Row Security Disabled):"));
 
-					if (!PQgetisnull(result, i, 3))
-						appendPQExpBuffer(&buf, " WITH CHECK %s",
-										  PQgetvalue(result, i, 3));
+			/* Might be an empty set - that's ok */
+			for (i = 0; i < tuples; i++)
+			{
+				printfPQExpBuffer(&buf, "    POLICY \"%s\"",
+									  PQgetvalue(result, i, 0));
 
-					printTableAddFooter(&cont, buf.data);
+				if (!PQgetisnull(result, i, 4))
+					appendPQExpBuffer(&buf, " (%s)",
+									  PQgetvalue(result, i, 4));
 
-					if (!PQgetisnull(result, i, 1))
-					{
-						printfPQExpBuffer(&buf, "          APPLIED TO %s",
-										  PQgetvalue(result, i, 1));
+				if (!PQgetisnull(result, i, 2))
+					appendPQExpBuffer(&buf, " EXPRESSION %s",
+									  PQgetvalue(result, i, 2));
 
-						printTableAddFooter(&cont, buf.data);
-					}
+				if (!PQgetisnull(result, i, 3))
+					appendPQExpBuffer(&buf, " WITH CHECK %s",
+									  PQgetvalue(result, i, 3));
+
+				printTableAddFooter(&cont, buf.data);
+
+				if (!PQgetisnull(result, i, 1))
+				{
+					printfPQExpBuffer(&buf, "          APPLIED TO %s",
+									  PQgetvalue(result, i, 1));
+
+					printTableAddFooter(&cont, buf.data);
 				}
 			}
 			PQclear(result);
@@ -2708,6 +2719,10 @@ describeRoles(const char *pattern, bool verbose)
 			if (strcmp(PQgetvalue(res, i, (verbose ? 10 : 9)), "t") == 0)
 				add_role_attribute(&buf, _("Replication"));
 
+		if (pset.sversion >= 90500)
+			if (strcmp(PQgetvalue(res, i, (verbose ? 11 : 10)), "t") == 0)
+				add_role_attribute(&buf, _("Bypass RLS"));
+
 		conns = atoi(PQgetvalue(res, i, 6));
 		if (conns >= 0)
 		{
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index a4594b6783c..886188c036f 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1214,11 +1214,12 @@ psql_completion(const char *text, int start, int end)
 			  pg_strcasecmp(prev2_wd, "ROLE") == 0))
 	{
 		static const char *const list_ALTERUSER[] =
-		{"CONNECTION LIMIT", "CREATEDB", "CREATEROLE", "CREATEUSER",
-			"ENCRYPTED", "INHERIT", "LOGIN", "NOCREATEDB", "NOCREATEROLE",
-			"NOCREATEUSER", "NOINHERIT", "NOLOGIN", "NOREPLICATION",
-			"NOSUPERUSER", "RENAME TO", "REPLICATION", "RESET", "SET",
-		"SUPERUSER", "UNENCRYPTED", "VALID UNTIL", "WITH", NULL};
+		{"BYPASSRLS", "CONNECTION LIMIT", "CREATEDB", "CREATEROLE",
+			"CREATEUSER", "ENCRYPTED", "INHERIT", "LOGIN", "NOBYPASSRLS",
+			"NOCREATEDB", "NOCREATEROLE", "NOCREATEUSER", "NOINHERIT",
+			"NOLOGIN", "NOREPLICATION", "NOSUPERUSER", "RENAME TO",
+			"REPLICATION", "RESET", "SET", "SUPERUSER", "UNENCRYPTED",
+		"VALID UNTIL", "WITH", NULL};
 
 		COMPLETE_WITH_LIST(list_ALTERUSER);
 	}
@@ -1231,11 +1232,12 @@ psql_completion(const char *text, int start, int end)
 	{
 		/* Similar to the above, but don't complete "WITH" again. */
 		static const char *const list_ALTERUSER_WITH[] =
-		{"CONNECTION LIMIT", "CREATEDB", "CREATEROLE", "CREATEUSER",
-			"ENCRYPTED", "INHERIT", "LOGIN", "NOCREATEDB", "NOCREATEROLE",
-			"NOCREATEUSER", "NOINHERIT", "NOLOGIN", "NOREPLICATION",
-			"NOSUPERUSER", "RENAME TO", "REPLICATION", "RESET", "SET",
-		"SUPERUSER", "UNENCRYPTED", "VALID UNTIL", NULL};
+		{"BYPASSRLS", "CONNECTION LIMIT", "CREATEDB", "CREATEROLE",
+			"CREATEUSER", "ENCRYPTED", "INHERIT", "LOGIN", "NOBYPASSRLS",
+			"NOCREATEDB", "NOCREATEROLE", "NOCREATEUSER", "NOINHERIT",
+			"NOLOGIN", "NOREPLICATION", "NOSUPERUSER", "RENAME TO",
+			"REPLICATION", "RESET", "SET", "SUPERUSER", "UNENCRYPTED",
+		"VALID UNTIL", NULL};
 
 		COMPLETE_WITH_LIST(list_ALTERUSER_WITH);
 	}
@@ -2565,10 +2567,10 @@ psql_completion(const char *text, int start, int end)
 			  pg_strcasecmp(prev2_wd, "GROUP") == 0 || pg_strcasecmp(prev2_wd, "USER") == 0))
 	{
 		static const char *const list_CREATEROLE[] =
-		{"ADMIN", "CONNECTION LIMIT", "CREATEDB", "CREATEROLE", "CREATEUSER",
-			"ENCRYPTED", "IN", "INHERIT", "LOGIN", "NOCREATEDB",
-			"NOCREATEROLE", "NOCREATEUSER", "NOINHERIT", "NOLOGIN",
-			"NOREPLICATION", "NOSUPERUSER", "REPLICATION", "ROLE",
+		{"ADMIN", "BYPASSRLS", "CONNECTION LIMIT", "CREATEDB", "CREATEROLE",
+			"CREATEUSER", "ENCRYPTED", "IN", "INHERIT", "LOGIN", "NOBYPASSRLS",
+			"NOCREATEDB", "NOCREATEROLE", "NOCREATEUSER", "NOINHERIT",
+			"NOLOGIN", "NOREPLICATION", "NOSUPERUSER", "REPLICATION", "ROLE",
 		"SUPERUSER", "SYSID", "UNENCRYPTED", "VALID UNTIL", "WITH", NULL};
 
 		COMPLETE_WITH_LIST(list_CREATEROLE);
@@ -2583,10 +2585,10 @@ psql_completion(const char *text, int start, int end)
 	{
 		/* Similar to the above, but don't complete "WITH" again. */
 		static const char *const list_CREATEROLE_WITH[] =
-		{"ADMIN", "CONNECTION LIMIT", "CREATEDB", "CREATEROLE", "CREATEUSER",
-			"ENCRYPTED", "IN", "INHERIT", "LOGIN", "NOCREATEDB",
-			"NOCREATEROLE", "NOCREATEUSER", "NOINHERIT", "NOLOGIN",
-			"NOREPLICATION", "NOSUPERUSER", "REPLICATION", "ROLE",
+		{"ADMIN", "BYPASSRLS", "CONNECTION LIMIT", "CREATEDB", "CREATEROLE",
+			"CREATEUSER", "ENCRYPTED", "IN", "INHERIT", "LOGIN", "NOBYPASSRLS",
+			"NOCREATEDB", "NOCREATEROLE", "NOCREATEUSER", "NOINHERIT",
+			"NOLOGIN", "NOREPLICATION", "NOSUPERUSER", "REPLICATION", "ROLE",
 		"SUPERUSER", "SYSID", "UNENCRYPTED", "VALID UNTIL", NULL};
 
 		COMPLETE_WITH_LIST(list_CREATEROLE_WITH);
diff --git a/src/include/catalog/catversion.h b/src/include/catalog/catversion.h
index af0475e8314..bee67ddb373 100644
--- a/src/include/catalog/catversion.h
+++ b/src/include/catalog/catversion.h
@@ -53,6 +53,6 @@
  */
 
 /*							yyyymmddN */
-#define CATALOG_VERSION_NO	201409191
+#define CATALOG_VERSION_NO	201409241
 
 #endif
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index f6353514cac..22c55a94903 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -65,7 +65,7 @@ CATALOG(pg_class,1259) BKI_BOOTSTRAP BKI_ROWTYPE_OID(83) BKI_SCHEMA_MACRO
 	bool		relhasrules;	/* has (or has had) any rules */
 	bool		relhastriggers; /* has (or has had) any TRIGGERs */
 	bool		relhassubclass; /* has (or has had) derived classes */
-	bool		relhasrowsecurity;	/* has (or has had) row-security policy */
+	bool		relrowsecurity;	/* row-security is enabled or not */
 	bool		relispopulated; /* matview currently holds query results */
 	char		relreplident;	/* see REPLICA_IDENTITY_xxx constants  */
 	TransactionId relfrozenxid; /* all Xids < this are frozen in this rel */
@@ -119,7 +119,7 @@ typedef FormData_pg_class *Form_pg_class;
 #define Anum_pg_class_relhasrules		21
 #define Anum_pg_class_relhastriggers	22
 #define Anum_pg_class_relhassubclass	23
-#define Anum_pg_class_relhasrowsecurity	24
+#define Anum_pg_class_relrowsecurity	24
 #define Anum_pg_class_relispopulated	25
 #define Anum_pg_class_relreplident		26
 #define Anum_pg_class_relfrozenxid		27
diff --git a/src/include/commands/policy.h b/src/include/commands/policy.h
index 95d8a6d1175..fcc991173b7 100644
--- a/src/include/commands/policy.h
+++ b/src/include/commands/policy.h
@@ -16,6 +16,7 @@
 #define POLICY_H
 
 #include "nodes/parsenodes.h"
+#include "utils/relcache.h"
 
 extern void RelationBuildRowSecurity(Relation relation);
 
@@ -24,10 +25,10 @@ extern void RemovePolicyById(Oid policy_id);
 extern Oid CreatePolicy(CreatePolicyStmt *stmt);
 extern Oid AlterPolicy(AlterPolicyStmt *stmt);
 
-Oid get_relation_policy_oid(Oid relid,
-							const char *policy_name, bool missing_ok);
+extern Oid get_relation_policy_oid(Oid relid, const char *policy_name,
+						bool missing_ok);
 
-Oid rename_policy(RenameStmt *stmt);
+extern Oid rename_policy(RenameStmt *stmt);
 
 
 #endif   /* POLICY_H */
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 889bcd201fc..c53e7851cc9 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -2046,7 +2046,7 @@ pg_tables| SELECT n.nspname AS schemaname,
     c.relhasindex AS hasindexes,
     c.relhasrules AS hasrules,
     c.relhastriggers AS hastriggers,
-    c.relhasrowsecurity AS hasrowsecurity
+    c.relrowsecurity AS rowsecurity
    FROM ((pg_class c
      LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace)))
      LEFT JOIN pg_tablespace t ON ((t.oid = c.reltablespace)))
-- 
GitLab