diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 68f84343520fd18ac7baa62eaf4387c9d1937a6b..76d64050618dc0d2f8d52c696f42b59606be5956 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -238,6 +238,11 @@
       <entry>replication slot information</entry>
      </row>
 
+     <row>
+      <entry><link linkend="catalog-pg-rowsecurity"><structname>pg_rowsecurity</structname></link></entry>
+      <entry>table row-level security policies</entry>
+     </row>
+
      <row>
       <entry><link linkend="catalog-pg-seclabel"><structname>pg_seclabel</structname></link></entry>
       <entry>security labels on database objects</entry>
@@ -1935,6 +1940,15 @@
       </entry>
      </row>
 
+     <row>
+      <entry><structfield>relhasrowsecurity</structfield></entry>
+      <entry><type>bool</type></entry>
+      <entry>
+       True if table has row-security enabled; see
+       <link linkend="catalog-pg-rowsecurity"><structname>pg_rowsecurity</structname></link> catalog
+      </entry>
+     </row>
+
      <row>
       <entry><structfield>relhassubclass</structfield></entry>
       <entry><type>bool</type></entry>
@@ -5328,6 +5342,86 @@
   </table>
  </sect1>
 
+ <sect1 id="catalog-pg-rowsecurity">
+  <title><structname>pg_rowsecurity</structname></title>
+
+  <indexterm zone="catalog-pg-rowsecurity">
+   <primary>pg_rowsecurity</primary>
+  </indexterm>
+
+  <para>
+   The catalog <structname>pg_rowsecurity</structname> stores row-level
+   security policies for each table.  A policy includes the kind of
+   command which it applies to (or all commands), the roles which it
+   applies to, the expression to be added as a security-barrier
+   qualification to queries which include the table and the expression
+   to be added as a with-check option for queries which attempt to add
+   new records to the table.
+  </para>
+
+  <table>
+
+   <title><structname>pg_rowsecurity</structname> Columns</title>
+
+   <tgroup cols="4">
+    <thead>
+     <row>
+      <entry>Name</entry>
+      <entry>Type</entry>
+      <entry>References</entry>
+      <entry>Description</entry>
+     </row>
+    </thead>
+
+    <tbody>
+     <row>
+      <entry><structfield>rsecpolname</structfield></entry>
+      <entry><type>name</type></entry>
+      <entry></entry>
+      <entry>The name of the row-security policy</entry>
+     </row>
+
+     <row>
+      <entry><structfield>rsecrelid</structfield></entry>
+      <entry><type>oid</type></entry>
+      <entry><literal><link linkend="catalog-pg-class"><structname>pg_class</structname></link>.oid</literal></entry>
+      <entry>The table to which the row-security policy belongs</entry>
+     </row>
+
+     <row>
+      <entry><structfield>rseccmd</structfield></entry>
+      <entry><type>char</type></entry>
+      <entry></entry>
+      <entry>The command type to which the row-security policy is applied.</entry>
+     </row>
+
+     <row>
+      <entry><structfield>rsecqual</structfield></entry>
+      <entry><type>pg_node_tree</type></entry>
+      <entry></entry>
+      <entry>The expression tree to be added to the security barrier qualifications for queries which use the table.</entry>
+     </row>
+
+     <row>
+      <entry><structfield>rsecwithcheck</structfield></entry>
+      <entry><type>pg_node_tree</type></entry>
+      <entry></entry>
+      <entry>The expression tree to be added to the with check qualifications for queries which attempt to add rows to the table.</entry>
+     </row>
+
+    </tbody>
+   </tgroup>
+  </table>
+
+  <note>
+   <para>
+    <literal>pg_class.relhasrowsecurity</literal>
+    True if the table has row-security enabled.
+    Must be true if the table has a row-security policy in this catalog.
+   </para>
+  </note>
+
+ </sect1>
 
  <sect1 id="catalog-pg-seclabel">
   <title><structname>pg_seclabel</structname></title>
@@ -9133,6 +9227,12 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx
       <entry><literal><link linkend="catalog-pg-class"><structname>pg_class</structname></link>.relhastriggers</literal></entry>
       <entry>True if table has (or once had) triggers</entry>
      </row>
+     <row>
+      <entry><structfield>hasrowsecurity</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>
+     </row>
     </tbody>
    </tgroup>
   </table>
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 5be8fdcc252de5cea9bf551785a7ef58075d61b8..70e47aaa3a1be9e9b953ea9c9c417fffc5788644 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -5429,6 +5429,46 @@ COPY postgres_log FROM '/full/path/to/logfile.csv' WITH csv;
       </listitem>
      </varlistentry>
 
+     <varlistentry id="guc-row-security" xreflabel="row_security">
+      <term><varname>row_security</varname> (<type>enum</type>)
+      <indexterm>
+       <primary><varname>row_security</> configuration parameter</primary>
+      </indexterm>
+      </term>
+      <listitem>
+       <para>
+        This variable controls if row security policies are to be applied
+        to queries which are run against tables that have row security enabled.
+        The default is 'on'.  When set to 'on', all users, except superusers
+        and the owner of the table, will have the row policies for the table
+        applied to their queries.  The table owner and superuser can request
+        that row policies be applied to their queries by setting this to
+        'force'.  Lastly, this can also be set to 'off' which will bypass row
+        policies for the table, if possible, and error if not.
+       </para>
+
+       <para>
+        For a user who is not a superuser and not the table owner to bypass
+        row policies for the table, they must have the BYPASSRLS role attribute.
+        If this is set to 'off' and the user queries a table which has row
+        policies enabled and the user does not have the right to bypass
+        row policies then a permission denied error will be returned.
+       </para>
+
+       <para>
+        The allowed values of <varname>row_security</> are
+        <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).
+       </para>
+
+       <para>
+        For more information on row security policies,
+        see <xref linkend="SQL-CREATEPOLICY">.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry id="guc-default-tablespace" xreflabel="default_tablespace">
       <term><varname>default_tablespace</varname> (<type>string</type>)
       <indexterm>
diff --git a/doc/src/sgml/event-trigger.sgml b/doc/src/sgml/event-trigger.sgml
index 3db8ef1a132d1df8664be27ed9691b994d03f952..6f71a27855e8a4efa89236ebed1f888003218203 100644
--- a/doc/src/sgml/event-trigger.sgml
+++ b/doc/src/sgml/event-trigger.sgml
@@ -195,6 +195,12 @@
         <entry align="center"><literal>X</literal></entry>
         <entry align="center"><literal>-</literal></entry>
        </row>
+       <row>
+        <entry align="left"><literal>ALTER POLICY</literal></entry>
+        <entry align="center"><literal>X</literal></entry>
+        <entry align="center"><literal>X</literal></entry>
+        <entry align="center"><literal>-</literal></entry>
+       </row>
        <row>
         <entry align="left"><literal>ALTER SCHEMA</literal></entry>
         <entry align="center"><literal>X</literal></entry>
@@ -351,6 +357,12 @@
         <entry align="center"><literal>X</literal></entry>
         <entry align="center"><literal>-</literal></entry>
        </row>
+       <row>
+        <entry align="left"><literal>CREATE POLICY</literal></entry>
+        <entry align="center"><literal>X</literal></entry>
+        <entry align="center"><literal>X</literal></entry>
+        <entry align="center"><literal>-</literal></entry>
+       </row>
        <row>
         <entry align="left"><literal>CREATE RULE</literal></entry>
         <entry align="center"><literal>X</literal></entry>
@@ -525,6 +537,12 @@
         <entry align="center"><literal>X</literal></entry>
         <entry align="center"><literal>X</literal></entry>
        </row>
+       <row>
+        <entry align="left"><literal>DROP POLICY</literal></entry>
+        <entry align="center"><literal>X</literal></entry>
+        <entry align="center"><literal>X</literal></entry>
+        <entry align="center"><literal>X</literal></entry>
+       </row>
        <row>
         <entry align="left"><literal>DROP RULE</literal></entry>
         <entry align="center"><literal>X</literal></entry>
diff --git a/doc/src/sgml/keywords.sgml b/doc/src/sgml/keywords.sgml
index 1c93b7c148dcc7a03ebb5a671f588e8d3630d524..b0dfd5ff75bce4cb477c754a208d05e54a21f349 100644
--- a/doc/src/sgml/keywords.sgml
+++ b/doc/src/sgml/keywords.sgml
@@ -3422,6 +3422,13 @@
     <entry>non-reserved</entry>
     <entry>non-reserved</entry>
    </row>
+   <row>
+    <entry><token>POLICY</token></entry>
+    <entry>non-reserved</entry>
+    <entry></entry>
+    <entry></entry>
+    <entry></entry>
+   </row>
    <row>
     <entry><token>PORTION</token></entry>
     <entry></entry>
diff --git a/doc/src/sgml/ref/allfiles.sgml b/doc/src/sgml/ref/allfiles.sgml
index b685e16a0fa0a524cf45aebbf428619241c579b3..7aa3128090d801168432f37d1871188c3de9ad0e 100644
--- a/doc/src/sgml/ref/allfiles.sgml
+++ b/doc/src/sgml/ref/allfiles.sgml
@@ -25,6 +25,7 @@ Complete list of usable sgml source files in this directory.
 <!ENTITY alterOperator      SYSTEM "alter_operator.sgml">
 <!ENTITY alterOperatorClass SYSTEM "alter_opclass.sgml">
 <!ENTITY alterOperatorFamily SYSTEM "alter_opfamily.sgml">
+<!ENTITY alterPolicy        SYSTEM "alter_policy.sgml">
 <!ENTITY alterRole          SYSTEM "alter_role.sgml">
 <!ENTITY alterRule          SYSTEM "alter_rule.sgml">
 <!ENTITY alterSchema        SYSTEM "alter_schema.sgml">
@@ -69,6 +70,7 @@ Complete list of usable sgml source files in this directory.
 <!ENTITY createOperator     SYSTEM "create_operator.sgml">
 <!ENTITY createOperatorClass SYSTEM "create_opclass.sgml">
 <!ENTITY createOperatorFamily SYSTEM "create_opfamily.sgml">
+<!ENTITY createPolicy       SYSTEM "create_policy.sgml">
 <!ENTITY createRole         SYSTEM "create_role.sgml">
 <!ENTITY createRule         SYSTEM "create_rule.sgml">
 <!ENTITY createSchema       SYSTEM "create_schema.sgml">
@@ -110,6 +112,7 @@ Complete list of usable sgml source files in this directory.
 <!ENTITY dropOperatorClass  SYSTEM "drop_opclass.sgml">
 <!ENTITY dropOperatorFamily  SYSTEM "drop_opfamily.sgml">
 <!ENTITY dropOwned          SYSTEM "drop_owned.sgml">
+<!ENTITY dropPolicy         SYSTEM "drop_policy.sgml">
 <!ENTITY dropRole           SYSTEM "drop_role.sgml">
 <!ENTITY dropRule           SYSTEM "drop_rule.sgml">
 <!ENTITY dropSchema         SYSTEM "drop_schema.sgml">
diff --git a/doc/src/sgml/ref/alter_policy.sgml b/doc/src/sgml/ref/alter_policy.sgml
new file mode 100644
index 0000000000000000000000000000000000000000..37615fcab5df2dbbc42d6be60dc7397f95212804
--- /dev/null
+++ b/doc/src/sgml/ref/alter_policy.sgml
@@ -0,0 +1,135 @@
+<!--
+doc/src/sgml/ref/alter_policy.sgml
+PostgreSQL documentation
+-->
+
+<refentry id="SQL-ALTERPOLICY">
+ <indexterm zone="sql-alterpolicy">
+  <primary>ALTER POLICY</primary>
+ </indexterm>
+
+ <refmeta>
+  <refentrytitle>ALTER POLICY</refentrytitle>
+  <manvolnum>7</manvolnum>
+  <refmiscinfo>SQL - Language Statements</refmiscinfo>
+ </refmeta>
+
+ <refnamediv>
+  <refname>ALTER POLICY</refname>
+  <refpurpose>change the definition of a row-security policy</refpurpose>
+ </refnamediv>
+
+ <refsynopsisdiv>
+<synopsis>
+ALTER POLICY <replaceable class="parameter">name</replaceable> ON <replaceable class="parameter">table_name</replaceable>
+    [ RENAME TO <replaceable class="PARAMETER">new_name</replaceable> ]
+    [ TO { <replaceable class="parameter">role_name</replaceable> | PUBLIC } [, ...] ]
+    [ USING ( <replaceable class="parameter">expression</replaceable> ) ]
+    [ WITH CHECK ( <replaceable class="parameter">check_expression</replaceable> ) ]
+</synopsis>
+ </refsynopsisdiv>
+
+ <refsect1>
+  <title>Description</title>
+
+  <para>
+   <command>ALTER POLICY</command> changes the <replaceable class="parameter">
+   definition</replaceable> of an existing row-security policy.
+  </para>
+
+  <para>
+   To use <command>ALTER POLICY</command>, you must own the table that
+   the policy applies to.
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>Parameters</title>
+
+  <variablelist>
+   <varlistentry>
+    <term><replaceable class="parameter">name</replaceable></term>
+    <listitem>
+     <para>
+      The name of an existing policy to alter.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><replaceable class="parameter">table_name</replaceable></term>
+    <listitem>
+     <para>
+      The name (optionally schema-qualified) of the table that the
+      policy is on.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><replaceable class="parameter">new_name</replaceable></term>
+    <listitem>
+     <para>
+      The new name for the policy.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><replaceable class="parameter">role_name</replaceable></term>
+    <listitem>
+     <para>
+      The role to which the policy applies.  Multiple roles can be specified at one time.
+      To apply the policy to all roles, use <literal>PUBLIC</literal>, which is also
+      the default.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><replaceable class="parameter">expression</replaceable></term>
+    <listitem>
+     <para>
+      The USING expression for the policy.  This expression will be added as a
+      security-barrier qualification to queries which use the table
+      automatically.  If multiple policies are being applied for a given
+      table then they are all combined and added using OR.  The USING
+      expression applies to records which are being retrived from the table.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><replaceable class="parameter">check_expression</replaceable></term>
+    <listitem>
+     <para>
+      The with-check expression for the policy.  This expression will be
+      added as a WITH CHECK OPTION qualification to queries which use the
+      table automatically.  If multiple policies are being applied for a
+      given table then they are all combined and added using OR.  The WITH
+      CHECK expression applies to records which are being added to the table.
+     </para>
+    </listitem>
+   </varlistentry>
+
+  </variablelist>
+ </refsect1>
+
+ <refsect1>
+  <title>Compatibility</title>
+
+  <para>
+   <command>ALTER POLICY</command> is a <productname>PostgreSQL</productname> extension.
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>See Also</title>
+
+  <simplelist type="inline">
+   <member><xref linkend="sql-createpolicy"></member>
+   <member><xref linkend="sql-droppolicy"></member>
+  </simplelist>
+ </refsect1>
+
+</refentry>
diff --git a/doc/src/sgml/ref/alter_role.sgml b/doc/src/sgml/ref/alter_role.sgml
index bcd46d5e4dcb6ea0f345df3d2474fcedb4764a2b..0471daa1cce7329d4bce0b3efbe80a0f4cf24790 100644
--- a/doc/src/sgml/ref/alter_role.sgml
+++ b/doc/src/sgml/ref/alter_role.sgml
@@ -32,6 +32,7 @@ ALTER ROLE <replaceable class="PARAMETER">name</replaceable> [ [ WITH ] <replace
     | INHERIT | NOINHERIT
     | LOGIN | NOLOGIN
     | REPLICATION | NOREPLICATION
+    | BYPASSRLS | NOBYPASSRLS
     | CONNECTION LIMIT <replaceable class="PARAMETER">connlimit</replaceable>
     | [ ENCRYPTED | UNENCRYPTED ] PASSWORD '<replaceable class="PARAMETER">password</replaceable>'
     | VALID UNTIL '<replaceable class="PARAMETER">timestamp</replaceable>'
@@ -142,6 +143,8 @@ ALTER ROLE { <replaceable class="PARAMETER">name</replaceable> | ALL } [ IN DATA
       <term><literal>NOLOGIN</literal></term>
       <term><literal>REPLICATION</literal></term>
       <term><literal>NOREPLICATION</literal></term>
+      <term><literal>BYPASSRLS</literal></term>
+      <term><literal>NOBYPASSRLS</literal></term>
       <term><literal>CONNECTION LIMIT</literal> <replaceable class="parameter">connlimit</replaceable></term>
       <term><literal>PASSWORD</> <replaceable class="parameter">password</replaceable></term>
       <term><literal>ENCRYPTED</></term>
diff --git a/doc/src/sgml/ref/alter_table.sgml b/doc/src/sgml/ref/alter_table.sgml
index 5bbf4fb359513ea186dbe4db6c5ea8b20b789421..1b35756c2958b1023b76e6a9224e2e8a5939adc5 100644
--- a/doc/src/sgml/ref/alter_table.sgml
+++ b/doc/src/sgml/ref/alter_table.sgml
@@ -59,6 +59,8 @@ ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable>
     ENABLE RULE <replaceable class="PARAMETER">rewrite_rule_name</replaceable>
     ENABLE REPLICA RULE <replaceable class="PARAMETER">rewrite_rule_name</replaceable>
     ENABLE ALWAYS RULE <replaceable class="PARAMETER">rewrite_rule_name</replaceable>
+    DISABLE ROW LEVEL SECURITY
+    ENABLE ROW LEVEL SECURITY
     CLUSTER ON <replaceable class="PARAMETER">index_name</replaceable>
     SET WITHOUT CLUSTER
     SET WITH OIDS
@@ -420,6 +422,21 @@ ALTER TABLE ALL IN TABLESPACE <replaceable class="PARAMETER">name</replaceable>
     </listitem>
    </varlistentry>
 
+   <varlistentry>
+    <term><literal>DISABLE</literal>/<literal>ENABLE ROW LEVEL SECURITY</literal></term>
+    <listitem>
+     <para>
+      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
+      NOT be applied and the policies will be ignored.
+      See also
+      <xref linkend="SQL-CREATEPOLICY">.
+     </para>
+    </listitem>
+   </varlistentry>
+
    <varlistentry>
     <term><literal>CLUSTER ON</literal></term>
     <listitem>
diff --git a/doc/src/sgml/ref/create_policy.sgml b/doc/src/sgml/ref/create_policy.sgml
new file mode 100644
index 0000000000000000000000000000000000000000..c6599eda1c0013a07106a43f690583d5cf7dd6a5
--- /dev/null
+++ b/doc/src/sgml/ref/create_policy.sgml
@@ -0,0 +1,318 @@
+<!--
+doc/src/sgml/ref/create_policy.sgml
+PostgreSQL documentation
+-->
+
+<refentry id="SQL-CREATEPOLICY">
+ <indexterm zone="sql-createpolicy">
+  <primary>CREATE POLICY</primary>
+ </indexterm>
+
+ <refmeta>
+  <refentrytitle>CREATE POLICY</refentrytitle>
+  <manvolnum>7</manvolnum>
+  <refmiscinfo>SQL - Language Statements</refmiscinfo>
+ </refmeta>
+
+ <refnamediv>
+  <refname>CREATE POLICY</refname>
+  <refpurpose>define a new row-security policy for a table</refpurpose>
+ </refnamediv>
+
+ <refsynopsisdiv>
+<synopsis>
+CREATE POLICY <replaceable class="parameter">name</replaceable> ON <replaceable class="parameter">table_name</replaceable>
+    [ FOR { ALL | SELECT | INSERT | UPDATE | DELETE } ]
+    [ TO { <replaceable class="parameter">role_name</replaceable> | PUBLIC } [, ...] ]
+    [ USING ( <replaceable class="parameter">expression</replaceable> ) ]
+    [ WITH CHECK ( <replaceable class="parameter">check_expression</replaceable> ) ]
+</synopsis>
+ </refsynopsisdiv>
+
+ <refsect1>
+  <title>Description</title>
+
+  <para>
+   The <command>CREATE POLICY</command> command defines a new row-security
+   policy for a table.  Note that row-security must also be enabled on the
+   table using <command>ALTER TABLE</command> in order for created policies
+   to be applied.
+  </para>
+
+  <para>
+   A row-security policy is an expression which is added to the security-barrier
+   qualifications of queries which are run against the table the policy is on,
+   or an expression which is added to the with-check options for a table and
+   which is applied to rows which would be added to the table.
+   The security-barrier qualifications will always be evaluated prior to any
+   user-defined functions or user-provided WHERE clauses, while the with-check
+   expression will be evaluated against the rows which are going to be added to
+   the table.  By adding policies to a table, a user can limit the rows which a
+   given user can select, insert, update, or delete.  This capability is also
+   known as Row-Level Security or RLS.
+  </para>
+
+  <para>
+   Policy names are per-table, therefore one policy name can be used for many
+   different tables and have a definition for each table which is appropriate to
+   that table.
+  </para>
+
+  <para>
+   Policies can be applied for specific commands or for specific roles.  The
+   default for newly created policies is that they apply for all commands and
+   roles, unless otherwise specified.  If multiple policies apply to a given
+   query, they will be combined using OR.
+  </para>
+
+  <para>
+   Note that while row-security policies will be applied for explicit queries
+   against tables in the system, they are not applied when the system is
+   performing internal referential integrity checks or validating constraints.
+   This means there are indirect ways to determine that a given value exists.
+   An example of this is attempting to insert a duplicate value
+   into a column which is the primary key or has a unique constraint.  If the
+   insert fails then the user can infer that the value already exists (this
+   example assumes that the user is permitted by policy to insert
+   records which they are not allowed to see).  Another example is where a user
+   is allowed to insert into a table which references another, otherwise hidden
+   table.  Existence can be determined by the user inserting values into the
+   referencing table, where success would indicate that the value exists in the
+   referenced table.  These issues can be addressed by carefully crafting
+   policies which prevent users from being able to insert, delete, or update
+   records at all which might possibly indicate a value they are not otherwise
+   able to see, or by using generated values (eg: surrogate keys) instead.
+  </para>
+
+  <para>
+   Regarding how policy expressions interact with the user: as the expressions
+   are added to the user's query directly, they will be run with the rights of
+   the user running the overall query.  Therefore, users who are using a given
+   policy must be able to access any tables or functions referenced in the
+   expression or they will simply receive a permission denied error when
+   attempting to query the RLS-enabled table.  This does not change how views
+   work, however.  As with normal queries and views, permission checks and
+   policies for the tables which are referenced by a view will use the view
+   owner's rights and any policies which apply to the view owner.
+  </para>
+
+ </refsect1>
+
+ <refsect1>
+  <title>Parameters</title>
+
+  <variablelist>
+   <varlistentry>
+    <term><replaceable class="parameter">name</replaceable></term>
+    <listitem>
+     <para>
+      The name of the policy to be created.  This must be distinct from the
+      name of any other policy for the table.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><replaceable class="parameter">table_name</replaceable></term>
+    <listitem>
+     <para>
+      The name (optionally schema-qualified) of the table the
+      policy applies to.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><replaceable class="parameter">command</replaceable></term>
+    <listitem>
+     <para>
+      The command to which the policy applies.  Valid options are
+      <command>ALL</command>, <command>SELECT</command>,
+      <command>INSERT</command>, <command>UPDATE</command>,
+      and <command>DELETE</command>.
+      <command>ALL</command> is the default.
+      See below for specifics regarding how these are applied.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><replaceable class="parameter">role_name</replaceable></term>
+    <listitem>
+     <para>
+      The roles to which the policy is to be applied.  The default is
+      <literal>PUBLIC</literal>, which will apply the policy to all roles.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><replaceable class="parameter">expression</replaceable></term>
+    <listitem>
+     <para>
+      Any <acronym>SQL</acronym> conditional expression (returning
+      <type>boolean</type>).  The conditional expression cannot contain
+      any aggregate or window functions.  This expression will be added
+      to queries to filter out the records which are visible to the query.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><replaceable class="parameter">check_expression</replaceable></term>
+    <listitem>
+     <para>
+      Any <acronym>SQL</acronym> conditional expression (returning
+      <type>boolean</type>).  The condition expression cannot contain
+      any aggregate or window functions.  This expression will be added
+      to queries which are attempting to add records to the table as
+      with-check options, and an error will be thrown if this condition
+      returns false for any records being added.
+     </para>
+    </listitem>
+   </varlistentry>
+
+  </variablelist>
+ </refsect1>
+
+ <refsect1>
+   <title>Per-Command policies</title>
+
+   <variablelist>
+
+     <varlistentry id="SQL-CREATEPOLICY-ALL">
+      <term><literal>ALL</></term>
+      <listitem>
+       <para>
+         Using <literal>ALL</literal> for a policy means that it will apply
+         to all commands, regardless of the type of command.  If an
+         <literal>ALL</literal> policy exists and more specific policies
+         exist, then both the <literal>ALL</literal> policy and the more
+         specific policy (or policies) will be combined using
+         <literal>OR</literal>, as usual for overlapping policies.
+         Additionally, <literal>ALL</literal> policies will be applied to
+         both the selection side of a query and the modification side, using
+         the USING policy for both if only a USING policy has been defined.
+
+         As an example, if an <literal>UPDATE</literal> is issued, then the
+         <literal>ALL</literal> policy will be applicable to both what the
+         <literal>UPDATE</literal> will be able to select out as rows to be
+         updated (with the USING expression being applied), and it will be
+         applied to rows which result from the <literal>UPDATE</literal>
+         statement, to check if they are permitted to be added to the table
+         (using the WITH CHECK expression, if defined, and the USING expression
+         otherwise).  If an INSERT or UPDATE command attempts to add rows to
+         the table which do not pass the <literal>ALL</literal> WITH CHECK
+         (or USING, if no WITH CHECK expression is defined) expression, the
+         command will error.
+       </para>
+      </listitem>
+     </varlistentry>
+
+     <varlistentry id="SQL-CREATEPOLICY-SELECT">
+      <term><literal>SELECT</></term>
+      <listitem>
+       <para>
+         Using <literal>SELECT</literal> for a policy means that it will apply
+         to <literal>SELECT</literal> commands.  The result is that only those
+         records from the relation which pass the <literal>SELECT</literal>
+         policy will be returned, even if other records exist in the relation.
+         The <literal>SELECT</literal> policy only accepts the USING expression
+         as it only ever applies in cases where records are being retrived from
+         the relation.
+       </para>
+      </listitem>
+     </varlistentry>
+
+     <varlistentry id="SQL-CREATEPOLICY-INSERT">
+      <term><literal>INSERT</></term>
+      <listitem>
+       <para>
+         Using <literal>INSERT</literal> for a policy means that it will apply
+         to <literal>INSERT</literal> commands.  Rows being inserted which do
+         not pass this policy will result in a policy violation ERROR and the
+         entire <literal>INSERT</literal> command will be aborted.  The
+         <literal>INSERT</literal> policy only accepts the WITH CHECK expression
+         as it only ever applies in cases where records are being added to the
+         relation.
+       </para>
+      </listitem>
+     </varlistentry>
+
+     <varlistentry id="SQL-CREATEPOLICY-UPDATE">
+      <term><literal>DELETE</></term>
+      <listitem>
+       <para>
+         Using <literal>UPDATE</literal> for a policy means that it will apply
+         to <literal>UPDATE</literal> commands.  As <literal>UPDATE</literal>
+         involves pulling an existing record and then making changes to some
+         portion (but possibly not all) of the record, the
+         <literal>UPDATE</literal> policy accepts both a USING expression and
+         a WITH CHECK expression.  The USING expression will be used to
+         determine which records the <literal>UPDATE</literal> command will
+         see to operate against, while the <literal>WITH CHECK</literal>
+         expression defines what rows are allowed to be added back into the
+         relation (similar to the <literal>INSERT</literal> policy).
+         Any rows whose resulting values do not pass the
+         <literal>WITH CHECK</literal> expression will cause an ERROR and the
+         entire command will be aborted.
+       </para>
+      </listitem>
+     </varlistentry>
+
+     <varlistentry id="SQL-CREATEPOLICY-DELETE">
+      <term><literal>DELETE</></term>
+      <listitem>
+       <para>
+         Using <literal>DELETE</literal> for a policy means that it will apply
+         to <literal>DELETE</literal> commands.  Only rows which pass this
+         policy will be seen by a <literal>DELETE</literal> command.  Rows may
+         be visible through a <literal>SELECT</literal> which are not seen by a
+         <literal>DELETE</literal>, as they do not pass the USING expression
+         for the <literal>DELETE</literal>, and rows which are not visible
+         through the <literal>SELECT</literal> policy may be deleted if they
+         pass the <literal>DELETE</literal> USING policy.  The
+         <literal>DELETE</literal> policy only accept the USING expression as
+         it only ever applies in cases where records are being extracted from
+         the relation for deletion.
+       </para>
+      </listitem>
+     </varlistentry>
+
+   </variablelist>
+ </refsect1>
+
+ <refsect1>
+  <title>Notes</title>
+
+  <para>
+   You must be the owner of a table to create or change policies for it.
+  </para>
+
+  <para>
+   In order to maintain <firstterm>referential integrity</firstterm> between
+   two related tables, row-security policies are not applied when the system
+   performs checks on foreign key constraints.
+  </para>
+
+ </refsect1>
+
+ <refsect1>
+  <title>Compatibility</title>
+
+  <para>
+   <command>CREATE POLICY</command> is a <productname>PostgreSQL</productname>
+   extension.
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>See Also</title>
+
+  <simplelist type="inline">
+   <member><xref linkend="sql-alterpolicy"></member>
+   <member><xref linkend="sql-droppolicy"></member>
+  </simplelist>
+ </refsect1>
+
+</refentry>
diff --git a/doc/src/sgml/ref/create_role.sgml b/doc/src/sgml/ref/create_role.sgml
index 641e3500c9ad1c6cac5a85bfa4e9568c7342cd74..ea26027511458b362c892c60f88c556c9eb6e9d0 100644
--- a/doc/src/sgml/ref/create_role.sgml
+++ b/doc/src/sgml/ref/create_role.sgml
@@ -32,6 +32,7 @@ CREATE ROLE <replaceable class="PARAMETER">name</replaceable> [ [ WITH ] <replac
     | INHERIT | NOINHERIT
     | LOGIN | NOLOGIN
     | REPLICATION | NOREPLICATION
+    | BYPASSRLS | NOBYPASSRLS
     | CONNECTION LIMIT <replaceable class="PARAMETER">connlimit</replaceable>
     | [ ENCRYPTED | UNENCRYPTED ] PASSWORD '<replaceable class="PARAMETER">password</replaceable>'
     | VALID UNTIL '<replaceable class="PARAMETER">timestamp</replaceable>'
@@ -190,6 +191,25 @@ CREATE ROLE <replaceable class="PARAMETER">name</replaceable> [ [ WITH ] <replac
       </listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><literal>BYPASSRLS</literal></term>
+      <term><literal>NOBYPASSRLS</literal></term>
+      <listitem>
+       <para>
+        These clauses determine whether a role is allowed to bypass row-security
+        policies.  A role having the <literal>BYPASSRLS</literal> attribute will
+        be allowed to bypass row-security policies by setting
+        <literal>row_security</literal> to
+        <literal>OFF</literal>. <literal>NOBYPASSRLS</literal> is the default.
+        Note that pg_dump will set <literal>row_security</literal> to
+        <literal>OFF</literal> by default, to ensure all contents of a table are
+        dumped out.  If the user running pg_dump does not have appropriate
+        permissions, an error will be returned.  The superuser and owner of the
+        table being dumped are considered to always have the right to bypass RLS.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry>
       <term><literal>CONNECTION LIMIT</literal> <replaceable class="parameter">connlimit</replaceable></term>
       <listitem>
diff --git a/doc/src/sgml/ref/drop_policy.sgml b/doc/src/sgml/ref/drop_policy.sgml
new file mode 100644
index 0000000000000000000000000000000000000000..31ca9db220eca6148a1fe5d7e0ebae94dd64e6c6
--- /dev/null
+++ b/doc/src/sgml/ref/drop_policy.sgml
@@ -0,0 +1,109 @@
+<!--
+doc/src/sgml/ref/drop_policy.sgml
+PostgreSQL documentation
+-->
+
+<refentry id="SQL-DROPPOLICY">
+ <indexterm zone="sql-droppolicy">
+  <primary>DROP POLICY</primary>
+ </indexterm>
+
+ <refmeta>
+  <refentrytitle>DROP POLICY</refentrytitle>
+  <manvolnum>7</manvolnum>
+  <refmiscinfo>SQL - Language Statements</refmiscinfo>
+ </refmeta>
+
+ <refnamediv>
+  <refname>DROP POLICY</refname>
+  <refpurpose>remove a row-security policy from a table</refpurpose>
+ </refnamediv>
+
+ <refsynopsisdiv>
+<synopsis>
+DROP POLICY [ IF EXISTS ] <replaceable class="parameter">name</replaceable> ON <replaceable class="parameter">table_name</replaceable>
+</synopsis>
+ </refsynopsisdiv>
+
+ <refsect1>
+  <title>Description</title>
+
+  <para>
+   <command>DROP POLICY</command> removes the specified row-security policy
+   from the table.  Note that if the last policy is removed for a table and
+   the table still has ROW POLICY enabled via <command>ALTER TABLE</command>,
+   then the default-deny policy will be used.  <command>ALTER TABLE</command>
+   can be used to disable row security for a table using
+   <literal>DISABLE ROW SECURITY</literal>, whether policies for the table
+   exist or not.
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>Parameters</title>
+
+  <variablelist>
+
+   <varlistentry>
+    <term><literal>IF EXISTS</literal></term>
+    <listitem>
+     <para>
+      Do not throw an error if the policy does not exist. A notice is issued
+      in this case.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><replaceable class="parameter">name</replaceable></term>
+    <listitem>
+     <para>
+      The name of the policy to drop.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><replaceable class="parameter">table_name</replaceable></term>
+    <listitem>
+     <para>
+      The name (optionally schema-qualified) of the table that
+      the policy is on.
+     </para>
+    </listitem>
+   </varlistentry>
+
+  </variablelist>
+ </refsect1>
+
+ <refsect1>
+  <title>Examples</title>
+
+  <para>
+   To drop the row-security policy called <literal>p1</literal> on the
+   table named <literal>my_table</literal>:
+
+   <programlisting>
+    DROP POLICY p1 ON my_table;
+   </programlisting>
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>Compatibility</title>
+
+  <para>
+   <command>DROP POLICY</command> is a <productname>PostgreSQL</productname> extension.
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>See Also</title>
+
+  <simplelist type="inline">
+   <member><xref linkend="sql-createpolicy"></member>
+   <member><xref linkend="sql-alterpolicy"></member>
+  </simplelist>
+ </refsect1>
+
+</refentry>
diff --git a/doc/src/sgml/reference.sgml b/doc/src/sgml/reference.sgml
index 6ec126381c3b5ffbe484759ed702ce145f60c009..10c9a6d4030606296c5533f9b7e9e26689a42aac 100644
--- a/doc/src/sgml/reference.sgml
+++ b/doc/src/sgml/reference.sgml
@@ -53,6 +53,7 @@
    &alterOperator;
    &alterOperatorClass;
    &alterOperatorFamily;
+   &alterPolicy;
    &alterRole;
    &alterRule;
    &alterSchema;
@@ -97,6 +98,7 @@
    &createOperator;
    &createOperatorClass;
    &createOperatorFamily;
+   &createPolicy;
    &createRole;
    &createRule;
    &createSchema;
@@ -138,6 +140,7 @@
    &dropOperatorClass;
    &dropOperatorFamily;
    &dropOwned;
+   &dropPolicy;
    &dropRole;
    &dropRule;
    &dropSchema;
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index a974bd526077d6d73cf565f01c1e243b5ef26e6f..b257b02ff5c198d56d00dff9376ac6adf02fec7d 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -39,7 +39,7 @@ POSTGRES_BKI_SRCS = $(addprefix $(top_srcdir)/src/include/catalog/,\
 	pg_ts_config.h pg_ts_config_map.h pg_ts_dict.h \
 	pg_ts_parser.h pg_ts_template.h pg_extension.h \
 	pg_foreign_data_wrapper.h pg_foreign_server.h pg_user_mapping.h \
-	pg_foreign_table.h \
+	pg_foreign_table.h pg_rowsecurity.h \
 	pg_default_acl.h pg_seclabel.h pg_shseclabel.h pg_collation.h pg_range.h \
 	toasting.h indexing.h \
     )
diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
index d9745cabd244ae5ca42d288fae73f57531f8e676..d30612c4d9abbd90dce86b323f3dfbfac50aa034 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -5080,6 +5080,25 @@ has_createrole_privilege(Oid roleid)
 	return result;
 }
 
+bool
+has_bypassrls_privilege(Oid roleid)
+{
+	bool		result = false;
+	HeapTuple	utup;
+
+	/* Superusers bypass all permission checking. */
+	if (superuser_arg(roleid))
+		return true;
+
+	utup = SearchSysCache1(AUTHOID, ObjectIdGetDatum(roleid));
+	if (HeapTupleIsValid(utup))
+	{
+		result = ((Form_pg_authid) GETSTRUCT(utup))->rolbypassrls;
+		ReleaseSysCache(utup);
+	}
+	return result;
+}
+
 /*
  * Fetch pg_default_acl entry for given role, namespace and object type
  * (object type must be given in pg_default_acl's encoding).
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index d41ba49f87775fe502a17c2ac8d22329277b4043..256486c5fd2f53fb0eeebcf43632eed61632bfea 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -45,6 +45,7 @@
 #include "catalog/pg_opfamily.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_rewrite.h"
+#include "catalog/pg_rowsecurity.h"
 #include "catalog/pg_tablespace.h"
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_ts_config.h"
@@ -57,6 +58,7 @@
 #include "commands/defrem.h"
 #include "commands/event_trigger.h"
 #include "commands/extension.h"
+#include "commands/policy.h"
 #include "commands/proclang.h"
 #include "commands/schemacmds.h"
 #include "commands/seclabel.h"
@@ -1249,6 +1251,10 @@ doDeletion(const ObjectAddress *object, int flags)
 			RemoveEventTriggerById(object->objectId);
 			break;
 
+		case OCLASS_ROWSECURITY:
+			RemovePolicyById(object->objectId);
+			break;
+
 		default:
 			elog(ERROR, "unrecognized object class: %u",
 				 object->classId);
@@ -2316,6 +2322,9 @@ getObjectClass(const ObjectAddress *object)
 
 		case EventTriggerRelationId:
 			return OCLASS_EVENT_TRIGGER;
+
+		case RowSecurityRelationId:
+			return OCLASS_ROWSECURITY;
 	}
 
 	/* shouldn't get here */
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index c346edac93be55889e8fe18398579be551ce0f5f..8d9eeb9dd7f9eeaf5e385c8f05062dbb5e096913 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -799,6 +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_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/objectaddress.c b/src/backend/catalog/objectaddress.c
index d143a4459d33c04edb73dda335f8029a504db83b..b69b75bcc2e1e53b0f6f7bff51dd970fe075b88c 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -42,6 +42,7 @@
 #include "catalog/pg_opfamily.h"
 #include "catalog/pg_operator.h"
 #include "catalog/pg_proc.h"
+#include "catalog/pg_rowsecurity.h"
 #include "catalog/pg_rewrite.h"
 #include "catalog/pg_tablespace.h"
 #include "catalog/pg_trigger.h"
@@ -55,6 +56,7 @@
 #include "commands/defrem.h"
 #include "commands/event_trigger.h"
 #include "commands/extension.h"
+#include "commands/policy.h"
 #include "commands/proclang.h"
 #include "commands/tablespace.h"
 #include "commands/trigger.h"
@@ -343,6 +345,18 @@ static const ObjectPropertyType ObjectProperty[] =
 		-1,
 		false
 	},
+	{
+		RowSecurityRelationId,
+		RowSecurityOidIndexId,
+		-1,
+		-1,
+		Anum_pg_rowsecurity_rsecpolname,
+		InvalidAttrNumber,
+		InvalidAttrNumber,
+		InvalidAttrNumber,
+		-1,
+		false
+	},
 	{
 		EventTriggerRelationId,
 		EventTriggerOidIndexId,
@@ -517,6 +531,7 @@ get_object_address(ObjectType objtype, List *objname, List *objargs,
 			case OBJECT_RULE:
 			case OBJECT_TRIGGER:
 			case OBJECT_CONSTRAINT:
+			case OBJECT_POLICY:
 				address = get_object_address_relobject(objtype, objname,
 													   &relation, missing_ok);
 				break;
@@ -982,6 +997,13 @@ get_object_address_relobject(ObjectType objtype, List *objname,
 					InvalidOid;
 				address.objectSubId = 0;
 				break;
+			case OBJECT_POLICY:
+				address.classId = RowSecurityRelationId;
+				address.objectId = relation ?
+					get_relation_policy_oid(reloid, depname, missing_ok) :
+					InvalidOid;
+				address.objectSubId = 0;
+				break;
 			default:
 				elog(ERROR, "unrecognized objtype: %d", (int) objtype);
 				/* placate compiler, which doesn't know elog won't return */
@@ -1155,6 +1177,7 @@ check_object_ownership(Oid roleid, ObjectType objtype, ObjectAddress address,
 		case OBJECT_COLUMN:
 		case OBJECT_RULE:
 		case OBJECT_TRIGGER:
+		case OBJECT_POLICY:
 		case OBJECT_CONSTRAINT:
 			if (!pg_class_ownercheck(RelationGetRelid(relation), roleid))
 				aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CLASS,
@@ -2166,6 +2189,41 @@ getObjectDescription(const ObjectAddress *object)
 				break;
 			}
 
+		case OCLASS_ROWSECURITY:
+			{
+				Relation	rsec_rel;
+				ScanKeyData	skey[1];
+				SysScanDesc	sscan;
+				HeapTuple	tuple;
+				Form_pg_rowsecurity form_rsec;
+
+				rsec_rel = heap_open(RowSecurityRelationId, AccessShareLock);
+
+				ScanKeyInit(&skey[0],
+							ObjectIdAttributeNumber,
+							BTEqualStrategyNumber, F_OIDEQ,
+							ObjectIdGetDatum(object->objectId));
+
+				sscan = systable_beginscan(rsec_rel, RowSecurityOidIndexId,
+										   true, NULL, 1, skey);
+
+				tuple = systable_getnext(sscan);
+
+				if (!HeapTupleIsValid(tuple))
+					elog(ERROR, "cache lookup failed for row-security relation %u",
+						 object->objectId);
+
+				form_rsec = (Form_pg_rowsecurity) GETSTRUCT(tuple);
+
+				appendStringInfo(&buffer, _("policy %s on "),
+								 NameStr(form_rsec->rsecpolname));
+				getRelationDescription(&buffer, form_rsec->rsecrelid);
+
+				systable_endscan(sscan);
+				heap_close(rsec_rel, AccessShareLock);
+				break;
+			}
+
 		default:
 			appendStringInfo(&buffer, "unrecognized object %u %u %d",
 							 object->classId,
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 22663c31fe27171539b30c75203dc9a8dc7af683..f62ed2e17d8aa9aa2cccbbb8bcf0259c2633b7a0 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -19,6 +19,7 @@ CREATE VIEW pg_roles AS
         rolconnlimit,
         '********'::text as rolpassword,
         rolvaliduntil,
+        rolbypassrls,
         setconfig as rolconfig,
         pg_authid.oid
     FROM pg_authid LEFT JOIN pg_db_role_setting s
@@ -62,6 +63,34 @@ CREATE VIEW pg_user AS
         useconfig
     FROM pg_shadow;
 
+CREATE VIEW pg_policies AS
+    SELECT
+        rs.rsecpolname AS policyname,
+        (SELECT relname FROM pg_catalog.pg_class WHERE oid = rs.rsecrelid) AS tablename,
+        CASE
+            WHEN rs.rsecroles = '{0}' THEN
+                string_to_array('public', '')
+            ELSE
+                ARRAY
+                (
+                    SELECT rolname
+                    FROM pg_catalog.pg_authid
+                    WHERE oid = ANY (rs.rsecroles) ORDER BY 1
+                )
+        END AS roles,
+		CASE WHEN rs.rseccmd IS NULL THEN 'ALL' ELSE
+			CASE rs.rseccmd
+                WHEN 'r' THEN 'SELECT'
+                WHEN 'a' THEN 'INSERT'
+                WHEN 'u' THEN 'UPDATE'
+                WHEN 'd' THEN 'DELETE'
+            END
+        END AS cmd,
+        pg_catalog.pg_get_expr(rs.rsecqual, rs.rsecrelid) AS qual,
+        pg_catalog.pg_get_expr(rs.rsecwithcheck, rs.rsecrelid) AS with_check
+    FROM pg_catalog.pg_rowsecurity rs
+    ORDER BY 1;
+
 CREATE VIEW pg_rules AS
     SELECT
         N.nspname AS schemaname,
@@ -89,7 +118,8 @@ CREATE VIEW pg_tables AS
         T.spcname AS tablespace,
         C.relhasindex AS hasindexes,
         C.relhasrules AS hasrules,
-        C.relhastriggers AS hastriggers
+        C.relhastriggers AS hastriggers,
+        C.relhasrowsecurity AS hasrowsecurity
     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/Makefile b/src/backend/commands/Makefile
index 22f116b78df2d04d3c63eac17bd93f9740c75087..b1ac704886feb047a8f6e89e4d3fbc9b1569fde9 100644
--- a/src/backend/commands/Makefile
+++ b/src/backend/commands/Makefile
@@ -17,7 +17,7 @@ OBJS = aggregatecmds.o alter.o analyze.o async.o cluster.o comment.o  \
 	dbcommands.o define.o discard.o dropcmds.o \
 	event_trigger.o explain.o extension.o foreigncmds.o functioncmds.o \
 	indexcmds.o lockcmds.o matview.o operatorcmds.o opclasscmds.o \
-	portalcmds.o prepare.o proclang.o \
+	policy.o portalcmds.o prepare.o proclang.o \
 	schemacmds.o seclabel.o sequence.o tablecmds.o tablespace.o trigger.o \
 	tsearchcmds.o typecmds.o user.o vacuum.o vacuumlazy.o \
 	variable.o view.o
diff --git a/src/backend/commands/alter.c b/src/backend/commands/alter.c
index 80c9743a0d509f97a074c0a03279df14c2a297da..c9a9bafef7418a586ac51db2d9ba605331a6ad41 100644
--- a/src/backend/commands/alter.c
+++ b/src/backend/commands/alter.c
@@ -43,6 +43,7 @@
 #include "commands/defrem.h"
 #include "commands/event_trigger.h"
 #include "commands/extension.h"
+#include "commands/policy.h"
 #include "commands/proclang.h"
 #include "commands/schemacmds.h"
 #include "commands/tablecmds.h"
@@ -338,6 +339,9 @@ ExecRenameStmt(RenameStmt *stmt)
 		case OBJECT_TRIGGER:
 			return renametrig(stmt);
 
+		case OBJECT_POLICY:
+			return rename_policy(stmt);
+
 		case OBJECT_DOMAIN:
 		case OBJECT_TYPE:
 			return RenameType(stmt);
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index fbd7492a73f659892cc3a68b5f5cc39c4d2cd728..6b8357634a74f03a3d96871772ac031697c1b9fc 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -37,7 +37,9 @@
 #include "optimizer/clauses.h"
 #include "optimizer/planner.h"
 #include "parser/parse_relation.h"
+#include "nodes/makefuncs.h"
 #include "rewrite/rewriteHandler.h"
+#include "rewrite/rowsecurity.h"
 #include "storage/fd.h"
 #include "tcop/tcopprot.h"
 #include "utils/acl.h"
@@ -784,6 +786,7 @@ DoCopy(const CopyStmt *stmt, const char *queryString, uint64 *processed)
 	bool		pipe = (stmt->filename == NULL);
 	Relation	rel;
 	Oid			relid;
+	Node	   *query = NULL;
 
 	/* Disallow COPY to/from file or program except to superusers. */
 	if (!pipe && !superuser())
@@ -837,11 +840,72 @@ DoCopy(const CopyStmt *stmt, const char *queryString, uint64 *processed)
 				rte->selectedCols = bms_add_member(rte->selectedCols, attno);
 		}
 		ExecCheckRTPerms(list_make1(rte), true);
+
+		/*
+		 * Permission check for row security.
+		 *
+		 * check_enable_rls will ereport(ERROR) if the user has requested
+		 * something invalid and will otherwise indicate if we should enable
+		 * RLS (returns RLS_ENABLED) or not for this COPY statement.
+		 *
+		 * If the relation has a row security policy and we are to apply it
+		 * then perform a "query" copy and allow the normal query processing to
+		 * handle the policies.
+		 *
+		 * If RLS is not enabled for this, then just fall through to the
+		 * normal non-filtering relation handling.
+		 */
+		if (check_enable_rls(rte->relid, InvalidOid) == RLS_ENABLED)
+		{
+			SelectStmt *select;
+			ColumnRef  *cr;
+			ResTarget  *target;
+			RangeVar   *from;
+
+			if (is_from)
+				ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("COPY FROM not supported with row security."),
+						 errhint("Use direct INSERT statements instead.")));
+
+			/* Build target list */
+			cr = makeNode(ColumnRef);
+
+			if (!stmt->attlist)
+				cr->fields = list_make1(makeNode(A_Star));
+			else
+				cr->fields = stmt->attlist;
+
+			cr->location = 1;
+
+			target = makeNode(ResTarget);
+			target->name = NULL;
+			target->indirection = NIL;
+			target->val = (Node *) cr;
+			target->location = 1;
+
+			/* Build FROM clause */
+			from = makeRangeVar(NULL, RelationGetRelationName(rel), 1);
+
+			/* Build query */
+			select = makeNode(SelectStmt);
+			select->targetList = list_make1(target);
+			select->fromClause = list_make1(from);
+
+			query = (Node*) select;
+
+			relid = InvalidOid;
+
+			/* Close the handle to the relation as it is no longer needed. */
+			heap_close(rel, (is_from ? RowExclusiveLock : AccessShareLock));
+			rel = NULL;
+		}
 	}
 	else
 	{
 		Assert(stmt->query);
 
+		query = stmt->query;
 		relid = InvalidOid;
 		rel = NULL;
 	}
@@ -861,7 +925,7 @@ DoCopy(const CopyStmt *stmt, const char *queryString, uint64 *processed)
 	}
 	else
 	{
-		cstate = BeginCopyTo(rel, stmt->query, queryString,
+		cstate = BeginCopyTo(rel, query, queryString,
 							 stmt->filename, stmt->is_program,
 							 stmt->attlist, stmt->options);
 		*processed = DoCopyTo(cstate);	/* copy from database to file */
diff --git a/src/backend/commands/createas.c b/src/backend/commands/createas.c
index 52451716f4612d5cc63767f4a382470123d582c9..e381c06e67f2e30ab4785277e989db48650ab43e 100644
--- a/src/backend/commands/createas.c
+++ b/src/backend/commands/createas.c
@@ -36,6 +36,7 @@
 #include "miscadmin.h"
 #include "parser/parse_clause.h"
 #include "rewrite/rewriteHandler.h"
+#include "rewrite/rowsecurity.h"
 #include "storage/smgr.h"
 #include "tcop/tcopprot.h"
 #include "utils/builtins.h"
@@ -419,6 +420,19 @@ intorel_startup(DestReceiver *self, int operation, TupleDesc typeinfo)
 
 	ExecCheckRTPerms(list_make1(rte), true);
 
+	/*
+	 * Make sure the constructed table does not have RLS enabled.
+	 *
+	 * check_enable_rls() will ereport(ERROR) itself if the user has requested
+	 * something invalid, and otherwise will return RLS_ENABLED if RLS should
+	 * be enabled here.  We don't actually support that currently, so throw
+	 * our own ereport(ERROR) if that happens.
+	 */
+	if (check_enable_rls(intoRelationId, InvalidOid) == RLS_ENABLED)
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 (errmsg("policies not yet implemented for this command"))));
+
 	/*
 	 * Tentatively mark the target as populated, if it's a matview and we're
 	 * going to fill it; otherwise, no change needed.
diff --git a/src/backend/commands/dropcmds.c b/src/backend/commands/dropcmds.c
index e64ad8027e22f9455063b6b9b87b74edee9109ba..858358166d9ab0db7e15a51067e133b51cd1cdeb 100644
--- a/src/backend/commands/dropcmds.c
+++ b/src/backend/commands/dropcmds.c
@@ -371,6 +371,15 @@ does_not_exist_skipping(ObjectType objtype, List *objname, List *objargs)
 												  list_length(objname) - 1));
 			}
 			break;
+		case OBJECT_POLICY:
+			if (!owningrel_does_not_exist_skipping(objname, &msg, &name))
+			{
+				msg = gettext_noop("policy \"%s\" for relation \"%s\" does not exist, skipping");
+				name = strVal(llast(objname));
+				args = NameListToString(list_truncate(list_copy(objname),
+												  list_length(objname) - 1));
+			}
+			break;
 		case OBJECT_EVENT_TRIGGER:
 			msg = gettext_noop("event trigger \"%s\" does not exist, skipping");
 			name = NameListToString(objname);
diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
index 754264eb3eea0b9ebbae51d3aeae943451703ffd..1b8c94bcfdf91668a42eb317ac33c1ba8fd8c48e 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -85,6 +85,7 @@ static event_trigger_support_data event_trigger_support[] = {
 	{"OPERATOR", true},
 	{"OPERATOR CLASS", true},
 	{"OPERATOR FAMILY", true},
+	{"POLICY", true},
 	{"ROLE", false},
 	{"RULE", true},
 	{"SCHEMA", true},
@@ -936,6 +937,7 @@ EventTriggerSupportsObjectType(ObjectType obtype)
 		case OBJECT_OPCLASS:
 		case OBJECT_OPERATOR:
 		case OBJECT_OPFAMILY:
+		case OBJECT_POLICY:
 		case OBJECT_RULE:
 		case OBJECT_SCHEMA:
 		case OBJECT_SEQUENCE:
@@ -995,6 +997,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/policy.c b/src/backend/commands/policy.c
new file mode 100644
index 0000000000000000000000000000000000000000..2f4df4890212dec36a6dbafaae33fc99a661500f
--- /dev/null
+++ b/src/backend/commands/policy.c
@@ -0,0 +1,988 @@
+/*-------------------------------------------------------------------------
+ *
+ * policy.c
+ *	  Commands for manipulating policies.
+ *
+ * Portions Copyright (c) 1996-2014, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/backend/commands/policy.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "access/genam.h"
+#include "access/heapam.h"
+#include "access/htup.h"
+#include "access/htup_details.h"
+#include "access/sysattr.h"
+#include "catalog/catalog.h"
+#include "catalog/dependency.h"
+#include "catalog/indexing.h"
+#include "catalog/namespace.h"
+#include "catalog/objectaccess.h"
+#include "catalog/pg_rowsecurity.h"
+#include "catalog/pg_type.h"
+#include "commands/policy.h"
+#include "miscadmin.h"
+#include "nodes/makefuncs.h"
+#include "nodes/pg_list.h"
+#include "optimizer/clauses.h"
+#include "parser/parse_clause.h"
+#include "parser/parse_node.h"
+#include "parser/parse_relation.h"
+#include "storage/lock.h"
+#include "utils/acl.h"
+#include "utils/array.h"
+#include "utils/builtins.h"
+#include "utils/fmgroids.h"
+#include "utils/inval.h"
+#include "utils/lsyscache.h"
+#include "utils/memutils.h"
+#include "utils/rel.h"
+#include "utils/syscache.h"
+
+static void RangeVarCallbackForPolicy(const RangeVar *rv,
+				Oid relid, Oid oldrelid, void *arg);
+static const char parse_row_security_command(const char *cmd_name);
+static ArrayType* rls_role_list_to_array(List *roles);
+
+/*
+ * Callback to RangeVarGetRelidExtended().
+ *
+ * Checks the following:
+ *  - the relation specified is a table.
+ *  - current user owns the table.
+ *  - the table is not a system table.
+ *
+ * If any of these checks fails then an error is raised.
+ */
+static void
+RangeVarCallbackForPolicy(const RangeVar *rv, Oid relid, Oid oldrelid,
+								void *arg)
+{
+	HeapTuple		tuple;
+	Form_pg_class	classform;
+	char			relkind;
+
+	tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relid));
+	if (!HeapTupleIsValid(tuple))
+		return;
+
+	classform = (Form_pg_class) GETSTRUCT(tuple);
+	relkind = classform->relkind;
+
+	/* Must own relation. */
+	if (!pg_class_ownercheck(relid, GetUserId()))
+		aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CLASS, rv->relname);
+
+	/* No system table modifications unless explicitly allowed. */
+	if (!allowSystemTableMods && IsSystemClass(relid, classform))
+		ereport(ERROR,
+				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+				 errmsg("permission denied: \"%s\" is a system catalog",
+						rv->relname)));
+
+	/* Relation type MUST be a table. */
+	if (relkind != RELKIND_RELATION)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("\"%s\" is not a table", rv->relname)));
+
+	ReleaseSysCache(tuple);
+}
+
+/*
+ * parse_row_security_command -
+ *   helper function to convert full command strings to their char
+ *   representation.
+ *
+ * cmd_name - full string command name. Valid values are 'all', 'select',
+ *			  'insert', 'update' and 'delete'.
+ *
+ */
+static const char
+parse_row_security_command(const char *cmd_name)
+{
+	char cmd;
+
+	if (!cmd_name)
+		elog(ERROR, "Unregonized command.");
+
+	if (strcmp(cmd_name, "all") == 0)
+		cmd = 0;
+	else if (strcmp(cmd_name, "select") == 0)
+		cmd = ACL_SELECT_CHR;
+	else if (strcmp(cmd_name, "insert") == 0)
+		cmd = ACL_INSERT_CHR;
+	else if (strcmp(cmd_name, "update") == 0)
+		cmd = ACL_UPDATE_CHR;
+	else if (strcmp(cmd_name, "delete") == 0)
+		cmd = ACL_DELETE_CHR;
+	else
+		elog(ERROR, "Unregonized command.");
+		/* error unrecognized command */
+
+	return cmd;
+}
+
+/*
+ * rls_role_list_to_array
+ *   helper function to convert a list of role names in to an array of
+ *   role ids.
+ *
+ * Note: If PUBLIC is provided as a role name, then ACL_ID_PUBLIC is
+ *       used as the role id.
+ *
+ * roles - the list of role names to convert.
+ */
+static ArrayType *
+rls_role_list_to_array(List *roles)
+{
+	ArrayType  *role_ids;
+	Datum	   *temp_array;
+	ListCell   *cell;
+	int			num_roles;
+	int			i = 0;
+
+	/* Handle no roles being passed in as being for public */
+	if (roles == NIL)
+	{
+		temp_array = (Datum *) palloc(sizeof(Datum));
+		temp_array[0] = ObjectIdGetDatum(ACL_ID_PUBLIC);
+
+		role_ids = construct_array(temp_array, 1, OIDOID, sizeof(Oid), true,
+								   'i');
+		return role_ids;
+	}
+
+	num_roles = list_length(roles);
+	temp_array = (Datum *) palloc(num_roles * sizeof(Datum));
+
+	foreach(cell, roles)
+	{
+		Oid		roleid = get_role_oid_or_public(strVal(lfirst(cell)));
+
+		/*
+		 * PUBLIC covers all roles, so it only makes sense alone.
+		 */
+		if (roleid == ACL_ID_PUBLIC)
+		{
+			if (num_roles != 1)
+				ereport(WARNING,
+						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						 errmsg("ignoring roles specified other than public"),
+						 errhint("All roles are members of the public role.")));
+
+			temp_array[0] = ObjectIdGetDatum(roleid);
+			num_roles = 1;
+			break;
+		}
+		else
+			temp_array[i++] = ObjectIdGetDatum(roleid);
+	}
+
+	role_ids = construct_array(temp_array, num_roles, OIDOID, sizeof(Oid), true,
+							   'i');
+
+	return role_ids;
+}
+
+/*
+ * Load row-security policy from the catalog, and keep it in
+ * the relation cache.
+ */
+void
+RelationBuildRowSecurity(Relation relation)
+{
+	Relation			catalog;
+	ScanKeyData			skey;
+	SysScanDesc			sscan;
+	HeapTuple			tuple;
+	MemoryContext		oldcxt;
+	MemoryContext		rscxt = NULL;
+	RowSecurityDesc	   *rsdesc = NULL;
+
+	catalog = heap_open(RowSecurityRelationId, AccessShareLock);
+
+	ScanKeyInit(&skey,
+				Anum_pg_rowsecurity_rsecrelid,
+				BTEqualStrategyNumber, F_OIDEQ,
+				ObjectIdGetDatum(RelationGetRelid(relation)));
+
+	sscan = systable_beginscan(catalog, RowSecurityRelidPolnameIndexId, true,
+							   NULL, 1, &skey);
+	PG_TRY();
+	{
+		/*
+		 * Set up our memory context- we will always set up some kind of
+		 * policy here.  If no explicit policies are found then an implicit
+		 * default-deny policy is created.
+		 */
+		rscxt = AllocSetContextCreate(CacheMemoryContext,
+									  "Row-security descriptor",
+									  ALLOCSET_SMALL_MINSIZE,
+									  ALLOCSET_SMALL_INITSIZE,
+									  ALLOCSET_SMALL_MAXSIZE);
+		rsdesc = MemoryContextAllocZero(rscxt, sizeof(RowSecurityDesc));
+		rsdesc->rscxt = rscxt;
+
+		/*
+		 * Loop through the row-level security entries for this relation, if
+		 * any.
+		 */
+		while (HeapTupleIsValid(tuple = systable_getnext(sscan)))
+		{
+			Datum				value_datum;
+			char				cmd_value;
+			ArrayType		   *roles;
+			char			   *qual_value;
+			Expr			   *qual_expr;
+			char			   *with_check_value;
+			Expr			   *with_check_qual;
+			char			   *policy_name_value;
+			Oid					policy_id;
+			bool				isnull;
+			RowSecurityPolicy  *policy = NULL;
+
+			oldcxt = MemoryContextSwitchTo(rscxt);
+
+			/* Get policy command */
+			value_datum = heap_getattr(tuple, Anum_pg_rowsecurity_rseccmd,
+								 RelationGetDescr(catalog), &isnull);
+			if (isnull)
+				cmd_value = 0;
+			else
+				cmd_value = DatumGetChar(value_datum);
+
+			/* Get policy name */
+			value_datum = heap_getattr(tuple, Anum_pg_rowsecurity_rsecpolname,
+										RelationGetDescr(catalog), &isnull);
+			Assert(!isnull);
+			policy_name_value = DatumGetCString(value_datum);
+
+			/* Get policy roles */
+			value_datum = heap_getattr(tuple, Anum_pg_rowsecurity_rsecroles,
+										RelationGetDescr(catalog), &isnull);
+			Assert(!isnull);
+			roles = DatumGetArrayTypeP(value_datum);
+
+			/* Get policy qual */
+			value_datum = heap_getattr(tuple, Anum_pg_rowsecurity_rsecqual,
+								 RelationGetDescr(catalog), &isnull);
+			if (!isnull)
+			{
+				qual_value = TextDatumGetCString(value_datum);
+				qual_expr = (Expr *) stringToNode(qual_value);
+			}
+			else
+				qual_expr = NULL;
+
+			/* Get WITH CHECK qual */
+			value_datum = heap_getattr(tuple, Anum_pg_rowsecurity_rsecwithcheck,
+										RelationGetDescr(catalog), &isnull);
+
+			if (!isnull)
+			{
+				with_check_value = TextDatumGetCString(value_datum);
+				with_check_qual = (Expr *) stringToNode(with_check_value);
+			}
+			else
+				with_check_qual = NULL;
+
+			policy_id = HeapTupleGetOid(tuple);
+
+			policy = palloc0(sizeof(RowSecurityPolicy));
+			policy->policy_name = policy_name_value;
+			policy->rsecid = policy_id;
+			policy->cmd = cmd_value;
+			policy->roles = roles;
+			policy->qual = copyObject(qual_expr);
+			policy->with_check_qual = copyObject(with_check_qual);
+			policy->hassublinks = contain_subplans((Node *) qual_expr) ||
+								  contain_subplans((Node *) with_check_qual);
+
+			rsdesc->policies = lcons(policy, rsdesc->policies);
+
+			MemoryContextSwitchTo(oldcxt);
+
+			if (qual_expr != NULL)
+				pfree(qual_expr);
+
+			if (with_check_qual != NULL)
+				pfree(with_check_qual);
+		}
+
+		/*
+		 * Check if no policies were added
+		 *
+		 * If no policies exist in pg_rowsecurity for this relation, then we
+		 * need to create a single default-deny policy.  We use InvalidOid for
+		 * the Oid to indicate that this is the default-deny policy (we may
+		 * decide to ignore the default policy if an extension adds policies).
+		 */
+		if (rsdesc->policies == NIL)
+		{
+			RowSecurityPolicy  *policy = NULL;
+			Datum				role;
+
+			oldcxt = MemoryContextSwitchTo(rscxt);
+
+			role = ObjectIdGetDatum(ACL_ID_PUBLIC);
+
+			policy = palloc0(sizeof(RowSecurityPolicy));
+			policy->policy_name = pstrdup("default-deny policy");
+			policy->rsecid = InvalidOid;
+			policy->cmd = '\0';
+			policy->roles = construct_array(&role, 1, OIDOID, sizeof(Oid), true,
+											'i');
+			policy->qual = (Expr *) makeConst(BOOLOID, -1, InvalidOid,
+											  sizeof(bool), BoolGetDatum(false),
+											  false, true);
+			policy->with_check_qual = copyObject(policy->qual);
+			policy->hassublinks = false;
+
+			rsdesc->policies = lcons(policy, rsdesc->policies);
+
+			MemoryContextSwitchTo(oldcxt);
+		}
+	}
+	PG_CATCH();
+	{
+		if (rscxt != NULL)
+			MemoryContextDelete(rscxt);
+		PG_RE_THROW();
+	}
+	PG_END_TRY();
+
+	systable_endscan(sscan);
+	heap_close(catalog, AccessShareLock);
+
+	relation->rsdesc = rsdesc;
+}
+
+/*
+ * RemovePolicyById -
+ *   remove a row-security policy by its OID.  If a policy does not exist with
+ *   the provided oid, then an error is raised.
+ *
+ * policy_id - the oid of the row-security policy.
+ */
+void
+RemovePolicyById(Oid policy_id)
+{
+	Relation 	pg_rowsecurity_rel;
+	SysScanDesc sscan;
+	ScanKeyData skey[1];
+	HeapTuple	tuple;
+	Oid			relid;
+	Relation	rel;
+
+	pg_rowsecurity_rel = heap_open(RowSecurityRelationId, RowExclusiveLock);
+
+	/*
+	 * Find the policy to delete.
+	 */
+	ScanKeyInit(&skey[0],
+				ObjectIdAttributeNumber,
+				BTEqualStrategyNumber, F_OIDEQ,
+				ObjectIdGetDatum(policy_id));
+
+	sscan = systable_beginscan(pg_rowsecurity_rel, RowSecurityOidIndexId, true,
+							   NULL, 1, skey);
+
+	tuple = systable_getnext(sscan);
+
+	/* If the policy exists, then remove it, otherwise raise an error. */
+	if (!HeapTupleIsValid(tuple))
+		elog(ERROR, "could not find tuple for row-security %u", policy_id);
+
+	/*
+	 * Open and exclusive-lock the relation the policy belong to.
+	 */
+	relid = ((Form_pg_rowsecurity) GETSTRUCT(tuple))->rsecrelid;
+
+	rel = heap_open(relid, AccessExclusiveLock);
+	if (rel->rd_rel->relkind != RELKIND_RELATION)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("\"%s\" is not a table",
+						RelationGetRelationName(rel))));
+
+	if (!allowSystemTableMods && IsSystemRelation(rel))
+		ereport(ERROR,
+				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+				 errmsg("permission denied: \"%s\" is a system catalog",
+						RelationGetRelationName(rel))));
+
+	simple_heap_delete(pg_rowsecurity_rel, &tuple->t_self);
+
+	systable_endscan(sscan);
+	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
+	 * 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
+	 * policy is created and all records are filtered (except for queries from
+	 * the owner).
+	 */
+
+	CacheInvalidateRelcache(rel);
+
+	/* Clean up */
+	heap_close(pg_rowsecurity_rel, RowExclusiveLock);
+}
+
+/*
+ * CreatePolicy -
+ *   handles the execution of the CREATE POLICY command.
+ *
+ * stmt - the CreatePolicyStmt that describes the policy to create.
+ */
+Oid
+CreatePolicy(CreatePolicyStmt *stmt)
+{
+	Relation		pg_rowsecurity_rel;
+	Oid				rowsec_id;
+	Relation		target_table;
+	Oid				table_id;
+	char			rseccmd;
+	ArrayType	   *role_ids;
+	ParseState	   *qual_pstate;
+	ParseState	   *with_check_pstate;
+	RangeTblEntry  *rte;
+	Node		   *qual;
+	Node		   *with_check_qual;
+	ScanKeyData		skey[2];
+	SysScanDesc		sscan;
+	HeapTuple		rsec_tuple;
+	Datum			values[Natts_pg_rowsecurity];
+	bool			isnull[Natts_pg_rowsecurity];
+	ObjectAddress	target;
+	ObjectAddress	myself;
+
+	/* Parse command */
+	rseccmd = parse_row_security_command(stmt->cmd);
+
+	/*
+	 * If the command is SELECT or DELETE then WITH CHECK should be NULL.
+	 */
+	if ((rseccmd == ACL_SELECT_CHR || rseccmd == ACL_DELETE_CHR)
+		&& stmt->with_check != NULL)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("WITH CHECK cannot be applied to SELECT or DELETE")));
+
+	/*
+	 * If the command is INSERT then WITH CHECK should be the only expression
+	 * provided.
+	 */
+	if (rseccmd == ACL_INSERT_CHR && stmt->qual != NULL)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("Only WITH CHECK expression allowed for INSERT")));
+
+
+	/* Collect role ids */
+	role_ids = rls_role_list_to_array(stmt->roles);
+
+	/* Parse the supplied clause */
+	qual_pstate = make_parsestate(NULL);
+	with_check_pstate = make_parsestate(NULL);
+
+	/* zero-clear */
+	memset(values,   0, sizeof(values));
+	memset(isnull,   0, sizeof(isnull));
+
+	/* Get id of table.  Also handles permissions checks. */
+	table_id = RangeVarGetRelidExtended(stmt->table, AccessExclusiveLock,
+										false, false,
+										RangeVarCallbackForPolicy,
+										(void *) stmt);
+
+	/* Open target_table to build quals. No lock is necessary.*/
+	target_table = relation_open(table_id, NoLock);
+
+	/* Add for the regular security quals */
+	rte = addRangeTableEntryForRelation(qual_pstate, target_table,
+										NULL, false, false);
+	addRTEtoQuery(qual_pstate, rte, false, true, true);
+
+	/* Add for the with-check quals */
+	rte = addRangeTableEntryForRelation(with_check_pstate, target_table,
+										NULL, false, false);
+	addRTEtoQuery(with_check_pstate, rte, false, true, true);
+
+	qual = transformWhereClause(qual_pstate,
+								copyObject(stmt->qual),
+								EXPR_KIND_WHERE,
+								"POLICY");
+
+	with_check_qual = transformWhereClause(with_check_pstate,
+								copyObject(stmt->with_check),
+								EXPR_KIND_WHERE,
+								"POLICY");
+
+	/* Open pg_rowsecurity catalog */
+	pg_rowsecurity_rel = heap_open(RowSecurityRelationId, RowExclusiveLock);
+
+	/* Set key - row security relation id. */
+	ScanKeyInit(&skey[0],
+				Anum_pg_rowsecurity_rsecrelid,
+				BTEqualStrategyNumber, F_OIDEQ,
+				ObjectIdGetDatum(table_id));
+
+	/* Set key - row security policy name. */
+	ScanKeyInit(&skey[1],
+				Anum_pg_rowsecurity_rsecpolname,
+				BTEqualStrategyNumber, F_NAMEEQ,
+				CStringGetDatum(stmt->policy_name));
+
+	sscan = systable_beginscan(pg_rowsecurity_rel,
+							   RowSecurityRelidPolnameIndexId, true, NULL, 2,
+							   skey);
+
+	rsec_tuple = systable_getnext(sscan);
+
+	/* Complain if the policy name already exists for the table */
+	if (HeapTupleIsValid(rsec_tuple))
+		ereport(ERROR,
+				(errcode(ERRCODE_DUPLICATE_OBJECT),
+				 errmsg("policy \"%s\" for relation \"%s\" already exists",
+				 stmt->policy_name, RelationGetRelationName(target_table))));
+
+	values[Anum_pg_rowsecurity_rsecrelid - 1] = ObjectIdGetDatum(table_id);
+	values[Anum_pg_rowsecurity_rsecpolname - 1]
+		= CStringGetDatum(stmt->policy_name);
+
+	if (rseccmd)
+		values[Anum_pg_rowsecurity_rseccmd - 1] = CharGetDatum(rseccmd);
+	else
+		isnull[Anum_pg_rowsecurity_rseccmd - 1] = true;
+
+	values[Anum_pg_rowsecurity_rsecroles - 1] = PointerGetDatum(role_ids);
+
+	/* Add qual if present. */
+	if (qual)
+		values[Anum_pg_rowsecurity_rsecqual - 1]
+			= CStringGetTextDatum(nodeToString(qual));
+	else
+		isnull[Anum_pg_rowsecurity_rsecqual - 1] = true;
+
+	/* Add WITH CHECK qual if present */
+	if (with_check_qual)
+		values[Anum_pg_rowsecurity_rsecwithcheck - 1]
+			= CStringGetTextDatum(nodeToString(with_check_qual));
+	else
+		isnull[Anum_pg_rowsecurity_rsecwithcheck - 1] = true;
+
+	rsec_tuple = heap_form_tuple(RelationGetDescr(pg_rowsecurity_rel), values,
+								 isnull);
+
+	rowsec_id = simple_heap_insert(pg_rowsecurity_rel, rsec_tuple);
+
+	/* Update Indexes */
+	CatalogUpdateIndexes(pg_rowsecurity_rel, rsec_tuple);
+
+	/* Record Dependencies */
+	target.classId = RelationRelationId;
+	target.objectId = table_id;
+	target.objectSubId = 0;
+
+	myself.classId = RowSecurityRelationId;
+	myself.objectId = rowsec_id;
+	myself.objectSubId = 0;
+
+	recordDependencyOn(&myself, &target, DEPENDENCY_AUTO);
+
+	recordDependencyOnExpr(&myself, qual, qual_pstate->p_rtable,
+						   DEPENDENCY_NORMAL);
+
+	recordDependencyOnExpr(&myself, with_check_qual,
+						   with_check_pstate->p_rtable, DEPENDENCY_NORMAL);
+
+	/* Invalidate Relation Cache */
+	CacheInvalidateRelcache(target_table);
+
+	/* Clean up. */
+	heap_freetuple(rsec_tuple);
+	free_parsestate(qual_pstate);
+	free_parsestate(with_check_pstate);
+	systable_endscan(sscan);
+	relation_close(target_table, NoLock);
+	heap_close(pg_rowsecurity_rel, RowExclusiveLock);
+
+	return rowsec_id;
+}
+
+/*
+ * AlterPolicy -
+ *   handles the execution of the ALTER POLICY command.
+ *
+ * stmt - the AlterPolicyStmt that describes the policy and how to alter it.
+ */
+Oid
+AlterPolicy(AlterPolicyStmt *stmt)
+{
+	Relation		pg_rowsecurity_rel;
+	Oid				rowsec_id;
+	Relation		target_table;
+	Oid				table_id;
+	ArrayType	   *role_ids = NULL;
+	List		   *qual_parse_rtable = NIL;
+	List		   *with_check_parse_rtable = NIL;
+	Node		   *qual = NULL;
+	Node		   *with_check_qual = NULL;
+	ScanKeyData		skey[2];
+	SysScanDesc		sscan;
+	HeapTuple		rsec_tuple;
+	HeapTuple		new_tuple;
+	Datum			values[Natts_pg_rowsecurity];
+	bool			isnull[Natts_pg_rowsecurity];
+	bool			replaces[Natts_pg_rowsecurity];
+	ObjectAddress	target;
+	ObjectAddress	myself;
+	Datum			cmd_datum;
+	char			rseccmd;
+	bool			rseccmd_isnull;
+
+	/* Parse role_ids */
+	if (stmt->roles != NULL)
+		role_ids = rls_role_list_to_array(stmt->roles);
+
+	/* Get id of table.  Also handles permissions checks. */
+	table_id = RangeVarGetRelidExtended(stmt->table, AccessExclusiveLock,
+										false, false,
+										RangeVarCallbackForPolicy,
+										(void *) stmt);
+
+	target_table = relation_open(table_id, NoLock);
+
+	/* Parse the row-security clause */
+	if (stmt->qual)
+	{
+		RangeTblEntry  *rte;
+		ParseState	   *qual_pstate = make_parsestate(NULL);
+
+		rte = addRangeTableEntryForRelation(qual_pstate, target_table,
+											NULL, false, false);
+
+		addRTEtoQuery(qual_pstate, rte, false, true, true);
+
+		qual = transformWhereClause(qual_pstate, copyObject(stmt->qual),
+									EXPR_KIND_WHERE,
+									"ROW SECURITY");
+
+		qual_parse_rtable = qual_pstate->p_rtable;
+		free_parsestate(qual_pstate);
+	}
+
+	/* Parse the with-check row-security clause */
+	if (stmt->with_check)
+	{
+		RangeTblEntry  *rte;
+		ParseState	   *with_check_pstate = make_parsestate(NULL);
+
+		rte = addRangeTableEntryForRelation(with_check_pstate, target_table,
+											NULL, false, false);
+
+		addRTEtoQuery(with_check_pstate, rte, false, true, true);
+
+		with_check_qual = transformWhereClause(with_check_pstate,
+											   copyObject(stmt->with_check),
+											   EXPR_KIND_WHERE,
+											   "ROW SECURITY");
+
+		with_check_parse_rtable = with_check_pstate->p_rtable;
+		free_parsestate(with_check_pstate);
+	}
+
+	/* zero-clear */
+	memset(values,   0, sizeof(values));
+	memset(replaces, 0, sizeof(replaces));
+	memset(isnull,   0, sizeof(isnull));
+
+	/* Find policy to update. */
+	pg_rowsecurity_rel = heap_open(RowSecurityRelationId, RowExclusiveLock);
+
+	/* Set key - row security relation id. */
+	ScanKeyInit(&skey[0],
+				Anum_pg_rowsecurity_rsecrelid,
+				BTEqualStrategyNumber, F_OIDEQ,
+				ObjectIdGetDatum(table_id));
+
+	/* Set key - row security policy name. */
+	ScanKeyInit(&skey[1],
+				Anum_pg_rowsecurity_rsecpolname,
+				BTEqualStrategyNumber, F_NAMEEQ,
+				CStringGetDatum(stmt->policy_name));
+
+	sscan = systable_beginscan(pg_rowsecurity_rel,
+							   RowSecurityRelidPolnameIndexId, true, NULL, 2,
+							   skey);
+
+	rsec_tuple = systable_getnext(sscan);
+
+	/* Check that the policy is found, raise an error if not. */
+	if (!HeapTupleIsValid(rsec_tuple))
+		ereport(ERROR,
+				(errcode(ERRCODE_UNDEFINED_OBJECT),
+				 errmsg("policy '%s' for does not exist on table %s",
+						stmt->policy_name,
+						RelationGetRelationName(target_table))));
+
+	/* Get policy command */
+	cmd_datum = heap_getattr(rsec_tuple, Anum_pg_rowsecurity_rseccmd,
+							 RelationGetDescr(pg_rowsecurity_rel),
+							 &rseccmd_isnull);
+	if (rseccmd_isnull)
+		rseccmd = 0;
+	else
+		rseccmd = DatumGetChar(cmd_datum);
+
+	/*
+	 * If the command is SELECT or DELETE then WITH CHECK should be NULL.
+	 */
+	if ((rseccmd == ACL_SELECT_CHR || rseccmd == ACL_DELETE_CHR)
+		&& stmt->with_check != NULL)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("only USING expression allowed for SELECT, DELETE")));
+
+	/*
+	 * If the command is INSERT then WITH CHECK should be the only
+	 * expression provided.
+	 */
+	if ((rseccmd == ACL_INSERT_CHR)
+		&& stmt->qual != NULL)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("only WITH CHECK expression allowed for INSERT")));
+
+	rowsec_id = HeapTupleGetOid(rsec_tuple);
+
+	if (role_ids != NULL)
+	{
+		replaces[Anum_pg_rowsecurity_rsecroles - 1] = true;
+		values[Anum_pg_rowsecurity_rsecroles - 1] = PointerGetDatum(role_ids);
+	}
+
+	if (qual != NULL)
+	{
+		replaces[Anum_pg_rowsecurity_rsecqual - 1] = true;
+		values[Anum_pg_rowsecurity_rsecqual - 1]
+			= CStringGetTextDatum(nodeToString(qual));
+	}
+
+	if (with_check_qual != NULL)
+	{
+		replaces[Anum_pg_rowsecurity_rsecwithcheck - 1] = true;
+		values[Anum_pg_rowsecurity_rsecwithcheck - 1]
+			= CStringGetTextDatum(nodeToString(with_check_qual));
+	}
+
+	new_tuple = heap_modify_tuple(rsec_tuple,
+								  RelationGetDescr(pg_rowsecurity_rel),
+								  values, isnull, replaces);
+	simple_heap_update(pg_rowsecurity_rel, &new_tuple->t_self, new_tuple);
+
+	/* Update Catalog Indexes */
+	CatalogUpdateIndexes(pg_rowsecurity_rel, new_tuple);
+
+	/* Update Dependencies. */
+	deleteDependencyRecordsFor(RowSecurityRelationId, rowsec_id, false);
+
+	/* Record Dependencies */
+	target.classId = RelationRelationId;
+	target.objectId = table_id;
+	target.objectSubId = 0;
+
+	myself.classId = RowSecurityRelationId;
+	myself.objectId = rowsec_id;
+	myself.objectSubId = 0;
+
+	recordDependencyOn(&myself, &target, DEPENDENCY_AUTO);
+
+	recordDependencyOnExpr(&myself, qual, qual_parse_rtable, DEPENDENCY_NORMAL);
+
+	recordDependencyOnExpr(&myself, with_check_qual, with_check_parse_rtable,
+						   DEPENDENCY_NORMAL);
+
+	heap_freetuple(new_tuple);
+
+	/* Invalidate Relation Cache */
+	CacheInvalidateRelcache(target_table);
+
+	/* Clean up. */
+	systable_endscan(sscan);
+	relation_close(target_table, NoLock);
+	heap_close(pg_rowsecurity_rel, RowExclusiveLock);
+
+	return rowsec_id;
+}
+
+/*
+ * rename_policy -
+ *   change the name of a policy on a relation
+ */
+Oid
+rename_policy(RenameStmt *stmt)
+{
+	Relation		pg_rowsecurity_rel;
+	Relation		target_table;
+	Oid				table_id;
+	Oid				opoloid;
+	ScanKeyData		skey[2];
+	SysScanDesc		sscan;
+	HeapTuple		rsec_tuple;
+
+	/* Get id of table.  Also handles permissions checks. */
+	table_id = RangeVarGetRelidExtended(stmt->relation, AccessExclusiveLock,
+										false, false,
+										RangeVarCallbackForPolicy,
+										(void *) stmt);
+
+	target_table = relation_open(table_id, NoLock);
+
+	pg_rowsecurity_rel = heap_open(RowSecurityRelationId, RowExclusiveLock);
+
+	/* First pass- check for conflict */
+
+	/* Add key - row security relation id. */
+	ScanKeyInit(&skey[0],
+				Anum_pg_rowsecurity_rsecrelid,
+				BTEqualStrategyNumber, F_OIDEQ,
+				ObjectIdGetDatum(table_id));
+
+	/* Add key - row security policy name. */
+	ScanKeyInit(&skey[1],
+				Anum_pg_rowsecurity_rsecpolname,
+				BTEqualStrategyNumber, F_NAMEEQ,
+				CStringGetDatum(stmt->newname));
+
+	sscan = systable_beginscan(pg_rowsecurity_rel,
+							   RowSecurityRelidPolnameIndexId, true, NULL, 2,
+							   skey);
+
+	if (HeapTupleIsValid(rsec_tuple = systable_getnext(sscan)))
+		ereport(ERROR,
+				(errcode(ERRCODE_DUPLICATE_OBJECT),
+				 errmsg("row-policy \"%s\" for table \"%s\" already exists",
+						stmt->newname, RelationGetRelationName(target_table))));
+
+	systable_endscan(sscan);
+
+	/* Second pass -- find existing policy and update */
+	/* Add key - row security relation id. */
+	ScanKeyInit(&skey[0],
+				Anum_pg_rowsecurity_rsecrelid,
+				BTEqualStrategyNumber, F_OIDEQ,
+				ObjectIdGetDatum(table_id));
+
+	/* Add key - row security policy name. */
+	ScanKeyInit(&skey[1],
+				Anum_pg_rowsecurity_rsecpolname,
+				BTEqualStrategyNumber, F_NAMEEQ,
+				CStringGetDatum(stmt->subname));
+
+	sscan = systable_beginscan(pg_rowsecurity_rel,
+							   RowSecurityRelidPolnameIndexId, true, NULL, 2,
+							   skey);
+
+	rsec_tuple = systable_getnext(sscan);
+
+	/* Complain if we did not find the policy */
+	if (!HeapTupleIsValid(rsec_tuple))
+		ereport(ERROR,
+				(errcode(ERRCODE_UNDEFINED_OBJECT),
+				 errmsg("row-policy \"%s\" for table \"%s\" does not exist",
+						stmt->subname, RelationGetRelationName(target_table))));
+
+	opoloid = HeapTupleGetOid(rsec_tuple);
+
+	rsec_tuple = heap_copytuple(rsec_tuple);
+
+	namestrcpy(&((Form_pg_rowsecurity) GETSTRUCT(rsec_tuple))->rsecpolname,
+			   stmt->newname);
+
+	simple_heap_update(pg_rowsecurity_rel, &rsec_tuple->t_self, rsec_tuple);
+
+	/* keep system catalog indexes current */
+	CatalogUpdateIndexes(pg_rowsecurity_rel, rsec_tuple);
+
+	InvokeObjectPostAlterHook(RowSecurityRelationId,
+							  HeapTupleGetOid(rsec_tuple), 0);
+
+	/*
+	 * Invalidate relation's relcache entry so that other backends (and
+	 * this one too!) are sent SI message to make them rebuild relcache
+	 * entries.  (Ideally this should happen automatically...)
+	 */
+	CacheInvalidateRelcache(target_table);
+
+	/* Clean up. */
+	systable_endscan(sscan);
+	heap_close(pg_rowsecurity_rel, RowExclusiveLock);
+	relation_close(target_table, NoLock);
+
+	return opoloid;
+}
+
+/*
+ * get_relation_policy_oid - Look up a policy by name to find its OID
+ *
+ * If missing_ok is false, throw an error if policy not found.  If
+ * true, just return InvalidOid.
+ */
+Oid
+get_relation_policy_oid(Oid relid, const char *policy_name, bool missing_ok)
+{
+	Relation		pg_rowsecurity_rel;
+	ScanKeyData		skey[2];
+	SysScanDesc		sscan;
+	HeapTuple		rsec_tuple;
+	Oid				policy_oid;
+
+	pg_rowsecurity_rel = heap_open(RowSecurityRelationId, AccessShareLock);
+
+	/* Add key - row security relation id. */
+	ScanKeyInit(&skey[0],
+				Anum_pg_rowsecurity_rsecrelid,
+				BTEqualStrategyNumber, F_OIDEQ,
+				ObjectIdGetDatum(relid));
+
+	/* Add key - row security policy name. */
+	ScanKeyInit(&skey[1],
+				Anum_pg_rowsecurity_rsecpolname,
+				BTEqualStrategyNumber, F_NAMEEQ,
+				CStringGetDatum(policy_name));
+
+	sscan = systable_beginscan(pg_rowsecurity_rel,
+							   RowSecurityRelidPolnameIndexId, true, NULL, 2,
+							   skey);
+
+	rsec_tuple = systable_getnext(sscan);
+
+	if (!HeapTupleIsValid(rsec_tuple))
+	{
+		if (!missing_ok)
+			ereport(ERROR,
+					(errcode(ERRCODE_UNDEFINED_OBJECT),
+					 errmsg("policy \"%s\" for table  \"%s\" does not exist",
+							policy_name, get_rel_name(relid))));
+
+		policy_oid = InvalidOid;
+	}
+	else
+		policy_oid = HeapTupleGetOid(rsec_tuple);
+
+	/* Clean up. */
+	systable_endscan(sscan);
+	heap_close(pg_rowsecurity_rel, AccessShareLock);
+
+	return policy_oid;
+}
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 7bc579bf4cea08a0ed63812824ff54b80da63d0b..0385404c57804c035f0f98321530c52cadc9c64f 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -36,6 +36,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"
@@ -45,6 +46,7 @@
 #include "commands/cluster.h"
 #include "commands/comment.h"
 #include "commands/defrem.h"
+#include "commands/policy.h"
 #include "commands/sequence.h"
 #include "commands/tablecmds.h"
 #include "commands/tablespace.h"
@@ -408,6 +410,8 @@ static void ATExecAddOf(Relation rel, const TypeName *ofTypename, LOCKMODE lockm
 static void ATExecDropOf(Relation rel, LOCKMODE lockmode);
 static void ATExecReplicaIdentity(Relation rel, ReplicaIdentityStmt *stmt, LOCKMODE lockmode);
 static void ATExecGenericOptions(Relation rel, List *options);
+static void ATExecEnableRowSecurity(Relation rel);
+static void ATExecDisableRowSecurity(Relation rel);
 
 static void copy_relation_data(SMgrRelation rel, SMgrRelation dst,
 				   ForkNumber forkNum, char relpersistence);
@@ -2872,6 +2876,8 @@ AlterTableGetLockLevel(List *cmds)
 			case AT_AddIndexConstraint:
 			case AT_ReplicaIdentity:
 			case AT_SetNotNull:
+			case AT_EnableRowSecurity:
+			case AT_DisableRowSecurity:
 				cmd_lockmode = AccessExclusiveLock;
 				break;
 
@@ -3280,6 +3286,8 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
 		case AT_DropInherit:	/* NO INHERIT */
 		case AT_AddOf:			/* OF */
 		case AT_DropOf: /* NOT OF */
+		case AT_EnableRowSecurity:
+		case AT_DisableRowSecurity:
 			ATSimplePermissions(rel, ATT_TABLE);
 			/* These commands never recurse */
 			/* No command-specific prep needed */
@@ -3571,6 +3579,12 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
 		case AT_ReplicaIdentity:
 			ATExecReplicaIdentity(rel, (ReplicaIdentityStmt *) cmd->def, lockmode);
 			break;
+		case AT_EnableRowSecurity:
+			ATExecEnableRowSecurity(rel);
+			break;
+		case AT_DisableRowSecurity:
+			ATExecDisableRowSecurity(rel);
+			break;
 		case AT_GenericOptions:
 			ATExecGenericOptions(rel, (List *) cmd->def);
 			break;
@@ -10614,6 +10628,62 @@ ATExecReplicaIdentity(Relation rel, ReplicaIdentityStmt *stmt, LOCKMODE lockmode
 	index_close(indexRel, NoLock);
 }
 
+/*
+ * ALTER TABLE ENABLE/DISABLE ROW LEVEL SECURITY
+ */
+static void
+ATExecEnableRowSecurity(Relation rel)
+{
+	Relation		pg_class;
+	Oid				relid;
+	HeapTuple		tuple;
+
+	relid = RelationGetRelid(rel);
+
+	pg_class = heap_open(RelationRelationId, RowExclusiveLock);
+
+	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(pg_class, &tuple->t_self, tuple);
+
+	/* keep catalog indexes current */
+	CatalogUpdateIndexes(pg_class, tuple);
+
+	heap_close(pg_class, RowExclusiveLock);
+	heap_freetuple(tuple);
+}
+
+static void
+ATExecDisableRowSecurity(Relation rel)
+{
+	Relation		pg_class;
+	Oid				relid;
+	HeapTuple		tuple;
+
+	relid = RelationGetRelid(rel);
+
+	/* Pull the record for this relation and update it */
+	pg_class = heap_open(RelationRelationId, RowExclusiveLock);
+
+	tuple = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(relid));
+
+	if (!HeapTupleIsValid(tuple))
+		elog(ERROR, "cache lookup failed for relation %u", relid);
+
+	((Form_pg_class) GETSTRUCT(tuple))->relhasrowsecurity = false;
+	simple_heap_update(pg_class, &tuple->t_self, tuple);
+
+	/* keep catalog indexes current */
+	CatalogUpdateIndexes(pg_class, tuple);
+
+	heap_close(pg_class, RowExclusiveLock);
+	heap_freetuple(tuple);
+}
+
 /*
  * ALTER FOREIGN TABLE <name> OPTIONS (...)
  */
diff --git a/src/backend/commands/user.c b/src/backend/commands/user.c
index 91b6fa5c17d5c04bcaab3bc58fa3ba467efc3968..1a73fd85582b4923b8969694acf60504cefd1c8d 100644
--- a/src/backend/commands/user.c
+++ b/src/backend/commands/user.c
@@ -87,6 +87,7 @@ CreateRole(CreateRoleStmt *stmt)
 	bool		createdb = false;		/* Can the user create databases? */
 	bool		canlogin = false;		/* Can this user login? */
 	bool		isreplication = false;	/* Is this a replication role? */
+	bool		bypassrls = false;		/* Is this a row security enabled role? */
 	int			connlimit = -1; /* maximum connections allowed */
 	List	   *addroleto = NIL;	/* roles to make this a member of */
 	List	   *rolemembers = NIL;		/* roles to be members of this role */
@@ -106,6 +107,7 @@ CreateRole(CreateRoleStmt *stmt)
 	DefElem    *drolemembers = NULL;
 	DefElem    *dadminmembers = NULL;
 	DefElem    *dvalidUntil = NULL;
+	DefElem    *dbypassRLS = NULL;
 
 	/* The defaults can vary depending on the original statement type */
 	switch (stmt->stmt_type)
@@ -232,6 +234,14 @@ CreateRole(CreateRoleStmt *stmt)
 						 errmsg("conflicting or redundant options")));
 			dvalidUntil = defel;
 		}
+		else if (strcmp(defel->defname, "bypassrls") == 0)
+		{
+			if (dbypassRLS)
+				ereport(ERROR,
+						(errcode(ERRCODE_SYNTAX_ERROR),
+						 errmsg("conflicting or redundant options")));
+			dbypassRLS = defel;
+		}
 		else
 			elog(ERROR, "option \"%s\" not recognized",
 				 defel->defname);
@@ -267,6 +277,8 @@ CreateRole(CreateRoleStmt *stmt)
 		adminmembers = (List *) dadminmembers->arg;
 	if (dvalidUntil)
 		validUntil = strVal(dvalidUntil->arg);
+	if (dbypassRLS)
+		bypassrls = intVal(dbypassRLS->arg) != 0;
 
 	/* Check some permissions first */
 	if (issuper)
@@ -283,6 +295,13 @@ CreateRole(CreateRoleStmt *stmt)
 					(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
 				   errmsg("must be superuser to create replication users")));
 	}
+	else if (bypassrls)
+	{
+		if (!superuser())
+			ereport(ERROR,
+					(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+					 errmsg("must be superuser to change bypassrls attribute.")));
+	}
 	else
 	{
 		if (!have_createrole_privilege())
@@ -375,6 +394,8 @@ CreateRole(CreateRoleStmt *stmt)
 	new_record[Anum_pg_authid_rolvaliduntil - 1] = validUntil_datum;
 	new_record_nulls[Anum_pg_authid_rolvaliduntil - 1] = validUntil_null;
 
+	new_record[Anum_pg_authid_rolbypassrls - 1] = BoolGetDatum(bypassrls);
+
 	tuple = heap_form_tuple(pg_authid_dsc, new_record, new_record_nulls);
 
 	/*
@@ -474,6 +495,7 @@ AlterRole(AlterRoleStmt *stmt)
 	char	   *validUntil = NULL;		/* time the login is valid until */
 	Datum		validUntil_datum;		/* same, as timestamptz Datum */
 	bool		validUntil_null;
+	bool		bypassrls = -1;
 	DefElem    *dpassword = NULL;
 	DefElem    *dissuper = NULL;
 	DefElem    *dinherit = NULL;
@@ -484,6 +506,7 @@ AlterRole(AlterRoleStmt *stmt)
 	DefElem    *dconnlimit = NULL;
 	DefElem    *drolemembers = NULL;
 	DefElem    *dvalidUntil = NULL;
+	DefElem    *dbypassRLS = NULL;
 	Oid			roleid;
 
 	/* Extract options from the statement node tree */
@@ -578,6 +601,14 @@ AlterRole(AlterRoleStmt *stmt)
 						 errmsg("conflicting or redundant options")));
 			dvalidUntil = defel;
 		}
+		else if (strcmp(defel->defname, "bypassrls") == 0)
+		{
+			if (dbypassRLS)
+				ereport(ERROR,
+						(errcode(ERRCODE_SYNTAX_ERROR),
+						 errmsg("conflicting or redundant options")));
+			dbypassRLS = defel;
+		}
 		else
 			elog(ERROR, "option \"%s\" not recognized",
 				 defel->defname);
@@ -609,6 +640,8 @@ AlterRole(AlterRoleStmt *stmt)
 		rolemembers = (List *) drolemembers->arg;
 	if (dvalidUntil)
 		validUntil = strVal(dvalidUntil->arg);
+	if (dbypassRLS)
+		bypassrls = intVal(dbypassRLS->arg);
 
 	/*
 	 * Scan the pg_authid relation to be certain the user exists.
@@ -642,6 +675,13 @@ AlterRole(AlterRoleStmt *stmt)
 					(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
 					 errmsg("must be superuser to alter replication users")));
 	}
+	else if (((Form_pg_authid) GETSTRUCT(tuple))->rolbypassrls || bypassrls >= 0)
+	{
+		if (!superuser())
+			ereport(ERROR,
+					(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+					 errmsg("must be superuser to change bypassrls attribute")));
+	}
 	else if (!have_createrole_privilege())
 	{
 		if (!(inherit < 0 &&
@@ -775,6 +815,12 @@ AlterRole(AlterRoleStmt *stmt)
 	new_record_nulls[Anum_pg_authid_rolvaliduntil - 1] = validUntil_null;
 	new_record_repl[Anum_pg_authid_rolvaliduntil - 1] = true;
 
+	if (bypassrls >= 0)
+	{
+		new_record[Anum_pg_authid_rolbypassrls - 1] = BoolGetDatum(bypassrls > 0);
+		new_record_repl[Anum_pg_authid_rolbypassrls - 1] = true;
+	}
+
 	new_tuple = heap_modify_tuple(tuple, pg_authid_dsc, new_record,
 								  new_record_nulls, new_record_repl);
 	simple_heap_update(pg_authid_rel, &tuple->t_self, new_tuple);
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 01eda70f0544afdc8350c2fa3b9cbe394fb03125..a546292da6ebdf46e51437e2d035f9264ab8eee2 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -501,6 +501,12 @@ ExecutorRewind(QueryDesc *queryDesc)
  *
  * Returns true if permissions are adequate.  Otherwise, throws an appropriate
  * error if ereport_on_violation is true, or simply returns false otherwise.
+ *
+ * Note that this does NOT address row-level security policies (aka: RLS).  If
+ * rows will be returned to the user as a result of this permission check
+ * passing, then RLS also needs to be consulted (and check_enable_rls()).
+ *
+ * See rewrite/rowsecurity.c.
  */
 bool
 ExecCheckRTPerms(List *rangeTable, bool ereport_on_violation)
@@ -1660,15 +1666,17 @@ ExecWithCheckOptions(ResultRelInfo *resultRelInfo,
 
 		/*
 		 * WITH CHECK OPTION checks are intended to ensure that the new tuple
-		 * is visible in the view.  If the view's qual evaluates to NULL, then
-		 * the new tuple won't be included in the view.  Therefore we need to
-		 * tell ExecQual to return FALSE for NULL (the opposite of what we do
-		 * above for CHECK constraints).
+		 * is visible (in the case of a view) or that it passes the
+		 * 'with-check' policy (in the case of row security).
+		 * If the qual evaluates to NULL or FALSE, then the new tuple won't be
+		 * included in the view or doesn't pass the 'with-check' policy for the
+		 * table.  We need ExecQual to return FALSE for NULL to handle the view
+		 * case (the opposite of what we do above for CHECK constraints).
 		 */
 		if (!ExecQual((List *) wcoExpr, econtext, false))
 			ereport(ERROR,
 					(errcode(ERRCODE_WITH_CHECK_OPTION_VIOLATION),
-				 errmsg("new row violates WITH CHECK OPTION for view \"%s\"",
+				 errmsg("new row violates WITH CHECK OPTION for \"%s\"",
 						wco->viewname),
 					 errdetail("Failing row contains %s.",
 							   ExecBuildSlotValueDescription(slot,
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index aa053a0f158889c409bc2a042bcda19c0ce43ddc..8d842d46899c8bb1e5111765e5afa2ff13284426 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2488,6 +2488,7 @@ _copyQuery(const Query *from)
 	COPY_SCALAR_FIELD(hasRecursive);
 	COPY_SCALAR_FIELD(hasModifyingCTE);
 	COPY_SCALAR_FIELD(hasForUpdate);
+	COPY_SCALAR_FIELD(hasRowSecurity);
 	COPY_NODE_FIELD(cteList);
 	COPY_NODE_FIELD(rtable);
 	COPY_NODE_FIELD(jointree);
@@ -3849,6 +3850,35 @@ _copyAlterTSConfigurationStmt(const AlterTSConfigurationStmt *from)
 	return newnode;
 }
 
+static CreatePolicyStmt *
+_copyCreatePolicyStmt(const CreatePolicyStmt *from)
+{
+	CreatePolicyStmt *newnode = makeNode(CreatePolicyStmt);
+
+	COPY_STRING_FIELD(policy_name);
+	COPY_NODE_FIELD(table);
+	COPY_SCALAR_FIELD(cmd);
+	COPY_NODE_FIELD(roles);
+	COPY_NODE_FIELD(qual);
+	COPY_NODE_FIELD(with_check);
+
+	return newnode;
+}
+
+static AlterPolicyStmt *
+_copyAlterPolicyStmt(const AlterPolicyStmt *from)
+{
+	AlterPolicyStmt *newnode = makeNode(AlterPolicyStmt);
+
+	COPY_STRING_FIELD(policy_name);
+	COPY_NODE_FIELD(table);
+	COPY_NODE_FIELD(roles);
+	COPY_NODE_FIELD(qual);
+	COPY_NODE_FIELD(with_check);
+
+	return newnode;
+}
+
 /* ****************************************************************
  *					pg_list.h copy functions
  * ****************************************************************
@@ -4561,7 +4591,12 @@ copyObject(const void *from)
 		case T_AlterTSConfigurationStmt:
 			retval = _copyAlterTSConfigurationStmt(from);
 			break;
-
+		case T_CreatePolicyStmt:
+			retval = _copyCreatePolicyStmt(from);
+			break;
+		case T_AlterPolicyStmt:
+			retval = _copyAlterPolicyStmt(from);
+			break;
 		case T_A_Expr:
 			retval = _copyAExpr(from);
 			break;
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 719923e02e764d188b9d5ffe72ea7d7f056113eb..7a291505d6421cdad69590d0a5dc0cfca0ef9a19 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -857,6 +857,7 @@ _equalQuery(const Query *a, const Query *b)
 	COMPARE_SCALAR_FIELD(hasRecursive);
 	COMPARE_SCALAR_FIELD(hasModifyingCTE);
 	COMPARE_SCALAR_FIELD(hasForUpdate);
+	COMPARE_SCALAR_FIELD(hasRowSecurity);
 	COMPARE_NODE_FIELD(cteList);
 	COMPARE_NODE_FIELD(rtable);
 	COMPARE_NODE_FIELD(jointree);
@@ -2007,6 +2008,31 @@ _equalAlterTSConfigurationStmt(const AlterTSConfigurationStmt *a,
 	return true;
 }
 
+static bool
+_equalCreatePolicyStmt(const CreatePolicyStmt *a, const CreatePolicyStmt *b)
+{
+	COMPARE_STRING_FIELD(policy_name);
+	COMPARE_NODE_FIELD(table);
+	COMPARE_SCALAR_FIELD(cmd);
+	COMPARE_NODE_FIELD(roles);
+	COMPARE_NODE_FIELD(qual);
+	COMPARE_NODE_FIELD(with_check);
+
+	return true;
+}
+
+static bool
+_equalAlterPolicyStmt(const AlterPolicyStmt *a, const AlterPolicyStmt *b)
+{
+	COMPARE_STRING_FIELD(policy_name);
+	COMPARE_NODE_FIELD(table);
+	COMPARE_NODE_FIELD(roles);
+	COMPARE_NODE_FIELD(qual);
+	COMPARE_NODE_FIELD(with_check);
+
+	return true;
+}
+
 static bool
 _equalAExpr(const A_Expr *a, const A_Expr *b)
 {
@@ -3025,7 +3051,12 @@ equal(const void *a, const void *b)
 		case T_AlterTSConfigurationStmt:
 			retval = _equalAlterTSConfigurationStmt(a, b);
 			break;
-
+		case T_CreatePolicyStmt:
+			retval = _equalCreatePolicyStmt(a, b);
+			break;
+		case T_AlterPolicyStmt:
+			retval = _equalAlterPolicyStmt(a, b);
+			break;
 		case T_A_Expr:
 			retval = _equalAExpr(a, b);
 			break;
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index e686a6c199d018346a7a9adc8c1377a2e6e5f6cf..1ff78ebddd3f4b231f326d52181ea00698c3284d 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2263,6 +2263,7 @@ _outQuery(StringInfo str, const Query *node)
 	WRITE_BOOL_FIELD(hasRecursive);
 	WRITE_BOOL_FIELD(hasModifyingCTE);
 	WRITE_BOOL_FIELD(hasForUpdate);
+	WRITE_BOOL_FIELD(hasRowSecurity);
 	WRITE_NODE_FIELD(cteList);
 	WRITE_NODE_FIELD(rtable);
 	WRITE_NODE_FIELD(jointree);
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 69d9989484990f3fcce8b1fdaa7c79c8ef852108..a324100ed7ff78aaddbae41bdc9509099cb3a576 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -208,6 +208,7 @@ _readQuery(void)
 	READ_BOOL_FIELD(hasRecursive);
 	READ_BOOL_FIELD(hasModifyingCTE);
 	READ_BOOL_FIELD(hasForUpdate);
+	READ_BOOL_FIELD(hasRowSecurity);
 	READ_NODE_FIELD(cteList);
 	READ_NODE_FIELD(rtable);
 	READ_NODE_FIELD(jointree);
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index e1480cda2470cc09cdd9358e03ac403df01babc5..a509edd3affa5f8c9a431a3ed7efc68fe2bfa190 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -177,6 +177,7 @@ standard_planner(Query *parse, int cursorOptions, ParamListInfo boundParams)
 	glob->lastPHId = 0;
 	glob->lastRowMarkId = 0;
 	glob->transientPlan = false;
+	glob->has_rls = false;
 
 	/* Determine what fraction of the plan is likely to be scanned */
 	if (cursorOptions & CURSOR_OPT_FAST_PLAN)
@@ -254,6 +255,7 @@ standard_planner(Query *parse, int cursorOptions, ParamListInfo boundParams)
 	result->relationOids = glob->relationOids;
 	result->invalItems = glob->invalItems;
 	result->nParamExec = glob->nParamExec;
+	result->has_rls = glob->has_rls;
 
 	return result;
 }
@@ -1206,6 +1208,7 @@ grouping_planner(PlannerInfo *root, double tuple_fraction)
 		 * This may add new security barrier subquery RTEs to the rangetable.
 		 */
 		expand_security_quals(root, tlist);
+		root->glob->has_rls = parse->hasRowSecurity;
 
 		/*
 		 * Locate any window functions in the tlist.  (We don't need to look
diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c
index 4d717df191d9f9ee6b941a979f8bb0c2a1250598..5bf84c1a214cb39db8394f2a4c5885166fb7dea6 100644
--- a/src/backend/optimizer/plan/setrefs.c
+++ b/src/backend/optimizer/plan/setrefs.c
@@ -2081,7 +2081,8 @@ record_plan_function_dependency(PlannerInfo *root, Oid funcid)
 void
 extract_query_dependencies(Node *query,
 						   List **relationOids,
-						   List **invalItems)
+						   List **invalItems,
+						   bool *hasRowSecurity)
 {
 	PlannerGlobal glob;
 	PlannerInfo root;
@@ -2091,6 +2092,7 @@ extract_query_dependencies(Node *query,
 	glob.type = T_PlannerGlobal;
 	glob.relationOids = NIL;
 	glob.invalItems = NIL;
+	glob.has_rls = false;
 
 	MemSet(&root, 0, sizeof(root));
 	root.type = T_PlannerInfo;
@@ -2100,6 +2102,7 @@ extract_query_dependencies(Node *query,
 
 	*relationOids = glob.relationOids;
 	*invalItems = glob.invalItems;
+	*hasRowSecurity = glob.has_rls;
 }
 
 static bool
@@ -2115,6 +2118,9 @@ extract_query_dependencies_walker(Node *node, PlannerInfo *context)
 		Query	   *query = (Query *) node;
 		ListCell   *lc;
 
+		/* Collect row-security information */
+		context->glob->has_rls = query->hasRowSecurity;
+
 		if (query->commandType == CMD_UTILITY)
 		{
 			/*
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index b46dd7b008caf407aa29606ee3e2cd094e3f4d4f..77d2f29fc7ad2d428a0fd2f1202c530bb95e9366 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -231,7 +231,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 		AlterObjectSchemaStmt AlterOwnerStmt AlterSeqStmt AlterSystemStmt AlterTableStmt
 		AlterTblSpcStmt AlterExtensionStmt AlterExtensionContentsStmt AlterForeignTableStmt
 		AlterCompositeTypeStmt AlterUserStmt AlterUserMappingStmt AlterUserSetStmt
-		AlterRoleStmt AlterRoleSetStmt
+		AlterRoleStmt AlterRoleSetStmt AlterPolicyStmt
 		AlterDefaultPrivilegesStmt DefACLAction
 		AnalyzeStmt ClosePortalStmt ClusterStmt CommentStmt
 		ConstraintsSetStmt CopyStmt CreateAsStmt CreateCastStmt
@@ -240,11 +240,11 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 		CreateSchemaStmt CreateSeqStmt CreateStmt CreateTableSpaceStmt
 		CreateFdwStmt CreateForeignServerStmt CreateForeignTableStmt
 		CreateAssertStmt CreateTrigStmt CreateEventTrigStmt
-		CreateUserStmt CreateUserMappingStmt CreateRoleStmt
+		CreateUserStmt CreateUserMappingStmt CreateRoleStmt CreatePolicyStmt
 		CreatedbStmt DeclareCursorStmt DefineStmt DeleteStmt DiscardStmt DoStmt
 		DropGroupStmt DropOpClassStmt DropOpFamilyStmt DropPLangStmt DropStmt
 		DropAssertStmt DropTrigStmt DropRuleStmt DropCastStmt DropRoleStmt
-		DropUserStmt DropdbStmt DropTableSpaceStmt DropFdwStmt
+		DropPolicyStmt DropUserStmt DropdbStmt DropTableSpaceStmt DropFdwStmt
 		DropForeignServerStmt DropUserMappingStmt ExplainStmt FetchStmt
 		GrantStmt GrantRoleStmt ImportForeignSchemaStmt IndexStmt InsertStmt
 		ListenStmt LoadStmt LockStmt NotifyStmt ExplainableStmt PreparableStmt
@@ -319,6 +319,10 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 
 %type <str>		all_Op MathOp
 
+%type <str>		row_security_cmd RowSecurityDefaultForCmd
+%type <node>	RowSecurityOptionalWithCheck RowSecurityOptionalExpr
+%type <list>	RowSecurityDefaultToRole RowSecurityOptionalToRole
+
 %type <str>		iso_level opt_encoding
 %type <node>	grantee
 %type <list>	grantee_list
@@ -589,7 +593,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	OBJECT_P OF OFF OFFSET OIDS ON ONLY OPERATOR OPTION OPTIONS OR
 	ORDER ORDINALITY OUT_P OUTER_P OVER OVERLAPS OVERLAY OWNED OWNER
 
-	PARSER PARTIAL PARTITION PASSING PASSWORD PLACING PLANS POSITION
+	PARSER PARTIAL PARTITION PASSING PASSWORD PLACING PLANS POLICY POSITION
 	PRECEDING PRECISION PRESERVE PREPARE PREPARED PRIMARY
 	PRIOR PRIVILEGES PROCEDURAL PROCEDURE PROGRAM
 
@@ -740,6 +744,7 @@ stmt :
 			| AlterGroupStmt
 			| AlterObjectSchemaStmt
 			| AlterOwnerStmt
+			| AlterPolicyStmt
 			| AlterSeqStmt
 			| AlterSystemStmt
 			| AlterTableStmt
@@ -774,6 +779,7 @@ stmt :
 			| CreateOpClassStmt
 			| CreateOpFamilyStmt
 			| AlterOpFamilyStmt
+			| CreatePolicyStmt
 			| CreatePLangStmt
 			| CreateSchemaStmt
 			| CreateSeqStmt
@@ -799,6 +805,7 @@ stmt :
 			| DropOpClassStmt
 			| DropOpFamilyStmt
 			| DropOwnedStmt
+			| DropPolicyStmt
 			| DropPLangStmt
 			| DropRuleStmt
 			| DropStmt
@@ -957,6 +964,10 @@ AlterOptRoleElem:
 						$$ = makeDefElem("canlogin", (Node *)makeInteger(TRUE));
 					else if (strcmp($1, "nologin") == 0)
 						$$ = makeDefElem("canlogin", (Node *)makeInteger(FALSE));
+					else if (strcmp($1, "bypassrls") == 0)
+						$$ = makeDefElem("bypassrls", (Node *)makeInteger(TRUE));
+					else if (strcmp($1, "nobypassrls") == 0)
+						$$ = makeDefElem("bypassrls", (Node *)makeInteger(FALSE));
 					else if (strcmp($1, "noinherit") == 0)
 					{
 						/*
@@ -2302,6 +2313,20 @@ alter_table_cmd:
 					n->def = $3;
 					$$ = (Node *)n;
 				}
+			/* ALTER TABLE <name> ENABLE ROW LEVEL SECURITY */
+			| ENABLE_P ROW LEVEL SECURITY
+				{
+					AlterTableCmd *n = makeNode(AlterTableCmd);
+					n->subtype = AT_EnableRowSecurity;
+					$$ = (Node *)n;
+				}
+			/* ALTER TABLE <name> DISABLE ROW LEVEL SECURITY */
+			| DISABLE_P ROW LEVEL SECURITY
+				{
+					AlterTableCmd *n = makeNode(AlterTableCmd);
+					n->subtype = AT_DisableRowSecurity;
+					$$ = (Node *)n;
+				}
 			| alter_generic_options
 				{
 					AlterTableCmd *n = makeNode(AlterTableCmd);
@@ -4495,6 +4520,105 @@ AlterUserMappingStmt: ALTER USER MAPPING FOR auth_ident SERVER name alter_generi
 				}
 		;
 
+/*****************************************************************************
+ *
+ *		QUERIES:
+ *				CREATE POLICY name ON table FOR cmd TO role USING (qual)
+ *					WITH CHECK (with_check)
+ *				ALTER POLICY name ON table FOR cmd TO role USING (qual)
+ *					WITH CHECK (with_check)
+ *				DROP POLICY name ON table
+ *
+ *****************************************************************************/
+
+CreatePolicyStmt:
+			CREATE POLICY name ON qualified_name RowSecurityDefaultForCmd
+				RowSecurityDefaultToRole RowSecurityOptionalExpr
+				RowSecurityOptionalWithCheck
+				{
+					CreatePolicyStmt *n = makeNode(CreatePolicyStmt);
+					n->policy_name = $3;
+					n->table = $5;
+					n->cmd = $6;
+					n->roles = $7;
+					n->qual = $8;
+					n->with_check = $9;
+					$$ = (Node *) n;
+				}
+		;
+
+AlterPolicyStmt:
+			ALTER POLICY name ON qualified_name RowSecurityOptionalToRole
+				RowSecurityOptionalExpr RowSecurityOptionalWithCheck
+				{
+					AlterPolicyStmt *n = makeNode(AlterPolicyStmt);
+					n->policy_name = $3;
+					n->table = $5;
+					n->roles = $6;
+					n->qual = $7;
+					n->with_check = $8;
+					$$ = (Node *) n;
+				}
+		;
+
+DropPolicyStmt:
+			DROP POLICY name ON any_name opt_drop_behavior
+				{
+					DropStmt *n = makeNode(DropStmt);
+					n->removeType = OBJECT_POLICY;
+					n->objects = list_make1(lappend($5, makeString($3)));
+					n->arguments = NIL;
+					n->behavior = $6;
+					n->missing_ok = false;
+					n->concurrent = false;
+					$$ = (Node *) n;
+				}
+			| DROP POLICY IF_P EXISTS name ON any_name opt_drop_behavior
+				{
+					DropStmt *n = makeNode(DropStmt);
+					n->removeType = OBJECT_POLICY;
+					n->objects = list_make1(lappend($7, makeString($5)));
+					n->arguments = NIL;
+					n->behavior = $8;
+					n->missing_ok = true;
+					n->concurrent = false;
+					$$ = (Node *) n;
+				}
+		;
+
+RowSecurityOptionalExpr:
+			USING '(' a_expr ')'	{ $$ = $3; }
+			| /* EMPTY */			{ $$ = NULL; }
+		;
+
+RowSecurityOptionalWithCheck:
+			WITH CHECK '(' a_expr ')'		{ $$ = $4; }
+			| /* EMPTY */					{ $$ = NULL; }
+		;
+
+RowSecurityDefaultToRole:
+			TO role_list			{ $$ = $2; }
+			| /* EMPTY */			{ $$ = list_make1(makeString("public")); }
+		;
+
+RowSecurityOptionalToRole:
+			TO role_list			{ $$ = $2; }
+			| /* EMPTY */			{ $$ = NULL; }
+		;
+
+RowSecurityDefaultForCmd:
+			FOR row_security_cmd	{ $$ = $2; }
+			| /* EMPTY */			{ $$ = "all"; }
+		;
+
+row_security_cmd:
+			ALL				{ $$ = "all"; }
+		|	SELECT			{ $$ = "select"; }
+		|	INSERT			{ $$ = "insert"; }
+		|	UPDATE			{ $$ = "update"; }
+		|	DELETE_P		{ $$ = "delete"; }
+		;
+
 /*****************************************************************************
  *
  *		QUERIES :
@@ -7240,6 +7364,26 @@ RenameStmt: ALTER AGGREGATE func_name aggr_args RENAME TO name
 					n->missing_ok = false;
 					$$ = (Node *)n;
 				}
+			| ALTER POLICY name ON qualified_name RENAME TO name
+				{
+					RenameStmt *n = makeNode(RenameStmt);
+					n->renameType = OBJECT_POLICY;
+					n->relation = $5;
+					n->subname = $3;
+					n->newname = $8;
+					n->missing_ok = false;
+					$$ = (Node *)n;
+				}
+			| ALTER POLICY IF_P EXISTS name ON qualified_name RENAME TO name
+				{
+					RenameStmt *n = makeNode(RenameStmt);
+					n->renameType = OBJECT_POLICY;
+					n->relation = $7;
+					n->subname = $5;
+					n->newname = $10;
+					n->missing_ok = true;
+					$$ = (Node *)n;
+				}
 			| ALTER SCHEMA name RENAME TO name
 				{
 					RenameStmt *n = makeNode(RenameStmt);
@@ -13036,6 +13180,7 @@ unreserved_keyword:
 			| PASSING
 			| PASSWORD
 			| PLANS
+			| POLICY
 			| PRECEDING
 			| PREPARE
 			| PREPARED
diff --git a/src/backend/rewrite/Makefile b/src/backend/rewrite/Makefile
index 9ff56c75adcf6b4e692a99a58d3b40f89ebaa959..25423d39e6d50b4dc10d6221484f9c8f4b5d5e04 100644
--- a/src/backend/rewrite/Makefile
+++ b/src/backend/rewrite/Makefile
@@ -13,6 +13,7 @@ top_builddir = ../../..
 include $(top_builddir)/src/Makefile.global
 
 OBJS = rewriteRemove.o rewriteDefine.o \
-       rewriteHandler.o rewriteManip.o rewriteSupport.o
+       rewriteHandler.o rewriteManip.o rewriteSupport.o \
+       rowsecurity.o
 
 include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index cb65c0502effc01212e240cbd8e1f14462aef75f..e640c1eaa512ef233894b39e2b92fa01353471cd 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -25,6 +25,7 @@
 #include "rewrite/rewriteDefine.h"
 #include "rewrite/rewriteHandler.h"
 #include "rewrite/rewriteManip.h"
+#include "rewrite/rowsecurity.h"
 #include "utils/builtins.h"
 #include "utils/lsyscache.h"
 #include "utils/rel.h"
@@ -1670,48 +1671,91 @@ fireRIRrules(Query *parsetree, List *activeRIRs, bool forUpdatePushedDown)
 		 * Collect the RIR rules that we must apply
 		 */
 		rules = rel->rd_rules;
-		if (rules == NULL)
+		if (rules != NULL)
 		{
-			heap_close(rel, NoLock);
-			continue;
-		}
-		locks = NIL;
-		for (i = 0; i < rules->numLocks; i++)
-		{
-			rule = rules->rules[i];
-			if (rule->event != CMD_SELECT)
-				continue;
+			locks = NIL;
+			for (i = 0; i < rules->numLocks; i++)
+			{
+				rule = rules->rules[i];
+				if (rule->event != CMD_SELECT)
+					continue;
 
-			locks = lappend(locks, rule);
-		}
+				locks = lappend(locks, rule);
+			}
+
+			/*
+			 * If we found any, apply them --- but first check for recursion!
+			 */
+			if (locks != NIL)
+			{
+				ListCell   *l;
+
+				if (list_member_oid(activeRIRs, RelationGetRelid(rel)))
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+							 errmsg("infinite recursion detected in rules for relation \"%s\"",
+									RelationGetRelationName(rel))));
+				activeRIRs = lcons_oid(RelationGetRelid(rel), activeRIRs);
+
+				foreach(l, locks)
+				{
+					rule = lfirst(l);
+
+					parsetree = ApplyRetrieveRule(parsetree,
+												  rule,
+												  rt_index,
+												  rel,
+												  activeRIRs,
+												  forUpdatePushedDown);
+				}
 
+				activeRIRs = list_delete_first(activeRIRs);
+			}
+		}
 		/*
-		 * If we found any, apply them --- but first check for recursion!
+		 * If the RTE has row-security quals, apply them and recurse into the
+		 * securityQuals.
 		 */
-		if (locks != NIL)
+		if (prepend_row_security_policies(parsetree, rte, rt_index))
 		{
-			ListCell   *l;
-
+			/*
+			 * We applied security quals, check for infinite recursion and
+			 * then expand any nested queries.
+			 */
 			if (list_member_oid(activeRIRs, RelationGetRelid(rel)))
-				ereport(ERROR,
-						(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
-						 errmsg("infinite recursion detected in rules for relation \"%s\"",
-								RelationGetRelationName(rel))));
-			activeRIRs = lcons_oid(RelationGetRelid(rel), activeRIRs);
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+							 errmsg("infinite recursion detected in row-security policy for relation \"%s\"",
+									RelationGetRelationName(rel))));
+
+			/*
+			 * Make sure we check for recursion in either securityQuals or
+			 * WITH CHECK quals.
+			 */
+			if (rte->securityQuals != NIL)
+			{
+				activeRIRs = lcons_oid(RelationGetRelid(rel), activeRIRs);
 
-			foreach(l, locks)
+				expression_tree_walker( (Node*) rte->securityQuals,
+										fireRIRonSubLink, (void*)activeRIRs );
+
+				activeRIRs = list_delete_first(activeRIRs);
+			}
+
+			if (parsetree->withCheckOptions != NIL)
 			{
-				rule = lfirst(l);
-
-				parsetree = ApplyRetrieveRule(parsetree,
-											  rule,
-											  rt_index,
-											  rel,
-											  activeRIRs,
-											  forUpdatePushedDown);
+				WithCheckOption    *wco;
+				List			   *quals = NIL;
+
+				wco = (WithCheckOption *) makeNode(WithCheckOption);
+				quals = lcons(wco->qual, quals);
+
+				activeRIRs = lcons_oid(RelationGetRelid(rel), activeRIRs);
+
+				expression_tree_walker( (Node*) quals, fireRIRonSubLink,
+									   (void*)activeRIRs);
 			}
 
-			activeRIRs = list_delete_first(activeRIRs);
 		}
 
 		heap_close(rel, NoLock);
diff --git a/src/backend/rewrite/rowsecurity.c b/src/backend/rewrite/rowsecurity.c
new file mode 100644
index 0000000000000000000000000000000000000000..e1ccd1295e6951e38a7d1b0998fca00042e0a6a9
--- /dev/null
+++ b/src/backend/rewrite/rowsecurity.c
@@ -0,0 +1,557 @@
+/*
+ * rewrite/rowsecurity.c
+ *    Routines to support policies for row-level security.
+ *
+ * Policies in PostgreSQL provide a mechanism to limit what records are
+ * returned to a user and what records a user is permitted to add to a table.
+ *
+ * Policies can be defined for specific roles, specific commands, or provided
+ * by an extension.  Row security can also be enabled for a table without any
+ * policies being explicitly defined, in which case a default-deny policy is
+ * applied.
+ *
+ * Any part of the system which is returning records back to the user, or
+ * which is accepting records from the user to add to a table, needs to
+ * consider the policies associated with the table (if any).  For normal
+ * queries, this is handled by calling prepend_row_security_policies() during
+ * rewrite, which looks at each RTE and adds the expressions defined by the
+ * policies to the securityQuals list for the RTE.  For queries which modify
+ * the relation, any WITH CHECK policies are added to the list of
+ * WithCheckOptions for the Query and checked against each row which is being
+ * added to the table.  Other parts of the system (eg: COPY) simply construct
+ * a normal query and use that, if RLS is to be applied.
+ *
+ * The check to see if RLS should be enabled is provided through
+ * check_enable_rls(), which returns an enum (defined in rowsecurity.h) to
+ * indicate if RLS should be enabled (RLS_ENABLED), or bypassed (RLS_NONE or
+ * RLS_NONE_ENV).  RLS_NONE_ENV indicates that RLS should be bypassed
+ * in the current environment, but that may change if the row_security GUC or
+ * the current role changes.
+ *
+ * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ */
+#include "postgres.h"
+
+#include "access/heapam.h"
+#include "access/htup_details.h"
+#include "access/sysattr.h"
+#include "catalog/pg_class.h"
+#include "catalog/pg_inherits_fn.h"
+#include "catalog/pg_rowsecurity.h"
+#include "catalog/pg_type.h"
+#include "miscadmin.h"
+#include "nodes/makefuncs.h"
+#include "nodes/nodeFuncs.h"
+#include "nodes/pg_list.h"
+#include "nodes/plannodes.h"
+#include "parser/parsetree.h"
+#include "rewrite/rewriteHandler.h"
+#include "rewrite/rewriteManip.h"
+#include "rewrite/rowsecurity.h"
+#include "utils/acl.h"
+#include "utils/lsyscache.h"
+#include "utils/rel.h"
+#include "utils/syscache.h"
+#include "tcop/utility.h"
+
+static List *pull_row_security_policies(CmdType cmd, Relation relation,
+										Oid user_id);
+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);
+
+/*
+ * hook to allow extensions to apply their own security policy
+ *
+ * See below where the hook is called in prepend_row_security_policies for
+ * insight into how to use this hook.
+ */
+row_security_policy_hook_type	row_security_policy_hook = NULL;
+
+/*
+ * Check the given RTE to see whether it's already had row-security quals
+ * expanded and, if not, prepend any row-security rules from built-in or
+ * plug-in sources to the securityQuals. The security quals are rewritten (for
+ * view expansion, etc) before being added to the RTE.
+ *
+ * Returns true if any quals were added. Note that quals may have been found
+ * but not added if user rights make the user exempt from row security.
+ */
+bool
+prepend_row_security_policies(Query* root, RangeTblEntry* rte, int rt_index)
+{
+	Expr			   *rowsec_expr = NULL;
+	Expr			   *rowsec_with_check_expr = NULL;
+	Expr			   *hook_expr = NULL;
+	Expr			   *hook_with_check_expr = NULL;
+
+	List			   *rowsec_policies;
+	List			   *hook_policies = NIL;
+
+	Relation 			rel;
+	Oid					user_id;
+	int					sec_context;
+	int					rls_status;
+	bool				defaultDeny = true;
+	bool				hassublinks = false;
+
+	/* This is just to get the security context */
+	GetUserIdAndSecContext(&user_id, &sec_context);
+
+	/* Switch to checkAsUser if it's set */
+	user_id = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+
+	/*
+	 * If this is not a normal relation, or we have been told
+	 * to explicitly skip RLS (perhaps because this is an FK check)
+	 * then just return immediately.
+	 */
+	if (rte->relid < FirstNormalObjectId
+		|| rte->relkind != RELKIND_RELATION
+		|| (sec_context & SECURITY_ROW_LEVEL_DISABLED))
+		return false;
+
+	/* Determine the state of RLS for this, pass checkAsUser explicitly */
+	rls_status = check_enable_rls(rte->relid, rte->checkAsUser);
+
+	/* If there is no RLS on this table at all, nothing to do */
+	if (rls_status == RLS_NONE)
+		return false;
+
+	/*
+	 * RLS_NONE_ENV means we are not doing any RLS now, but that may change
+	 * with changes to the environment, so we mark it as hasRowSecurity to
+	 * force a re-plan when the environment changes.
+	 */
+	if (rls_status == RLS_NONE_ENV)
+	{
+		/*
+		 * Indicate that this query may involve RLS and must therefore
+		 * be replanned if the environment changes (GUCs, role), but we
+		 * are not adding anything here.
+		 */
+		root->hasRowSecurity = true;
+
+		return false;
+	}
+
+	/*
+	 * We may end up getting called multiple times for the same RTE, so check
+	 * to make sure we aren't doing double-work.
+	 */
+	if (rte->securityQuals != NIL)
+		return false;
+
+	/* Grab the built-in policies which should be applied to this relation. */
+	rel = heap_open(rte->relid, NoLock);
+
+	rowsec_policies = pull_row_security_policies(root->commandType, rel,
+												 user_id);
+
+	/*
+	 * Check if this is only the default-deny policy.
+	 *
+	 * Normally, if the table has row-security enabled but there are
+	 * no policies, we use a default-deny policy and not allow anything.
+	 * However, when an extension uses the hook to add their own
+	 * policies, we don't want to include the default deny policy or
+	 * there won't be any way for a user to use an extension exclusively
+	 * for the policies to be used.
+	 */
+	if (((RowSecurityPolicy *) linitial(rowsec_policies))->rsecid
+			== InvalidOid)
+		defaultDeny = true;
+
+	/* Now that we have our policies, build the expressions from them. */
+	process_policies(rowsec_policies, rt_index, &rowsec_expr,
+					 &rowsec_with_check_expr, &hassublinks);
+
+	/*
+	 * Also, allow extensions to add their own policies.
+	 *
+	 * Note that, as with the internal policies, if multiple policies are
+	 * returned then they will be combined into a single expression with
+	 * 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
+	 * filtering of the result set (or further reduce the set of records
+	 * allowed to be added).
+	 *
+	 * If only a USING policy is returned by the extension then it will be
+	 * used for WITH CHECK as well, similar to how internal policies are
+	 * handled.
+	 *
+	 * The only caveat to this is that if there are NO internal policies
+	 * defined, there ARE policies returned by the extension, and RLS is
+	 * enabled on the table, then we will ignore the internally-generated
+	 * default-deny policy and use only the policies returned by the
+	 * extension.
+	 */
+	if (row_security_policy_hook)
+	{
+		hook_policies = (*row_security_policy_hook)(root->commandType, rel);
+
+		/* Build the expression from any policies returned. */
+		process_policies(hook_policies, rt_index, &hook_expr,
+						 &hook_with_check_expr, &hassublinks);
+	}
+
+	/*
+	 * If the only built-in policy is the default-deny one, and hook
+	 * policies exist, then use the hook policies only and do not apply
+	 * the default-deny policy.  Otherwise, apply both sets (AND'd
+	 * together).
+	 */
+	if (defaultDeny && hook_policies != NIL)
+		rowsec_expr = NULL;
+
+	/*
+	 * For INSERT or UPDATE, we need to add the WITH CHECK quals to
+	 * Query's withCheckOptions to verify that any new records pass the
+	 * WITH CHECK policy (this will be a copy of the USING policy, if no
+	 * explicit WITH CHECK policy exists).
+	 */
+	if (root->commandType == CMD_INSERT || root->commandType == CMD_UPDATE)
+	{
+		/*
+		 * WITH CHECK OPTIONS wants a WCO node which wraps each Expr, so
+		 * create them as necessary.
+		 */
+		if (rowsec_with_check_expr)
+		{
+			WithCheckOption	   *wco;
+
+			wco = (WithCheckOption *) makeNode(WithCheckOption);
+			wco->viewname = RelationGetRelationName(rel);
+			wco->qual = (Node *) rowsec_with_check_expr;
+			wco->cascaded = false;
+			root->withCheckOptions = lcons(wco, root->withCheckOptions);
+		}
+
+		/*
+		 * Ditto for the expression, if any, returned from the extension.
+		 */
+		if (hook_with_check_expr)
+		{
+			WithCheckOption	   *wco;
+
+			wco = (WithCheckOption *) makeNode(WithCheckOption);
+			wco->viewname = RelationGetRelationName(rel);
+			wco->qual = (Node *) hook_with_check_expr;
+			wco->cascaded = false;
+			root->withCheckOptions = lcons(wco, root->withCheckOptions);
+		}
+	}
+
+	/* For SELECT, UPDATE, and DELETE, set the security quals */
+	if (root->commandType == CMD_SELECT
+		|| root->commandType == CMD_UPDATE
+		|| root->commandType == CMD_DELETE)
+	{
+		if (rowsec_expr)
+			rte->securityQuals = lcons(rowsec_expr, rte->securityQuals);
+
+		if (hook_expr)
+			rte->securityQuals = lcons(hook_expr,
+									   rte->securityQuals);
+	}
+
+	heap_close(rel, NoLock);
+
+	/*
+	 * Mark this query as having row security, so plancache can invalidate
+	 * it when necessary (eg: role changes)
+	 */
+	root->hasRowSecurity = true;
+
+	/*
+	 * If we have sublinks added because of the policies being added to the
+	 * query, then set hasSubLinks on the Query to force subLinks to be
+	 * properly expanded.
+	 */
+	if (hassublinks)
+		root->hasSubLinks = hassublinks;
+
+	/* If we got this far, we must have added quals */
+	return true;
+}
+
+/*
+ * pull_row_security_policies
+ *
+ * Returns the list of policies to be added for this relation, based on the
+ * type of command and the roles to which it applies, from the relation cache.
+ *
+ */
+static List *
+pull_row_security_policies(CmdType cmd, Relation relation, Oid user_id)
+{
+	List			   *policies = NIL;
+	ListCell		   *item;
+	RowSecurityPolicy  *policy;
+
+	/*
+	 * Row security is enabled for the relation and the row security GUC is
+	 * either 'on' or 'force' here, so find the policies to apply to the table.
+	 * There must always be at least one policy defined (may be the simple
+	 * 'default-deny' policy, if none are explicitly defined on the table).
+	 */
+	foreach(item, relation->rsdesc->policies)
+	{
+		policy = (RowSecurityPolicy *) lfirst(item);
+
+		/* Always add ALL policies, if they exist. */
+		if (policy->cmd == '\0' && check_role_for_policy(policy, user_id))
+			policies = lcons(policy, policies);
+
+		/* Build the list of policies to return. */
+		switch(cmd)
+		{
+			case CMD_SELECT:
+				if (policy->cmd == ACL_SELECT_CHR
+					&& check_role_for_policy(policy, 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))
+					policies = lcons(policy, policies);
+				break;
+			case CMD_UPDATE:
+				if (policy->cmd == ACL_UPDATE_CHR
+					&& check_role_for_policy(policy, user_id))
+					policies = lcons(policy, policies);
+				break;
+			case CMD_DELETE:
+				if (policy->cmd == ACL_DELETE_CHR
+					&& check_role_for_policy(policy, user_id))
+					policies = lcons(policy, policies);
+				break;
+			default:
+				elog(ERROR, "unrecognized command type.");
+				break;
+		}
+	}
+
+	/*
+	 * There should always be a policy applied.  If there are none found then
+	 * create a simply defauly-deny policy (might be that policies exist but
+	 * that none of them apply to the role which is querying the table).
+	 */
+	if (policies == NIL)
+	{
+		RowSecurityPolicy  *policy = NULL;
+		Datum               role;
+
+		role = ObjectIdGetDatum(ACL_ID_PUBLIC);
+
+		policy = palloc0(sizeof(RowSecurityPolicy));
+		policy->policy_name = pstrdup("default-deny policy");
+		policy->rsecid = InvalidOid;
+		policy->cmd = '\0';
+		policy->roles = construct_array(&role, 1, OIDOID, sizeof(Oid), true,
+										'i');
+		policy->qual = (Expr *) makeConst(BOOLOID, -1, InvalidOid,
+										  sizeof(bool), BoolGetDatum(false),
+										  false, true);
+		policy->with_check_qual = copyObject(policy->qual);
+		policy->hassublinks = false;
+
+		policies = list_make1(policy);
+	}
+
+	Assert(policies != NIL);
+
+	return policies;
+}
+
+/*
+ * process_policies
+ *
+ * This will step through the policies which are passed in (which would come
+ * from either the built-in ones created on a table, or from policies provided
+ * by an extension through the hook provided), work out how to combine them,
+ * rewrite them as necessary, and produce an Expr for the normal security
+ * quals and an Expr for the with check quals.
+ *
+ * qual_eval, with_check_eval, and hassublinks are output variables
+ */
+static void
+process_policies(List *policies, int rt_index, Expr **qual_eval,
+				 Expr **with_check_eval, bool *hassublinks)
+{
+	ListCell		   *item;
+	List			   *quals = NIL;
+	List			   *with_check_quals = NIL;
+
+	/*
+	 * Extract the USING and WITH CHECK quals from each of the policies
+	 * and add them to our lists.
+	 */
+	foreach(item, policies)
+	{
+		RowSecurityPolicy *policy = (RowSecurityPolicy *) lfirst(item);
+
+		if (policy->qual != NULL)
+			quals = lcons(copyObject(policy->qual), quals);
+
+		if (policy->with_check_qual != NULL)
+			with_check_quals = lcons(copyObject(policy->with_check_qual),
+									 with_check_quals);
+
+		if (policy->hassublinks)
+			*hassublinks = true;
+	}
+
+	/*
+	 * If we end up without any normal quals (perhaps the only policy matched
+	 * was for INSERT), then create a single all-false one.
+	 */
+	if (quals == NIL)
+		quals = lcons(makeConst(BOOLOID, -1, InvalidOid, sizeof(bool),
+								BoolGetDatum(false), false, true), quals);
+
+	/*
+	 * If we end up with only USING quals, then use those as
+	 * WITH CHECK quals also.
+	 */
+	if (with_check_quals == NIL)
+		with_check_quals = copyObject(quals);
+
+	/*
+	 * Row security quals always have the target table as varno 1, as no
+	 * joins are permitted in row security expressions. We must walk the
+	 * expression, updating any references to varno 1 to the varno
+	 * the table has in the outer query.
+	 *
+	 * We rewrite the expression in-place.
+	 */
+	ChangeVarNodes((Node *) quals, 1, rt_index, 0);
+	ChangeVarNodes((Node *) with_check_quals, 1, rt_index, 0);
+
+	/*
+	 * If more than one security qual is returned, then they need to be
+	 * OR'ed together.
+	 */
+	if (list_length(quals) > 1)
+		*qual_eval = makeBoolExpr(OR_EXPR, quals, -1);
+	else
+		*qual_eval = (Expr*) linitial(quals);
+
+	/*
+	 * If more than one WITH CHECK qual is returned, then they need to
+	 * be OR'ed together.
+	 */
+	if (list_length(with_check_quals) > 1)
+		*with_check_eval = makeBoolExpr(OR_EXPR, with_check_quals, -1);
+	else
+		*with_check_eval = (Expr*) linitial(with_check_quals);
+
+	return;
+}
+
+/*
+ * check_enable_rls
+ *
+ * Determine, based on the relation, row_security setting, and current role,
+ * if RLS is applicable to this query.  RLS_NONE_ENV indicates that, while
+ * RLS is not to be added for this query, a change in the environment may change
+ * that.  RLS_NONE means that RLS is not on the relation at all and therefore
+ * we don't need to worry about it.  RLS_ENABLED means RLS should be implemented
+ * for the table and the plan cache needs to be invalidated if the environment
+ * changes.
+ *
+ * Handle checking as another role via checkAsUser (for views, etc).
+ */
+int
+check_enable_rls(Oid relid, Oid checkAsUser)
+{
+	HeapTuple		tuple;
+	Form_pg_class	classform;
+	bool			relhasrowsecurity;
+	Oid				user_id = checkAsUser ? checkAsUser : GetUserId();
+
+	tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relid));
+	if (!HeapTupleIsValid(tuple))
+		return RLS_NONE;
+
+	classform = (Form_pg_class) GETSTRUCT(tuple);
+
+	relhasrowsecurity = classform->relhasrowsecurity;
+
+	ReleaseSysCache(tuple);
+
+	/* Nothing to do if the relation does not have RLS */
+	if (!relhasrowsecurity)
+		return RLS_NONE;
+
+	/*
+	 * Check permissions
+	 *
+	 * If the relation has row level security enabled and the row_security GUC
+	 * is off, then check if the user has rights to bypass RLS for this
+	 * relation.  Table owners can always bypass, as can any role with the
+	 * BYPASSRLS capability.
+	 *
+	 * If the role is the table owner, then we bypass RLS unless row_security
+	 * is set to 'force'.  Note that superuser is always considered an owner.
+	 *
+	 * Return RLS_NONE_ENV to indicate that this decision depends on the
+	 * environment (in this case, what the current values of user_id and
+	 * row_security are).
+	 */
+	if (row_security != ROW_SECURITY_FORCE
+		&& (pg_class_ownercheck(relid, user_id)))
+		return RLS_NONE_ENV;
+
+	/*
+	 * If the row_security GUC is 'off' then check if the user has permission
+	 * to bypass it.  Note that we have already handled the case where the user
+	 * is the table owner above.
+	 *
+	 * Note that row_security is always considered 'on' when querying
+	 * through a view or other cases where checkAsUser is true, so skip this
+	 * if checkAsUser is in use.
+	 */
+	if (!checkAsUser && row_security == ROW_SECURITY_OFF)
+	{
+		if (has_bypassrls_privilege(user_id))
+			/* OK to bypass */
+			return RLS_NONE_ENV;
+		else
+			ereport(ERROR,
+					(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+					 errmsg("insufficient privilege to bypass row security.")));
+	}
+
+	/* RLS should be fully enabled for this relation. */
+	return RLS_ENABLED;
+}
+
+/*
+ * 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)
+{
+	int			i;
+	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++)
+	{
+		if (is_member_of_role(user_id, roles[i]))
+			return true;
+	}
+
+	return false;
+}
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index e2c2d3d5589b0097ea1e9c9bbb8c561663dde72a..24aa2b3dc8985a0220aeacfb1fdb0e3153997b33 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -39,6 +39,7 @@
 #include "commands/extension.h"
 #include "commands/matview.h"
 #include "commands/lockcmds.h"
+#include "commands/policy.h"
 #include "commands/portalcmds.h"
 #include "commands/prepare.h"
 #include "commands/proclang.h"
@@ -1320,6 +1321,14 @@ ProcessUtilitySlow(Node *parsetree,
 				ExecAlterDefaultPrivilegesStmt((AlterDefaultPrivilegesStmt *) parsetree);
 				break;
 
+			case T_CreatePolicyStmt:	/* CREATE POLICY */
+				CreatePolicy((CreatePolicyStmt *) parsetree);
+				break;
+
+			case T_AlterPolicyStmt:		/* ALTER POLICY */
+				AlterPolicy((AlterPolicyStmt *) parsetree);
+				break;
+
 			default:
 				elog(ERROR, "unrecognized node type: %d",
 					 (int) nodeTag(parsetree));
@@ -1623,6 +1632,9 @@ AlterObjectTypeCommandTag(ObjectType objtype)
 		case OBJECT_OPFAMILY:
 			tag = "ALTER OPERATOR FAMILY";
 			break;
+		case OBJECT_POLICY:
+			tag = "ALTER POLICY";
+			break;
 		case OBJECT_ROLE:
 			tag = "ALTER ROLE";
 			break;
@@ -1944,6 +1956,9 @@ CreateCommandTag(Node *parsetree)
 				case OBJECT_OPFAMILY:
 					tag = "DROP OPERATOR FAMILY";
 					break;
+				case OBJECT_POLICY:
+					tag = "DROP POLICY";
+					break;
 				default:
 					tag = "???";
 			}
@@ -2287,6 +2302,14 @@ CreateCommandTag(Node *parsetree)
 			tag = "ALTER TEXT SEARCH CONFIGURATION";
 			break;
 
+		case T_CreatePolicyStmt:
+			tag = "CREATE POLICY";
+			break;
+
+		case T_AlterPolicyStmt:
+			tag = "ALTER POLICY";
+			break;
+
 		case T_PrepareStmt:
 			tag = "PREPARE";
 			break;
@@ -2831,6 +2854,14 @@ GetCommandLogLevel(Node *parsetree)
 			lev = LOGSTMT_DDL;
 			break;
 
+		case T_CreatePolicyStmt:
+			lev = LOGSTMT_DDL;
+			break;
+
+		case T_AlterPolicyStmt:
+			lev = LOGSTMT_DDL;
+			break;
+
 		case T_AlterTSDictionaryStmt:
 			lev = LOGSTMT_DDL;
 			break;
diff --git a/src/backend/utils/adt/acl.c b/src/backend/utils/adt/acl.c
index 38cd5b89c999f8d731e049aa6ee46eb6697d97a6..dc6eb2c8aacdb6ac66ef2ce25f8e343bd4588d9f 100644
--- a/src/backend/utils/adt/acl.c
+++ b/src/backend/utils/adt/acl.c
@@ -117,7 +117,6 @@ static AclMode convert_role_priv_string(text *priv_type_text);
 static AclResult pg_role_aclcheck(Oid role_oid, Oid roleid, AclMode mode);
 
 static void RoleMembershipCacheCallback(Datum arg, int cacheid, uint32 hashvalue);
-static Oid	get_role_oid_or_public(const char *rolname);
 
 
 /*
@@ -5126,7 +5125,7 @@ get_role_oid(const char *rolname, bool missing_ok)
  * get_role_oid_or_public - As above, but return ACL_ID_PUBLIC if the
  *		role name is "public".
  */
-static Oid
+Oid
 get_role_oid_or_public(const char *rolname)
 {
 	if (strcmp(rolname, "public") == 0)
diff --git a/src/backend/utils/adt/ri_triggers.c b/src/backend/utils/adt/ri_triggers.c
index e4d7b2c34b6501e0eb2fb0b0bdbdbeaf46bcfabc..ed4a3769e45cfdfdb55cd9f57fdab84a89c93312 100644
--- a/src/backend/utils/adt/ri_triggers.c
+++ b/src/backend/utils/adt/ri_triggers.c
@@ -2303,6 +2303,18 @@ RI_Initial_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
 	if (!ExecCheckRTPerms(list_make2(fkrte, pkrte), false))
 		return false;
 
+	/*
+	 * Also punt if RLS is enabled on either table unless this role has the
+	 * bypassrls right or is the table owner of the table(s) involved which
+	 * have RLS enabled.
+	 */
+	if (!has_bypassrls_privilege(GetUserId()) &&
+		((pk_rel->rd_rel->relhasrowsecurity &&
+		  !pg_class_ownercheck(pkrte->relid, GetUserId())) ||
+		 (fk_rel->rd_rel->relhasrowsecurity &&
+		  !pg_class_ownercheck(fkrte->relid, GetUserId()))))
+		return false;
+
 	/*----------
 	 * The query string built is:
 	 *	SELECT fk.keycols FROM ONLY relname fk
@@ -2956,6 +2968,7 @@ ri_PlanCheck(const char *querystr, int nargs, Oid *argtypes,
 	Relation	query_rel;
 	Oid			save_userid;
 	int			save_sec_context;
+	int			temp_sec_context;
 
 	/*
 	 * Use the query type code to determine whether the query is run against
@@ -2968,8 +2981,22 @@ ri_PlanCheck(const char *querystr, int nargs, Oid *argtypes,
 
 	/* Switch to proper UID to perform check as */
 	GetUserIdAndSecContext(&save_userid, &save_sec_context);
+
+	/*
+	 * Row-level security should be disabled in the case where a foreign-key
+	 * relation is queried to check existence of tuples that references the
+	 * primary-key being modified.
+	 */
+	temp_sec_context = save_sec_context | SECURITY_LOCAL_USERID_CHANGE;
+	if (qkey->constr_queryno == RI_PLAN_CHECK_LOOKUPPK
+		|| qkey->constr_queryno == RI_PLAN_CHECK_LOOKUPPK_FROM_PK
+		|| qkey->constr_queryno == RI_PLAN_RESTRICT_DEL_CHECKREF
+		|| qkey->constr_queryno == RI_PLAN_RESTRICT_UPD_CHECKREF)
+		temp_sec_context |= SECURITY_ROW_LEVEL_DISABLED;
+
+
 	SetUserIdAndSecContext(RelationGetForm(query_rel)->relowner,
-						   save_sec_context | SECURITY_LOCAL_USERID_CHANGE);
+						   temp_sec_context);
 
 	/* Create the plan */
 	qplan = SPI_prepare(querystr, nargs, argtypes);
diff --git a/src/backend/utils/cache/plancache.c b/src/backend/utils/cache/plancache.c
index d03d3b3cdfff0291583234146f2a0b3a5c34a150..26ef2fa6ff16fab552424f0d4f623ff81479fc5e 100644
--- a/src/backend/utils/cache/plancache.c
+++ b/src/backend/utils/cache/plancache.c
@@ -53,12 +53,14 @@
 #include "catalog/namespace.h"
 #include "executor/executor.h"
 #include "executor/spi.h"
+#include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/cost.h"
 #include "optimizer/planmain.h"
 #include "optimizer/prep.h"
 #include "parser/analyze.h"
 #include "parser/parsetree.h"
+#include "rewrite/rowsecurity.h"
 #include "storage/lmgr.h"
 #include "tcop/pquery.h"
 #include "tcop/utility.h"
@@ -151,6 +153,8 @@ CreateCachedPlan(Node *raw_parse_tree,
 	CachedPlanSource *plansource;
 	MemoryContext source_context;
 	MemoryContext oldcxt;
+	Oid user_id;
+	int security_context;
 
 	Assert(query_string != NULL);		/* required as of 8.4 */
 
@@ -173,6 +177,8 @@ CreateCachedPlan(Node *raw_parse_tree,
 	 */
 	oldcxt = MemoryContextSwitchTo(source_context);
 
+	GetUserIdAndSecContext(&user_id, &security_context);
+
 	plansource = (CachedPlanSource *) palloc0(sizeof(CachedPlanSource));
 	plansource->magic = CACHEDPLANSOURCE_MAGIC;
 	plansource->raw_parse_tree = copyObject(raw_parse_tree);
@@ -201,6 +207,11 @@ CreateCachedPlan(Node *raw_parse_tree,
 	plansource->generic_cost = -1;
 	plansource->total_custom_cost = 0;
 	plansource->num_custom_plans = 0;
+	plansource->has_rls = false;
+	plansource->rowSecurityDisabled
+		= (security_context & SECURITY_ROW_LEVEL_DISABLED) != 0;
+	plansource->row_security_env = row_security;
+	plansource->planUserId = InvalidOid;
 
 	MemoryContextSwitchTo(oldcxt);
 
@@ -371,7 +382,8 @@ CompleteCachedPlan(CachedPlanSource *plansource,
 		 */
 		extract_query_dependencies((Node *) querytree_list,
 								   &plansource->relationOids,
-								   &plansource->invalItems);
+								   &plansource->invalItems,
+								   &plansource->has_rls);
 
 		/*
 		 * Also save the current search_path in the query_context.  (This
@@ -565,6 +577,17 @@ RevalidateCachedQuery(CachedPlanSource *plansource)
 		return NIL;
 	}
 
+	/*
+	 * If this is a new cached plan, then set the user id it was planned by
+	 * and under what row security settings; these are needed to determine
+	 * plan invalidation when RLS is involved.
+	 */
+	if (!OidIsValid(plansource->planUserId))
+	{
+		plansource->planUserId = GetUserId();
+		plansource->row_security_env = row_security;
+	}
+
 	/*
 	 * If the query is currently valid, we should have a saved search_path ---
 	 * check to see if that matches the current environment.  If not, we want
@@ -582,6 +605,23 @@ RevalidateCachedQuery(CachedPlanSource *plansource)
 		}
 	}
 
+	/*
+	 * Check if row security is enabled for this query and things have changed
+	 * such that we need to invalidate this plan and rebuild it.  Note that if
+	 * row security was explicitly disabled (eg: this is a FK check plan) then
+	 * we don't invalidate due to RLS.
+	 *
+	 * Otherwise, if the plan has a possible RLS dependency, force a replan if
+	 * either the role under which the plan was planned or the row_security
+	 * setting has been changed.
+	 */
+	if (plansource->is_valid
+		&& !plansource->rowSecurityDisabled
+		&& plansource->has_rls
+		&& (plansource->planUserId != GetUserId()
+			|| plansource->row_security_env != row_security))
+		plansource->is_valid = false;
+
 	/*
 	 * If the query is currently valid, acquire locks on the referenced
 	 * objects; then check again.  We need to do it this way to cover the race
@@ -723,7 +763,8 @@ RevalidateCachedQuery(CachedPlanSource *plansource)
 	 */
 	extract_query_dependencies((Node *) qlist,
 							   &plansource->relationOids,
-							   &plansource->invalItems);
+							   &plansource->invalItems,
+							   &plansource->has_rls);
 
 	/*
 	 * Also save the current search_path in the query_context.  (This should
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index b6f03df6a43c66a44e38e6917239ab7854c7c9bb..e7f7129bd9dada7a62aa266e4bac2b1edc788ae2 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -55,6 +55,7 @@
 #include "catalog/pg_type.h"
 #include "catalog/schemapg.h"
 #include "catalog/storage.h"
+#include "commands/policy.h"
 #include "commands/trigger.h"
 #include "miscadmin.h"
 #include "optimizer/clauses.h"
@@ -966,6 +967,11 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
 	else
 		relation->trigdesc = NULL;
 
+	if (relation->rd_rel->relhasrowsecurity)
+		RelationBuildRowSecurity(relation);
+	else
+		relation->rsdesc = NULL;
+
 	/*
 	 * if it's an index, initialize index-related information
 	 */
@@ -1936,6 +1942,8 @@ RelationDestroyRelation(Relation relation, bool remember_tupdesc)
 		MemoryContextDelete(relation->rd_indexcxt);
 	if (relation->rd_rulescxt)
 		MemoryContextDelete(relation->rd_rulescxt);
+	if (relation->rsdesc)
+		MemoryContextDelete(relation->rsdesc->rscxt);
 	if (relation->rd_fdwroutine)
 		pfree(relation->rd_fdwroutine);
 	pfree(relation);
@@ -3242,8 +3250,8 @@ RelationCacheInitializePhase3(void)
 	 * wrong in the results from formrdesc or the relcache cache file. If we
 	 * faked up relcache entries using formrdesc, then read the real pg_class
 	 * rows and replace the fake entries with them. Also, if any of the
-	 * relcache entries have rules or triggers, load that info the hard way
-	 * since it isn't recorded in the cache file.
+	 * relcache entries have rules, triggers, or security policies, load that
+	 * info the hard way since it isn't recorded in the cache file.
 	 *
 	 * Whenever we access the catalogs to read data, there is a possibility of
 	 * a shared-inval cache flush causing relcache entries to be removed.
@@ -3334,6 +3342,21 @@ RelationCacheInitializePhase3(void)
 			restart = true;
 		}
 
+		/*
+		 * 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-
+		 * 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)
+		{
+			RelationBuildRowSecurity(relation);
+
+			Assert (relation->rsdesc != NULL);
+			restart = true;
+		}
+
 		/* Release hold on the relation */
 		RelationDecrementReferenceCount(relation);
 
@@ -4706,6 +4729,7 @@ load_relcache_init_file(bool shared)
 		rel->rd_rules = NULL;
 		rel->rd_rulescxt = NULL;
 		rel->trigdesc = NULL;
+		rel->rsdesc = NULL;
 		rel->rd_indexprs = NIL;
 		rel->rd_indpred = NIL;
 		rel->rd_exclops = NULL;
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index b87bfb3ff00c236e59cbf3a69d88f9ff4a7bbe3b..d2083142589d2dbcb7a9fde61942a077976918e3 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -61,6 +61,7 @@
 #include "replication/syncrep.h"
 #include "replication/walreceiver.h"
 #include "replication/walsender.h"
+#include "rewrite/rowsecurity.h"
 #include "storage/bufmgr.h"
 #include "storage/dsm_impl.h"
 #include "storage/standby.h"
@@ -400,6 +401,23 @@ static const struct config_enum_entry huge_pages_options[] = {
 	{NULL, 0, false}
 };
 
+/*
+ * Although only "on", "off", and "force" are documented, we
+ * accept all the likely variants of "on" and "off".
+ */
+static const struct config_enum_entry row_security_options[] = {
+	{"on", ROW_SECURITY_ON, false},
+	{"off", ROW_SECURITY_OFF, false},
+	{"force", ROW_SECURITY_FORCE, false},
+	{"true", ROW_SECURITY_ON, true},
+	{"false", ROW_SECURITY_OFF, true},
+	{"yes", ROW_SECURITY_ON, true},
+	{"no", ROW_SECURITY_OFF, true},
+	{"1", ROW_SECURITY_ON, true},
+	{"0", ROW_SECURITY_OFF, true},
+	{NULL, 0, false}
+};
+
 /*
  * Options for enum values stored in other modules
  */
@@ -456,6 +474,8 @@ int			tcp_keepalives_idle;
 int			tcp_keepalives_interval;
 int			tcp_keepalives_count;
 
+int			row_security = true;
+
 /*
  * This really belongs in pg_shmem.c, but is defined here so that it doesn't
  * need to be duplicated in all the different implementations of pg_shmem.c.
@@ -3517,6 +3537,16 @@ static struct config_enum ConfigureNamesEnum[] =
 		NULL, NULL, NULL
 	},
 
+	{
+		{"row_security", PGC_USERSET, CONN_AUTH_SECURITY,
+			gettext_noop("Enable row security."),
+			gettext_noop("When enabled, row security will be applied to all users.")
+		},
+		&row_security,
+		ROW_SECURITY_ON, row_security_options,
+		NULL, NULL, NULL
+	},
+
 	/* End-of-list marker */
 	{
 		{NULL, 0, 0, NULL, NULL}, NULL, 0, NULL, NULL, NULL, NULL
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index 1b5f39fa6c1f94a127e5ea09fdfb97be8e622cb9..485d5d4b5c4a18c3cd5fe0fa3104a8ed5be7f395 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -90,6 +90,7 @@
 #ssl_crl_file = ''			# (change requires restart)
 #password_encryption = on
 #db_user_namespace = off
+#row_security = on
 
 # GSSAPI using Kerberos
 #krb_server_keyfile = ''
diff --git a/src/bin/pg_dump/common.c b/src/bin/pg_dump/common.c
index 94e9147b133b8ebc509af3613d96fedda3dccb2d..2f855cf706694a4bf8a7a1299fe2de78a972d7dd 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.h b/src/bin/pg_dump/pg_backup.h
index 25780cfc1a0b57b06e36f5739c8ed05664d1829c..921bc1ba365927ad616531d38ba33f9176a7ba3a 100644
--- a/src/bin/pg_dump/pg_backup.h
+++ b/src/bin/pg_dump/pg_backup.h
@@ -150,6 +150,7 @@ typedef struct _restoreOptions
 	bool		single_txn;
 
 	bool	   *idWanted;		/* array showing which dump IDs to emit */
+	int			enable_row_security;
 } RestoreOptions;
 
 typedef void (*SetupWorkerPtr) (Archive *AH, RestoreOptions *ropt);
diff --git a/src/bin/pg_dump/pg_backup_archiver.c b/src/bin/pg_dump/pg_backup_archiver.c
index ded9135c36066ce1fed915702a121cd18746fe9a..5476a1e7e2b5a811d5ce7b5e5c4a4551a3770002 100644
--- a/src/bin/pg_dump/pg_backup_archiver.c
+++ b/src/bin/pg_dump/pg_backup_archiver.c
@@ -373,6 +373,14 @@ RestoreArchive(Archive *AHX)
 			ahprintf(AH, "BEGIN;\n\n");
 	}
 
+	/*
+	 * 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");
+
 	/*
 	 * Establish important parameter values right away.
 	 */
@@ -3242,6 +3250,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 c084ee9d9e2a3b885df4174daceaf2cd5853bad2..29153294e262fcb9ca83b7e49d4ec62a2a6135f5 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -137,6 +137,7 @@ static int	no_security_labels = 0;
 static int	no_synchronized_snapshots = 0;
 static int	no_unlogged_table_data = 0;
 static int	serializable_deferrable = 0;
+static int	enable_row_security = 0;
 
 
 static void help(const char *progname);
@@ -247,6 +248,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);
@@ -345,6 +347,7 @@ main(int argc, char **argv)
 		{"column-inserts", no_argument, &column_inserts, 1},
 		{"disable-dollar-quoting", no_argument, &disable_dollar_quoting, 1},
 		{"disable-triggers", no_argument, &disable_triggers, 1},
+		{"enable-row-security", no_argument, &enable_row_security, 1},
 		{"exclude-table-data", required_argument, NULL, 4},
 		{"if-exists", no_argument, &if_exists, 1},
 		{"inserts", no_argument, &dump_inserts, 1},
@@ -825,6 +828,7 @@ main(int argc, char **argv)
 	ropt->noTablespace = outputNoTablespaces;
 	ropt->disable_triggers = disable_triggers;
 	ropt->use_setsessauth = use_setsessauth;
+	ropt->enable_row_security = enable_row_security;
 
 	if (compressLevel == -1)
 		ropt->compression = 0;
@@ -897,6 +901,7 @@ help(const char *progname)
 	printf(_("  --column-inserts             dump data as INSERT commands with column names\n"));
 	printf(_("  --disable-dollar-quoting     disable dollar quoting, use SQL standard quoting\n"));
 	printf(_("  --disable-triggers           disable triggers during data-only restore\n"));
+	printf(_("  --enable-row-security        enable row level security\n"));
 	printf(_("  --exclude-table-data=TABLE   do NOT dump data for the named table(s)\n"));
 	printf(_("  --if-exists                  use IF EXISTS when dropping objects\n"));
 	printf(_("  --inserts                    dump data as INSERT commands, rather than COPY\n"));
@@ -1050,6 +1055,14 @@ setup_connection(Archive *AH, const char *dumpencoding, char *use_role)
 		else
 			AH->sync_snapshot_id = get_synchronized_snapshot(AH);
 	}
+
+	if (AH->remoteVersion >= 90500)
+	{
+		if (enable_row_security)
+			ExecuteSqlStatement(AH, "SET row_security TO ON");
+		else
+			ExecuteSqlStatement(AH, "SET row_security TO OFF");
+	}
 }
 
 static void
@@ -2757,6 +2770,240 @@ 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_rsecpolname;
+	int				i_rseccmd;
+	int				i_rsecroles;
+	int				i_rsecqual;
+	int				i_rsecwithcheck;
+	int				i, j, ntups;
+
+	if (fout->remoteVersion < 90500)
+		return;
+
+	for (i = 0; i < numTables; i++)
+	{
+		TableInfo *tbinfo = &tblinfo[i];
+
+		/* Ignore row-security on tables not to be dumped */
+		if (!tbinfo->dobj.dump)
+			continue;
+
+		if (g_verbose)
+			write_msg(NULL, "reading row-security enabled for table \"%s\"",
+					  tbinfo->dobj.name);
+
+		/*
+		 * Get row-security enabled information for the table.
+		 * We represent RLS enabled on a table by creating RowSecurityInfo
+		 * object with an empty policy.
+		 */
+		if (tbinfo->hasrowsec)
+		{
+			/*
+			 * Note: use tableoid 0 so that this object won't be mistaken for
+			 * something that pg_depend entries apply to.
+			 */
+			rsinfo = pg_malloc(sizeof(RowSecurityInfo));
+			rsinfo->dobj.objType = DO_ROW_SECURITY;
+			rsinfo->dobj.catId.tableoid = 0;
+			rsinfo->dobj.catId.oid = tbinfo->dobj.catId.oid;
+			AssignDumpId(&rsinfo->dobj);
+			rsinfo->dobj.namespace = tbinfo->dobj.namespace;
+			rsinfo->dobj.name = pg_strdup(tbinfo->dobj.name);
+			rsinfo->rstable = tbinfo;
+			rsinfo->rsecpolname = NULL;
+			rsinfo->rseccmd = NULL;
+			rsinfo->rsecroles = NULL;
+			rsinfo->rsecqual = NULL;
+			rsinfo->rsecwithcheck = NULL;
+		}
+
+		if (g_verbose)
+			write_msg(NULL, "reading row-security policies 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);
+
+		/* Get the policies for the table. */
+		appendPQExpBuffer(query,
+						  "SELECT oid, tableoid, s.rsecpolname, s.rseccmd, "
+						  "CASE WHEN s.rsecroles = '{0}' THEN 'PUBLIC' ELSE "
+						  "   array_to_string(ARRAY(SELECT rolname from pg_roles WHERE oid = ANY(s.rsecroles)), ', ') END AS rsecroles, "
+						  "pg_get_expr(s.rsecqual, s.rsecrelid) AS rsecqual, "
+						  "pg_get_expr(s.rsecwithcheck, s.rsecrelid) AS rsecwithcheck "
+						  "FROM pg_catalog.pg_rowsecurity s "
+						  "WHERE rsecrelid = '%u'",
+						  tbinfo->dobj.catId.oid);
+		res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
+
+		ntups = PQntuples(res);
+
+		if (ntups == 0)
+		{
+			/*
+			 * No explicit policies to handle (only the default-deny policy,
+			 * which is handled as part of the table definition.  Clean up and
+			 * return.
+			 */
+			PQclear(res);
+			continue;
+		}
+
+		i_oid = PQfnumber(res, "oid");
+		i_tableoid = PQfnumber(res, "tableoid");
+		i_rsecpolname = PQfnumber(res, "rsecpolname");
+		i_rseccmd = PQfnumber(res, "rseccmd");
+		i_rsecroles = PQfnumber(res, "rsecroles");
+		i_rsecqual = PQfnumber(res, "rsecqual");
+		i_rsecwithcheck = PQfnumber(res, "rsecwithcheck");
+
+		rsinfo = pg_malloc(ntups * sizeof(RowSecurityInfo));
+
+		for (j = 0; j < ntups; j++)
+		{
+			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);
+			rsinfo[j].dobj.namespace = tbinfo->dobj.namespace;
+			rsinfo[j].rstable = tbinfo;
+			rsinfo[j].rsecpolname = pg_strdup(PQgetvalue(res, j,
+														 i_rsecpolname));
+
+			rsinfo[j].dobj.name = pg_strdup(rsinfo[j].rsecpolname);
+
+			if (PQgetisnull(res, j, i_rseccmd))
+				rsinfo[j].rseccmd = NULL;
+			else
+				rsinfo[j].rseccmd = pg_strdup(PQgetvalue(res, j, i_rseccmd));
+
+			rsinfo[j].rsecroles = pg_strdup(PQgetvalue(res, j, i_rsecroles));
+
+			if (PQgetisnull(res, j, i_rsecqual))
+				rsinfo[j].rsecqual = NULL;
+			else
+				rsinfo[j].rsecqual = pg_strdup(PQgetvalue(res, j, i_rsecqual));
+
+			if (PQgetisnull(res, j, i_rsecwithcheck))
+				rsinfo[j].rsecwithcheck = NULL;
+			else
+				rsinfo[j].rsecwithcheck
+					= pg_strdup(PQgetvalue(res, j, i_rsecwithcheck));
+		}
+		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)
+		return;
+
+	/*
+	 * If rsecpolname is NULL, then this record is just indicating that ROW
+	 * LEVEL SECURITY is enabled for the table.
+	 * Dump as ALTER TABLE <table> ENABLE ROW LEVEL SECURITY.
+	 */
+	if (rsinfo->rsecpolname == NULL)
+	{
+		query = createPQExpBuffer();
+
+		appendPQExpBuffer(query, "ALTER TABLE %s ENABLE ROW LEVEL SECURITY;",
+						  fmtId(rsinfo->dobj.name));
+
+		ArchiveEntry(fout, rsinfo->dobj.catId, rsinfo->dobj.dumpId,
+					 rsinfo->dobj.name,
+					 rsinfo->dobj.namespace->dobj.name,
+					 NULL,
+					 tbinfo->rolname, false,
+					 "ROW SECURITY", SECTION_NONE,
+					 query->data, "", NULL,
+					 NULL, 0,
+					 NULL, NULL);
+
+		destroyPQExpBuffer(query);
+		return;
+	}
+
+	if (!rsinfo->rseccmd)
+		cmd = "ALL";
+	else if (strcmp(rsinfo->rseccmd, "r") == 0)
+		cmd = "SELECT";
+	else if (strcmp(rsinfo->rseccmd, "a") == 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);
+	}
+
+	query = createPQExpBuffer();
+	delqry = createPQExpBuffer();
+
+	appendPQExpBuffer(query, "CREATE POLICY %s ON %s FOR %s",
+					  rsinfo->rsecpolname, fmtId(tbinfo->dobj.name), cmd);
+
+	if (rsinfo->rsecroles != NULL)
+		appendPQExpBuffer(query, " TO %s", rsinfo->rsecroles);
+
+	if (rsinfo->rsecqual != NULL)
+		appendPQExpBuffer(query, " USING %s", rsinfo->rsecqual);
+
+	if (rsinfo->rsecwithcheck != NULL)
+		appendPQExpBuffer(query, " WITH CHECK %s", rsinfo->rsecwithcheck);
+
+	appendPQExpBuffer(query, ";\n");
+
+	appendPQExpBuffer(delqry, "DROP POLICY %s ON %s;\n",
+					  rsinfo->rsecpolname, fmtId(tbinfo->dobj.name));
+
+	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);
+	destroyPQExpBuffer(delqry);
+}
+
 static void
 binary_upgrade_set_type_oids_by_type_oid(Archive *fout,
 										 PQExpBuffer upgrade_buffer,
@@ -4287,6 +4534,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_relminmxid;
@@ -4328,7 +4576,48 @@ getTables(Archive *fout, int *numTables)
 	 * we cannot correctly identify inherited columns, owned sequences, etc.
 	 */
 
-	if (fout->remoteVersion >= 90400)
+	if (fout->remoteVersion >= 90500)
+	{
+		/*
+		 * Left join to pick up dependency info linking sequences to their
+		 * owning column, if any (note this dependency is AUTO as of 8.2)
+		 */
+		appendPQExpBuffer(query,
+						  "SELECT c.tableoid, c.oid, c.relname, "
+						  "c.relacl, c.relkind, c.relnamespace, "
+						  "(%s c.relowner) AS rolname, "
+						  "c.relchecks, c.relhastriggers, "
+						  "c.relhasindex, c.relhasrules, c.relhasoids, "
+						  "c.relhasrowsecurity, "
+						  "c.relfrozenxid, c.relminmxid, tc.oid AS toid, "
+						  "tc.relfrozenxid AS tfrozenxid, "
+						  "tc.relminmxid AS tminmxid, "
+						  "c.relpersistence, c.relispopulated, "
+						  "c.relreplident, c.relpages, "
+						  "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, "
+						  "(SELECT spcname FROM pg_tablespace t WHERE t.oid = c.reltablespace) AS reltablespace, "
+						  "array_to_string(array_remove(array_remove(c.reloptions,'check_option=local'),'check_option=cascaded'), ', ') AS reloptions, "
+						  "CASE WHEN 'check_option=local' = ANY (c.reloptions) THEN 'LOCAL'::text "
+						  "WHEN 'check_option=cascaded' = ANY (c.reloptions) THEN 'CASCADED'::text ELSE NULL END AS checkoption, "
+						  "array_to_string(array(SELECT 'toast.' || x FROM unnest(tc.reloptions) x), ', ') AS toast_reloptions "
+						  "FROM pg_class c "
+						  "LEFT JOIN pg_depend d ON "
+						  "(c.relkind = '%c' AND "
+						  "d.classid = c.tableoid AND d.objid = c.oid AND "
+						  "d.objsubid = 0 AND "
+						  "d.refclassid = c.tableoid AND d.deptype = 'a') "
+					   "LEFT JOIN pg_class tc ON (c.reltoastrelid = tc.oid) "
+				   "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c') "
+						  "ORDER BY c.oid",
+						  username_subquery,
+						  RELKIND_SEQUENCE,
+						  RELKIND_RELATION, RELKIND_SEQUENCE,
+						  RELKIND_VIEW, RELKIND_COMPOSITE_TYPE,
+						  RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+	}
+	else if (fout->remoteVersion >= 90400)
 	{
 		/*
 		 * Left join to pick up dependency info linking sequences to their
@@ -4340,6 +4629,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, c.relminmxid, tc.oid AS toid, "
 						  "tc.relfrozenxid AS tfrozenxid, "
 						  "tc.relminmxid AS tminmxid, "
@@ -4380,6 +4670,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, c.relminmxid, tc.oid AS toid, "
 						  "tc.relfrozenxid AS tfrozenxid, "
 						  "tc.relminmxid AS tminmxid, "
@@ -4420,6 +4711,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, 0 AS relminmxid, tc.oid AS toid, "
 						  "tc.relfrozenxid AS tfrozenxid, "
 						  "0 AS tminmxid, "
@@ -4458,6 +4750,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, 0 AS relminmxid, tc.oid AS toid, "
 						  "tc.relfrozenxid AS tfrozenxid, "
 						  "0 AS tminmxid, "
@@ -4495,6 +4788,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, 0 AS relminmxid, tc.oid AS toid, "
 						  "tc.relfrozenxid AS tfrozenxid, "
 						  "0 AS tminmxid, "
@@ -4532,6 +4826,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, 0 AS relminmxid, tc.oid AS toid, "
 						  "tc.relfrozenxid AS tfrozenxid, "
 						  "0 AS tminmxid, "
@@ -4569,6 +4864,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 relminmxid,"
 						  "0 AS toid, "
 						  "0 AS tfrozenxid, 0 AS tminmxid,"
@@ -4605,6 +4901,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 relminmxid,"
 						  "0 AS toid, "
 						  "0 AS tfrozenxid, 0 AS tminmxid,"
@@ -4637,6 +4934,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 relminmxid,"
 						  "0 AS toid, "
 						  "0 AS tfrozenxid, 0 AS tminmxid,"
@@ -4664,6 +4962,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 relminmxid,"
 						  "0 AS toid, "
 						  "0 AS tfrozenxid, 0 AS tminmxid,"
@@ -4701,6 +5000,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 relminmxid,"
 						  "0 AS toid, "
 						  "0 AS tfrozenxid, 0 AS tminmxid,"
@@ -4748,6 +5048,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_relminmxid = PQfnumber(res, "relminmxid");
@@ -4799,6 +5100,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));
@@ -7930,6 +8232,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 */
@@ -15332,6 +15637,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 d18418758070286a4abf3b326f5849e2d2be01c5..b5d820e7616707e8e66b14b75ac7e0a8d0a45c93 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 */
 	uint32		minmxid;		/* for restore min multi xid */
@@ -486,6 +488,23 @@ typedef struct _blobInfo
 	char	   *blobacl;
 } BlobInfo;
 
+/*
+ * The RowSecurityInfo struct is used to represent row policies on a table and
+ * to indicate if a table has RLS enabled (ENABLE ROW SECURITY).  If
+ * rsecpolname is NULL, then the record indicates ENABLE ROW SECURITY, while if
+ * it's non-NULL then this is a regular policy definition.
+ */
+typedef struct _rowSecurityInfo
+{
+	DumpableObject  dobj;
+	TableInfo	   *rstable;
+	char		   *rsecpolname;	/* null indicates RLS is enabled on rel */
+	char		   *rseccmd;
+	char		   *rsecroles;
+	char		   *rsecqual;
+	char		   *rsecwithcheck;
+} RowSecurityInfo;
+
 /* global decls */
 extern bool force_quotes;		/* double-quotes for identifiers flag */
 extern bool g_verbose;			/* verbose flag */
@@ -577,5 +596,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 f0caa6b6599e1ec469a8de007e9f526865824e6e..90aedee7d23525b4254d264f0ef89346bdfe3479 100644
--- a/src/bin/pg_dump/pg_dump_sort.c
+++ b/src/bin/pg_dump/pg_dump_sort.c
@@ -70,7 +70,8 @@ static const int oldObjectTypePriority[] =
 	10,							/* DO_PRE_DATA_BOUNDARY */
 	13,							/* DO_POST_DATA_BOUNDARY */
 	20,							/* DO_EVENT_TRIGGER */
-	15							/* DO_REFRESH_MATVIEW */
+	15,							/* DO_REFRESH_MATVIEW */
+	21							/* DO_ROW_SECURITY */
 };
 
 /*
@@ -118,7 +119,8 @@ static const int newObjectTypePriority[] =
 	22,							/* DO_PRE_DATA_BOUNDARY */
 	25,							/* DO_POST_DATA_BOUNDARY */
 	32,							/* DO_EVENT_TRIGGER */
-	33							/* DO_REFRESH_MATVIEW */
+	33,							/* DO_REFRESH_MATVIEW */
+	34							/* DO_ROW_SECURITY */
 };
 
 static DumpId preDataBoundId;
@@ -1434,6 +1436,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)",
diff --git a/src/bin/pg_dump/pg_dumpall.c b/src/bin/pg_dump/pg_dumpall.c
index b2b3e6feb74d5d577dfee4101b1997a33cc15a60..c25ea851d3ef362389293e326baa401ad945fbe7 100644
--- a/src/bin/pg_dump/pg_dumpall.c
+++ b/src/bin/pg_dump/pg_dumpall.c
@@ -663,17 +663,29 @@ dumpRoles(PGconn *conn)
 				i_rolpassword,
 				i_rolvaliduntil,
 				i_rolreplication,
+				i_rolbypassrls,
 				i_rolcomment,
 				i_is_current_user;
 	int			i;
 
 	/* note: rolconfig is dumped later */
-	if (server_version >= 90100)
+	if (server_version >= 90500)
+		printfPQExpBuffer(buf,
+						  "SELECT oid, rolname, rolsuper, rolinherit, "
+						  "rolcreaterole, rolcreatedb, "
+						  "rolcanlogin, rolconnlimit, rolpassword, "
+						  "rolvaliduntil, rolreplication, rolbypassrls, "
+			 "pg_catalog.shobj_description(oid, 'pg_authid') as rolcomment, "
+						  "rolname = current_user AS is_current_user "
+						  "FROM pg_authid "
+						  "ORDER BY 2");
+	else if (server_version >= 90100)
 		printfPQExpBuffer(buf,
 						  "SELECT oid, rolname, rolsuper, rolinherit, "
 						  "rolcreaterole, rolcreatedb, "
 						  "rolcanlogin, rolconnlimit, rolpassword, "
 						  "rolvaliduntil, rolreplication, "
+						  "false as rolbypassrls, "
 			 "pg_catalog.shobj_description(oid, 'pg_authid') as rolcomment, "
 						  "rolname = current_user AS is_current_user "
 						  "FROM pg_authid "
@@ -684,6 +696,7 @@ dumpRoles(PGconn *conn)
 						  "rolcreaterole, rolcreatedb, "
 						  "rolcanlogin, rolconnlimit, rolpassword, "
 						  "rolvaliduntil, false as rolreplication, "
+						  "false as rolbypassrls, "
 			 "pg_catalog.shobj_description(oid, 'pg_authid') as rolcomment, "
 						  "rolname = current_user AS is_current_user "
 						  "FROM pg_authid "
@@ -694,6 +707,7 @@ dumpRoles(PGconn *conn)
 						  "rolcreaterole, rolcreatedb, "
 						  "rolcanlogin, rolconnlimit, rolpassword, "
 						  "rolvaliduntil, false as rolreplication, "
+						  "false as rolbypassrls, "
 						  "null as rolcomment, "
 						  "rolname = current_user AS is_current_user "
 						  "FROM pg_authid "
@@ -724,6 +738,7 @@ dumpRoles(PGconn *conn)
 						  "null::text as rolpassword, "
 						  "null::abstime as rolvaliduntil, "
 						  "false as rolreplication, "
+						  "false as rolbypassrls, "
 						  "null as rolcomment, false "
 						  "FROM pg_group "
 						  "WHERE NOT EXISTS (SELECT 1 FROM pg_shadow "
@@ -743,6 +758,7 @@ dumpRoles(PGconn *conn)
 	i_rolpassword = PQfnumber(res, "rolpassword");
 	i_rolvaliduntil = PQfnumber(res, "rolvaliduntil");
 	i_rolreplication = PQfnumber(res, "rolreplication");
+	i_rolbypassrls = PQfnumber(res, "rolbypassrls");
 	i_rolcomment = PQfnumber(res, "rolcomment");
 	i_is_current_user = PQfnumber(res, "is_current_user");
 
@@ -810,6 +826,11 @@ dumpRoles(PGconn *conn)
 		else
 			appendPQExpBufferStr(buf, " NOREPLICATION");
 
+		if (strcmp(PQgetvalue(res, i, i_rolbypassrls), "t") == 0)
+			appendPQExpBufferStr(buf, " BYPASSRLS");
+		else
+			appendPQExpBufferStr(buf, " NOBYPASSRLS");
+
 		if (strcmp(PQgetvalue(res, i, i_rolconnlimit), "-1") != 0)
 			appendPQExpBuffer(buf, " CONNECTION LIMIT %s",
 							  PQgetvalue(res, i, i_rolconnlimit));
diff --git a/src/bin/pg_dump/pg_restore.c b/src/bin/pg_dump/pg_restore.c
index fdfdc19c3fb44d92e5c253ace2fbaa5b7367894e..1c1b80f13744e9b79d23396f97b37a892bbae36c 100644
--- a/src/bin/pg_dump/pg_restore.c
+++ b/src/bin/pg_dump/pg_restore.c
@@ -70,6 +70,7 @@ main(int argc, char **argv)
 	Archive    *AH;
 	char	   *inputFileSpec;
 	static int	disable_triggers = 0;
+	static int	enable_row_security = 0;
 	static int	if_exists = 0;
 	static int	no_data_for_failed_tables = 0;
 	static int	outputNoTablespaces = 0;
@@ -111,6 +112,7 @@ main(int argc, char **argv)
 		 * the following options don't have an equivalent short option letter
 		 */
 		{"disable-triggers", no_argument, &disable_triggers, 1},
+		{"enable-row-security", no_argument, &enable_row_security, 1},
 		{"if-exists", no_argument, &if_exists, 1},
 		{"no-data-for-failed-tables", no_argument, &no_data_for_failed_tables, 1},
 		{"no-tablespaces", no_argument, &outputNoTablespaces, 1},
@@ -333,6 +335,7 @@ main(int argc, char **argv)
 	}
 
 	opts->disable_triggers = disable_triggers;
+	opts->enable_row_security = enable_row_security;
 	opts->noDataForFailedTables = no_data_for_failed_tables;
 	opts->noTablespace = outputNoTablespaces;
 	opts->use_setsessauth = use_setsessauth;
@@ -460,6 +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(_("  --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 282cd432a27fc0a84e98666d62d0305b1040f350..97dc2dded2cc5a75cf5117d89a90b5552e021625 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -742,7 +742,7 @@ permissionsList(const char *pattern)
 	PQExpBufferData buf;
 	PGresult   *res;
 	printQueryOpt myopt = pset.popt;
-	static const bool translate_columns[] = {false, false, true, false, false};
+	static const bool translate_columns[] = {false, false, true, false, false, false};
 
 	initPQExpBuffer(&buf);
 
@@ -778,7 +778,38 @@ permissionsList(const char *pattern)
 						  "    FROM pg_catalog.pg_attribute a\n"
 						  "    WHERE attrelid = c.oid AND NOT attisdropped AND attacl IS NOT NULL\n"
 						  "  ), E'\\n') AS \"%s\"",
-						  gettext_noop("Column access privileges"));
+						  gettext_noop("Column privileges"));
+
+	if (pset.sversion >= 90500)
+		appendPQExpBuffer(&buf,
+						  ",\n  pg_catalog.array_to_string(ARRAY(\n"
+						  "    SELECT rsecpolname\n"
+						  "    || CASE WHEN rseccmd IS NOT NULL THEN\n"
+						  "           E' (' || rseccmd || E')'\n"
+						  "       ELSE E':' \n"
+						  "       END\n"
+						  "    || CASE WHEN rs.rsecqual IS NOT NULL THEN\n"
+						  "           E'\\n  (u): ' || pg_catalog.pg_get_expr(rsecqual, rsecrelid)\n"
+						  "       ELSE E''\n"
+						  "       END\n"
+						  "    || CASE WHEN rsecwithcheck IS NOT NULL THEN\n"
+						  "           E'\\n  (c): ' || pg_catalog.pg_get_expr(rsecwithcheck, rsecrelid)\n"
+						  "       ELSE E''\n"
+						  "       END"
+						  "    || CASE WHEN rs.rsecroles <> '{0}' THEN\n"
+						  "           E'\\n  to: ' || pg_catalog.array_to_string(\n"
+						  "               ARRAY(\n"
+						  "                   SELECT rolname\n"
+						  "                   FROM pg_catalog.pg_roles\n"
+						  "                   WHERE oid = ANY (rs.rsecroles)\n"
+						  "                   ORDER BY 1\n"
+						  "               ), E', ')\n"
+						  "       ELSE E''\n"
+						  "       END\n"
+						  "    FROM pg_catalog.pg_rowsecurity rs\n"
+						  "    WHERE rsecrelid = c.oid), E'\\n')\n"
+						  "    AS \"%s\"",
+						  gettext_noop("Policies"));
 
 	appendPQExpBufferStr(&buf, "\nFROM pg_catalog.pg_class c\n"
 	   "     LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace\n"
@@ -1173,6 +1204,7 @@ describeOneTableDetails(const char *schemaname,
 		bool		hasindex;
 		bool		hasrules;
 		bool		hastriggers;
+		bool		hasrowsecurity;
 		bool		hasoids;
 		Oid			tablespace;
 		char	   *reloptions;
@@ -1194,11 +1226,28 @@ describeOneTableDetails(const char *schemaname,
 	initPQExpBuffer(&tmpbuf);
 
 	/* Get general table info */
-	if (pset.sversion >= 90400)
+	if (pset.sversion >= 90500)
 	{
 		printfPQExpBuffer(&buf,
 			  "SELECT c.relchecks, c.relkind, c.relhasindex, c.relhasrules, "
-						  "c.relhastriggers, c.relhasoids, "
+						  "c.relhastriggers, c.relhasrowsecurity, 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"
+						  "FROM pg_catalog.pg_class c\n "
+		   "LEFT JOIN pg_catalog.pg_class tc ON (c.reltoastrelid = tc.oid)\n"
+						  "WHERE c.oid = '%s';",
+						  (verbose ?
+						   "pg_catalog.array_to_string(c.reloptions || "
+						   "array(select 'toast.' || x from pg_catalog.unnest(tc.reloptions) x), ', ')\n"
+						   : "''"),
+						  oid);
+	}
+	else if (pset.sversion >= 90400)
+	{
+		printfPQExpBuffer(&buf,
+			  "SELECT c.relchecks, c.relkind, c.relhasindex, c.relhasrules, "
+						  "c.relhastriggers, false, 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"
@@ -1215,7 +1264,7 @@ describeOneTableDetails(const char *schemaname,
 	{
 		printfPQExpBuffer(&buf,
 			  "SELECT c.relchecks, c.relkind, c.relhasindex, c.relhasrules, "
-						  "c.relhastriggers, c.relhasoids, "
+						  "c.relhastriggers, false, c.relhasoids, "
 						  "%s, c.reltablespace, "
 						  "CASE WHEN c.reloftype = 0 THEN '' ELSE c.reloftype::pg_catalog.regtype::pg_catalog.text END, "
 						  "c.relpersistence\n"
@@ -1232,7 +1281,7 @@ describeOneTableDetails(const char *schemaname,
 	{
 		printfPQExpBuffer(&buf,
 			  "SELECT c.relchecks, c.relkind, c.relhasindex, c.relhasrules, "
-						  "c.relhastriggers, c.relhasoids, "
+						  "c.relhastriggers, false, c.relhasoids, "
 						  "%s, c.reltablespace, "
 						  "CASE WHEN c.reloftype = 0 THEN '' ELSE c.reloftype::pg_catalog.regtype::pg_catalog.text END\n"
 						  "FROM pg_catalog.pg_class c\n "
@@ -1248,7 +1297,7 @@ describeOneTableDetails(const char *schemaname,
 	{
 		printfPQExpBuffer(&buf,
 			  "SELECT c.relchecks, c.relkind, c.relhasindex, c.relhasrules, "
-						  "c.relhastriggers, c.relhasoids, "
+						  "c.relhastriggers, false, c.relhasoids, "
 						  "%s, c.reltablespace\n"
 						  "FROM pg_catalog.pg_class c\n "
 		   "LEFT JOIN pg_catalog.pg_class tc ON (c.reltoastrelid = tc.oid)\n"
@@ -1263,7 +1312,7 @@ describeOneTableDetails(const char *schemaname,
 	{
 		printfPQExpBuffer(&buf,
 					  "SELECT relchecks, relkind, relhasindex, relhasrules, "
-						  "reltriggers <> 0, relhasoids, "
+						  "reltriggers <> 0, false, relhasoids, "
 						  "%s, reltablespace\n"
 						  "FROM pg_catalog.pg_class WHERE oid = '%s';",
 						  (verbose ?
@@ -1274,7 +1323,7 @@ describeOneTableDetails(const char *schemaname,
 	{
 		printfPQExpBuffer(&buf,
 					  "SELECT relchecks, relkind, relhasindex, relhasrules, "
-						  "reltriggers <> 0, relhasoids, "
+						  "reltriggers <> 0, false, relhasoids, "
 						  "'', reltablespace\n"
 						  "FROM pg_catalog.pg_class WHERE oid = '%s';",
 						  oid);
@@ -1283,7 +1332,7 @@ describeOneTableDetails(const char *schemaname,
 	{
 		printfPQExpBuffer(&buf,
 					  "SELECT relchecks, relkind, relhasindex, relhasrules, "
-						  "reltriggers <> 0, relhasoids, "
+						  "reltriggers <> 0, false, relhasoids, "
 						  "'', ''\n"
 						  "FROM pg_catalog.pg_class WHERE oid = '%s';",
 						  oid);
@@ -1306,18 +1355,19 @@ 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.hasoids = strcmp(PQgetvalue(res, 0, 5), "t") == 0;
+	tableinfo.hasrowsecurity = 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, 6)) : NULL;
+		pg_strdup(PQgetvalue(res, 0, 7)) : NULL;
 	tableinfo.tablespace = (pset.sversion >= 80000) ?
-		atooid(PQgetvalue(res, 0, 7)) : 0;
+		atooid(PQgetvalue(res, 0, 8)) : 0;
 	tableinfo.reloftype = (pset.sversion >= 90000 &&
-						   strcmp(PQgetvalue(res, 0, 8), "") != 0) ?
-		pg_strdup(PQgetvalue(res, 0, 8)) : NULL;
+						   strcmp(PQgetvalue(res, 0, 9), "") != 0) ?
+		pg_strdup(PQgetvalue(res, 0, 9)) : NULL;
 	tableinfo.relpersistence = (pset.sversion >= 90100) ?
-		*(PQgetvalue(res, 0, 9)) : 0;
+		*(PQgetvalue(res, 0, 10)) : 0;
 	tableinfo.relreplident = (pset.sversion >= 90400) ?
-		*(PQgetvalue(res, 0, 10)) : 'd';
+		*(PQgetvalue(res, 0, 11)) : 'd';
 	PQclear(res);
 	res = NULL;
 
@@ -1948,6 +1998,67 @@ describeOneTableDetails(const char *schemaname,
 			PQclear(result);
 		}
 
+
+		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)
+		{
+			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"
+						   "pg_catalog.pg_get_expr(rs.rsecqual, rs.rsecrelid),\n"
+						   "pg_catalog.pg_get_expr(rs.rsecwithcheck, rs.rsecrelid),\n"
+						   "rs.rseccmd AS cmd\n"
+							  "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)
+			{
+				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 (!PQgetisnull(result, i, 2))
+						appendPQExpBuffer(&buf, " EXPRESSION %s",
+										  PQgetvalue(result, i, 2));
+
+					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);
+		}
+
 		/* print rules */
 		if (tableinfo.hasrules && tableinfo.relkind != 'm')
 		{
@@ -2529,6 +2640,11 @@ describeRoles(const char *pattern, bool verbose)
 			appendPQExpBufferStr(&buf, "\n, r.rolreplication");
 		}
 
+		if (pset.sversion >= 90500)
+		{
+			appendPQExpBufferStr(&buf, "\n, r.rolbypassrls");
+		}
+
 		appendPQExpBufferStr(&buf, "\nFROM pg_catalog.pg_roles r\n");
 
 		processSQLNamePattern(pset.db, &buf, pattern, false, false,
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index b80fe13168342e4d3b2e190c43dcafd5ea936435..a4594b6783c7dc1aae20f0b59db8265a2f55f1cf 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -782,6 +782,7 @@ static const pgsql_thing_t words_after_create[] = {
 								 * good idea. */
 	{"OWNED", NULL, NULL, THING_NO_CREATE},		/* for DROP OWNED BY ... */
 	{"PARSER", Query_for_list_of_ts_parsers, NULL, THING_NO_SHOW},
+	{"POLICY", NULL, NULL},
 	{"ROLE", Query_for_list_of_roles},
 	{"RULE", "SELECT pg_catalog.quote_ident(rulename) FROM pg_catalog.pg_rules WHERE substring(pg_catalog.quote_ident(rulename),1,%d)='%s'"},
 	{"SCHEMA", Query_for_list_of_schemas},
@@ -971,7 +972,7 @@ psql_completion(const char *text, int start, int end)
 		{"AGGREGATE", "COLLATION", "CONVERSION", "DATABASE", "DEFAULT PRIVILEGES", "DOMAIN",
 			"EVENT TRIGGER", "EXTENSION", "FOREIGN DATA WRAPPER", "FOREIGN TABLE", "FUNCTION",
 			"GROUP", "INDEX", "LANGUAGE", "LARGE OBJECT", "MATERIALIZED VIEW", "OPERATOR",
-			"ROLE", "RULE", "SCHEMA", "SERVER", "SEQUENCE", "SYSTEM", "TABLE",
+			"POLICY", "ROLE", "RULE", "SCHEMA", "SERVER", "SEQUENCE", "SYSTEM", "TABLE",
 			"TABLESPACE", "TEXT SEARCH", "TRIGGER", "TYPE",
 		"USER", "USER MAPPING FOR", "VIEW", NULL};
 
@@ -1398,6 +1399,44 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(list_ALTERMATVIEW);
 	}
 
+	/* ALTER POLICY <name> ON */
+	else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
+			 pg_strcasecmp(prev2_wd, "POLICY") == 0)
+		COMPLETE_WITH_CONST("ON");
+	/* ALTER POLICY <name> ON <table> */
+	else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
+			 pg_strcasecmp(prev3_wd, "POLICY") == 0 &&
+			 pg_strcasecmp(prev_wd, "ON") == 0)
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
+	/* ALTER POLICY <name> ON <table> - show options */
+	else if (pg_strcasecmp(prev5_wd, "ALTER") == 0 &&
+			 pg_strcasecmp(prev4_wd, "POLICY") == 0 &&
+			 pg_strcasecmp(prev2_wd, "ON") == 0)
+	{
+		static const char *const list_ALTERPOLICY[] =
+		{"RENAME TO", "TO", "USING", "WITH CHECK", NULL};
+
+		COMPLETE_WITH_LIST(list_ALTERPOLICY);
+	}
+	/* ALTER POLICY <name> ON <table> TO <role> */
+	else if (pg_strcasecmp(prev6_wd, "ALTER") == 0 &&
+			 pg_strcasecmp(prev5_wd, "POLICY") == 0 &&
+			 pg_strcasecmp(prev3_wd, "ON") == 0 &&
+			 pg_strcasecmp(prev_wd, "TO") == 0)
+		COMPLETE_WITH_QUERY(Query_for_list_of_grant_roles);
+	/* ALTER POLICY <name> ON <table> USING ( */
+	else if (pg_strcasecmp(prev6_wd, "ALTER") == 0 &&
+			 pg_strcasecmp(prev5_wd, "POLICY") == 0 &&
+			 pg_strcasecmp(prev3_wd, "ON") == 0 &&
+			 pg_strcasecmp(prev_wd, "USING") == 0)
+		COMPLETE_WITH_CONST("(");
+	/* ALTER POLICY <name> ON <table> WITH CHECK ( */
+	else if (pg_strcasecmp(prev6_wd, "POLICY") == 0 &&
+			 pg_strcasecmp(prev4_wd, "ON") == 0 &&
+			 pg_strcasecmp(prev2_wd, "WITH") == 0 &&
+			 pg_strcasecmp(prev_wd, "CHECK") == 0)
+		COMPLETE_WITH_CONST("(");
+
 	/* ALTER RULE <name>, add ON */
 	else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
 			 pg_strcasecmp(prev2_wd, "RULE") == 0)
@@ -1462,7 +1501,7 @@ psql_completion(const char *text, int start, int end)
 			 pg_strcasecmp(prev_wd, "ENABLE") == 0)
 	{
 		static const char *const list_ALTERENABLE[] =
-		{"ALWAYS", "REPLICA", "RULE", "TRIGGER", NULL};
+		{"ALWAYS", "REPLICA", "ROW LEVEL SECURITY", "RULE", "TRIGGER", NULL};
 
 		COMPLETE_WITH_LIST(list_ALTERENABLE);
 	}
@@ -1529,7 +1568,7 @@ psql_completion(const char *text, int start, int end)
 			 pg_strcasecmp(prev_wd, "DISABLE") == 0)
 	{
 		static const char *const list_ALTERDISABLE[] =
-		{"RULE", "TRIGGER", NULL};
+		{ "ROW LEVEL SECURITY", "RULE", "TRIGGER", NULL};
 
 		COMPLETE_WITH_LIST(list_ALTERDISABLE);
 	}
@@ -1549,6 +1588,16 @@ psql_completion(const char *text, int start, int end)
 		completion_info_charp = prev3_wd;
 		COMPLETE_WITH_QUERY(Query_for_trigger_of_table);
 	}
+	else if (pg_strcasecmp(prev4_wd, "DISABLE") == 0 &&
+			 pg_strcasecmp(prev3_wd, "ROW") == 0 &&
+			 pg_strcasecmp(prev2_wd, "LEVEL") == 0 &&
+			 pg_strcasecmp(prev_wd, "SECURITY") == 0)
+	{
+		static const char *const list_DISABLERLS[] =
+		{ "CASCADE", NULL};
+
+		COMPLETE_WITH_LIST(list_DISABLERLS);
+	}
 
 	/* ALTER TABLE xxx ALTER */
 	else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
@@ -2251,12 +2300,103 @@ psql_completion(const char *text, int start, int end)
 			 pg_strcasecmp(prev_wd, "(") == 0)
 		COMPLETE_WITH_ATTR(prev4_wd, "");
 	/* Complete USING with an index method */
-	else if (pg_strcasecmp(prev_wd, "USING") == 0)
+	else if ((pg_strcasecmp(prev6_wd, "INDEX") == 0 ||
+			 pg_strcasecmp(prev5_wd, "INDEX") == 0 ||
+			 pg_strcasecmp(prev4_wd, "INDEX") == 0) &&
+			 pg_strcasecmp(prev3_wd, "ON") == 0 &&
+			 pg_strcasecmp(prev_wd, "USING") == 0)
 		COMPLETE_WITH_QUERY(Query_for_list_of_access_methods);
 	else if (pg_strcasecmp(prev4_wd, "ON") == 0 &&
+			 (!(pg_strcasecmp(prev6_wd, "POLICY") == 0) &&
+			  !(pg_strcasecmp(prev4_wd, "FOR") == 0)) &&
 			 pg_strcasecmp(prev2_wd, "USING") == 0)
 		COMPLETE_WITH_CONST("(");
 
+	/* CREATE POLICY */
+	/* Complete "CREATE POLICY <name> ON" */
+	else if (pg_strcasecmp(prev3_wd, "CREATE") == 0 &&
+			 pg_strcasecmp(prev2_wd, "POLICY") == 0)
+		COMPLETE_WITH_CONST("ON");
+	/* Complete "CREATE POLICY <name> ON <table>" */
+	else if (pg_strcasecmp(prev4_wd, "CREATE") == 0 &&
+			 pg_strcasecmp(prev3_wd, "POLICY") == 0 &&
+			 pg_strcasecmp(prev_wd, "ON") == 0)
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
+	/* Complete "CREATE POLICY <name> ON <table> FOR|TO|USING|WITH CHECK" */
+	else if (pg_strcasecmp(prev5_wd, "CREATE") == 0 &&
+			 pg_strcasecmp(prev4_wd, "POLICY") == 0 &&
+			 pg_strcasecmp(prev2_wd, "ON") == 0)
+	{
+		static const char *const list_POLICYOPTIONS[] =
+		{"FOR", "TO", "USING", "WITH CHECK", NULL};
+
+		COMPLETE_WITH_LIST(list_POLICYOPTIONS);
+	}
+	/* Complete "CREATE POLICY <name> ON <table> FOR ALL|SELECT|INSERT|UPDATE|DELETE" */
+	else if (pg_strcasecmp(prev6_wd, "CREATE") == 0 &&
+			 pg_strcasecmp(prev5_wd, "POLICY") == 0 &&
+			 pg_strcasecmp(prev3_wd, "ON") == 0 &&
+			 pg_strcasecmp(prev_wd, "FOR") == 0)
+	{
+		static const char *const list_POLICYCMDS[] =
+		{"ALL", "SELECT", "INSERT", "UPDATE", "DELETE", NULL};
+
+		COMPLETE_WITH_LIST(list_POLICYCMDS);
+	}
+	/* Complete "CREATE POLICY <name> ON <table> FOR INSERT TO|WITH CHECK" */
+	else if (pg_strcasecmp(prev6_wd, "POLICY") == 0 &&
+			 pg_strcasecmp(prev4_wd, "ON") == 0 &&
+			 pg_strcasecmp(prev2_wd, "FOR") == 0 &&
+			 pg_strcasecmp(prev_wd, "INSERT") == 0)
+	{
+		static const char *const list_POLICYOPTIONS[] =
+		{"TO", "WITH CHECK", NULL};
+
+		COMPLETE_WITH_LIST(list_POLICYOPTIONS);
+	}
+	/*
+	 * Complete "CREATE POLICY <name> ON <table> FOR SELECT TO|USING"
+	 * Complete "CREATE POLICY <name> ON <table> FOR DELETE TO|USING"
+	 */
+	else if (pg_strcasecmp(prev6_wd, "POLICY") == 0 &&
+			 pg_strcasecmp(prev4_wd, "ON") == 0 &&
+			 pg_strcasecmp(prev2_wd, "FOR") == 0 &&
+			 (pg_strcasecmp(prev_wd, "SELECT") == 0 ||
+			 pg_strcasecmp(prev_wd, "DELETE") == 0))
+	{
+		static const char *const list_POLICYOPTIONS[] =
+		{"TO", "USING", NULL};
+
+		COMPLETE_WITH_LIST(list_POLICYOPTIONS);
+	}
+	/*
+	 * Complete "CREATE POLICY <name> ON <table> FOR ALL TO|USING|WITH CHECK"
+	 * Complete "CREATE POLICY <name> ON <table> FOR UPDATE TO|USING|WITH CHECK"
+	 */
+	else if (pg_strcasecmp(prev6_wd, "POLICY") == 0 &&
+			 pg_strcasecmp(prev4_wd, "ON") == 0 &&
+			 pg_strcasecmp(prev2_wd, "FOR") == 0 &&
+			 (pg_strcasecmp(prev_wd, "ALL") == 0 ||
+			 pg_strcasecmp(prev_wd, "UPDATE") == 0))
+	{
+		static const char *const list_POLICYOPTIONS[] =
+		{"TO", "USING", "WITH CHECK", NULL};
+
+		COMPLETE_WITH_LIST(list_POLICYOPTIONS);
+	}
+	/* Complete "CREATE POLICY <name> ON <table> TO <role>" */
+	else if (pg_strcasecmp(prev6_wd, "CREATE") == 0 &&
+			 pg_strcasecmp(prev5_wd, "POLICY") == 0 &&
+			 pg_strcasecmp(prev3_wd, "ON") == 0 &&
+			 pg_strcasecmp(prev_wd, "TO") == 0)
+		COMPLETE_WITH_QUERY(Query_for_list_of_grant_roles);
+	/* Complete "CREATE POLICY <name> ON <table> USING (" */
+	else if (pg_strcasecmp(prev6_wd, "CREATE") == 0 &&
+			 pg_strcasecmp(prev5_wd, "POLICY") == 0 &&
+			 pg_strcasecmp(prev3_wd, "ON") == 0 &&
+			 pg_strcasecmp(prev_wd, "USING") == 0)
+		COMPLETE_WITH_CONST("(");
+
 /* CREATE RULE */
 	/* Complete "CREATE RULE <sth>" with "AS" */
 	else if (pg_strcasecmp(prev3_wd, "CREATE") == 0 &&
@@ -2726,6 +2866,16 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_QUERY(Query_for_list_of_event_triggers);
 	}
 
+	/* DROP POLICY <name> ON */
+	else if (pg_strcasecmp(prev3_wd, "DROP") == 0 &&
+			 pg_strcasecmp(prev2_wd, "POLICY") == 0)
+		COMPLETE_WITH_CONST("ON");
+	/* DROP POLICY <name> ON <table> */
+	else if (pg_strcasecmp(prev4_wd, "DROP") == 0 &&
+			 pg_strcasecmp(prev3_wd, "POLICY") == 0 &&
+			 pg_strcasecmp(prev_wd, "ON") == 0)
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
+
 	/* DROP RULE */
 	else if (pg_strcasecmp(prev3_wd, "DROP") == 0 &&
 			 pg_strcasecmp(prev2_wd, "RULE") == 0)
diff --git a/src/include/catalog/catversion.h b/src/include/catalog/catversion.h
index a36dbeb80b13b82919026e6f4294142ecb1197da..af0475e8314387e892944e7b22117c4a9faa4b10 100644
--- a/src/include/catalog/catversion.h
+++ b/src/include/catalog/catversion.h
@@ -53,6 +53,6 @@
  */
 
 /*							yyyymmddN */
-#define CATALOG_VERSION_NO	201409162
+#define CATALOG_VERSION_NO	201409191
 
 #endif
diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h
index 8ed259283aef330db045de410da159ce6d365225..6a4913a66e57f3d6f6d0705c6798370b50e168e3 100644
--- a/src/include/catalog/dependency.h
+++ b/src/include/catalog/dependency.h
@@ -147,6 +147,7 @@ typedef enum ObjectClass
 	OCLASS_DEFACL,				/* pg_default_acl */
 	OCLASS_EXTENSION,			/* pg_extension */
 	OCLASS_EVENT_TRIGGER,		/* pg_event_trigger */
+	OCLASS_ROWSECURITY,			/* pg_rowsecurity */
 	MAX_OCLASS					/* MUST BE LAST */
 } ObjectClass;
 
diff --git a/src/include/catalog/indexing.h b/src/include/catalog/indexing.h
index 59576fd7512a9b778092084ffe2f91c89202c569..870692cf54f9e5151ed116583dbf7fa6d713227f 100644
--- a/src/include/catalog/indexing.h
+++ b/src/include/catalog/indexing.h
@@ -299,6 +299,12 @@ DECLARE_UNIQUE_INDEX(pg_extension_name_index, 3081, on pg_extension using btree(
 DECLARE_UNIQUE_INDEX(pg_range_rngtypid_index, 3542, on pg_range using btree(rngtypid oid_ops));
 #define RangeTypidIndexId					3542
 
+DECLARE_UNIQUE_INDEX(pg_rowsecurity_oid_index, 3257, on pg_rowsecurity using btree(oid oid_ops));
+#define RowSecurityOidIndexId				3257
+
+DECLARE_UNIQUE_INDEX(pg_rowsecurity_polname_relid_index, 3258, on pg_rowsecurity using btree(rsecrelid oid_ops, rsecpolname name_ops));
+#define RowSecurityRelidPolnameIndexId				3258
+
 /* last step of initialization script: build the indexes declared above */
 BUILD_INDICES
 
diff --git a/src/include/catalog/pg_authid.h b/src/include/catalog/pg_authid.h
index e7c32c9fa6eb4fd8b17efdf7e6a47ecba6837926..3b63d2bb9e0be6ca6c56f2892d672e0aa673fe1b 100644
--- a/src/include/catalog/pg_authid.h
+++ b/src/include/catalog/pg_authid.h
@@ -52,6 +52,7 @@ CATALOG(pg_authid,1260) BKI_SHARED_RELATION BKI_ROWTYPE_OID(2842) BKI_SCHEMA_MAC
 	bool		rolcatupdate;	/* allowed to alter catalogs manually? */
 	bool		rolcanlogin;	/* allowed to log in as session user? */
 	bool		rolreplication; /* role used for streaming replication */
+	bool		rolbypassrls;	/* allowed to bypass row level security? */
 	int32		rolconnlimit;	/* max connections allowed (-1=no limit) */
 
 	/* remaining fields may be null; use heap_getattr to read them! */
@@ -73,7 +74,7 @@ typedef FormData_pg_authid *Form_pg_authid;
  *		compiler constants for pg_authid
  * ----------------
  */
-#define Natts_pg_authid					11
+#define Natts_pg_authid					12
 #define Anum_pg_authid_rolname			1
 #define Anum_pg_authid_rolsuper			2
 #define Anum_pg_authid_rolinherit		3
@@ -82,9 +83,10 @@ typedef FormData_pg_authid *Form_pg_authid;
 #define Anum_pg_authid_rolcatupdate		6
 #define Anum_pg_authid_rolcanlogin		7
 #define Anum_pg_authid_rolreplication	8
-#define Anum_pg_authid_rolconnlimit		9
-#define Anum_pg_authid_rolpassword		10
-#define Anum_pg_authid_rolvaliduntil	11
+#define Anum_pg_authid_rolbypassrls		9
+#define Anum_pg_authid_rolconnlimit		10
+#define Anum_pg_authid_rolpassword		11
+#define Anum_pg_authid_rolvaliduntil	12
 
 /* ----------------
  *		initial contents of pg_authid
@@ -93,7 +95,7 @@ typedef FormData_pg_authid *Form_pg_authid;
  * user choices.
  * ----------------
  */
-DATA(insert OID = 10 ( "POSTGRES" t t t t t t t -1 _null_ _null_ ));
+DATA(insert OID = 10 ( "POSTGRES" t t t t t t t t -1 _null_ _null_));
 
 #define BOOTSTRAP_SUPERUSERID 10
 
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index f2fb317e5f19663945b719afa0c18a83c4b95f30..f6353514cacfbf7f33f1eb37f540c393a3698292 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -65,6 +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		relispopulated; /* matview currently holds query results */
 	char		relreplident;	/* see REPLICA_IDENTITY_xxx constants  */
 	TransactionId relfrozenxid; /* all Xids < this are frozen in this rel */
@@ -94,7 +95,7 @@ typedef FormData_pg_class *Form_pg_class;
  * ----------------
  */
 
-#define Natts_pg_class					29
+#define Natts_pg_class					30
 #define Anum_pg_class_relname			1
 #define Anum_pg_class_relnamespace		2
 #define Anum_pg_class_reltype			3
@@ -118,12 +119,13 @@ 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_relispopulated	24
-#define Anum_pg_class_relreplident		25
-#define Anum_pg_class_relfrozenxid		26
-#define Anum_pg_class_relminmxid		27
-#define Anum_pg_class_relacl			28
-#define Anum_pg_class_reloptions		29
+#define Anum_pg_class_relhasrowsecurity	24
+#define Anum_pg_class_relispopulated	25
+#define Anum_pg_class_relreplident		26
+#define Anum_pg_class_relfrozenxid		27
+#define Anum_pg_class_relminmxid		28
+#define Anum_pg_class_relacl			29
+#define Anum_pg_class_reloptions		30
 
 /* ----------------
  *		initial contents of pg_class
@@ -138,13 +140,13 @@ typedef FormData_pg_class *Form_pg_class;
  * Note: "3" in the relfrozenxid column stands for FirstNormalTransactionId;
  * similarly, "1" in relminmxid stands for FirstMultiXactId
  */
-DATA(insert OID = 1247 (  pg_type		PGNSP 71 0 PGUID 0 0 0 0 0 0 0 f f p r 30 0 t f f f f t n 3 1 _null_ _null_ ));
+DATA(insert OID = 1247 (  pg_type		PGNSP 71 0 PGUID 0 0 0 0 0 0 0 f f p r 30 0 t f f f f f t n 3 1 _null_ _null_ ));
 DESCR("");
-DATA(insert OID = 1249 (  pg_attribute	PGNSP 75 0 PGUID 0 0 0 0 0 0 0 f f p r 21 0 f f f f f t n 3 1 _null_ _null_ ));
+DATA(insert OID = 1249 (  pg_attribute	PGNSP 75 0 PGUID 0 0 0 0 0 0 0 f f p r 21 0 f f f f f f t n 3 1 _null_ _null_ ));
 DESCR("");
-DATA(insert OID = 1255 (  pg_proc		PGNSP 81 0 PGUID 0 0 0 0 0 0 0 f f p r 27 0 t f f f f t n 3 1 _null_ _null_ ));
+DATA(insert OID = 1255 (  pg_proc		PGNSP 81 0 PGUID 0 0 0 0 0 0 0 f f p r 27 0 t f f f f f t n 3 1 _null_ _null_ ));
 DESCR("");
-DATA(insert OID = 1259 (  pg_class		PGNSP 83 0 PGUID 0 0 0 0 0 0 0 f f p r 29 0 t f f f f t n 3 1 _null_ _null_ ));
+DATA(insert OID = 1259 (  pg_class		PGNSP 83 0 PGUID 0 0 0 0 0 0 0 f f p r 30 0 t f f f f f t n 3 1 _null_ _null_ ));
 DESCR("");
 
 
diff --git a/src/include/catalog/pg_rowsecurity.h b/src/include/catalog/pg_rowsecurity.h
new file mode 100644
index 0000000000000000000000000000000000000000..2638d5e684894dbce672c21c38a99502076d0f1f
--- /dev/null
+++ b/src/include/catalog/pg_rowsecurity.h
@@ -0,0 +1,53 @@
+/*
+ * pg_rowsecurity.h
+ *   definition of the system catalog for row-security policy (pg_rowsecurity)
+ *
+ * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ */
+#ifndef PG_ROWSECURITY_H
+#define PG_ROWSECURITY_H
+
+#include "catalog/genbki.h"
+
+/* ----------------
+ *		pg_rowsecurity definition. cpp turns this into
+ *		typedef struct FormData_pg_rowsecurity
+ * ----------------
+ */
+#define RowSecurityRelationId	3256
+
+CATALOG(pg_rowsecurity,3256)
+{
+	NameData		rsecpolname;	/* Policy name. */
+	Oid				rsecrelid;		/* Oid of the relation with policy. */
+	char			rseccmd;		/* One of ACL_*_CHR, or \0 for all */
+
+#ifdef CATALOG_VARLEN
+	Oid				rsecroles[1]	/* Roles associated with policy, not-NULL */
+	pg_node_tree	rsecqual;		/* Policy quals. */
+	pg_node_tree	rsecwithcheck;	/* WITH CHECK quals. */
+#endif
+} FormData_pg_rowsecurity;
+
+/* ----------------
+ *		Form_pg_rowsecurity corresponds to a pointer to a row with
+ *		the format of pg_rowsecurity relation.
+ * ----------------
+ */
+typedef FormData_pg_rowsecurity *Form_pg_rowsecurity;
+
+/* ----------------
+ * 		compiler constants for pg_rowsecurity
+ * ----------------
+ */
+#define Natts_pg_rowsecurity				6
+#define Anum_pg_rowsecurity_rsecpolname		1
+#define Anum_pg_rowsecurity_rsecrelid		2
+#define Anum_pg_rowsecurity_rseccmd			3
+#define Anum_pg_rowsecurity_rsecroles		4
+#define Anum_pg_rowsecurity_rsecqual		5
+#define Anum_pg_rowsecurity_rsecwithcheck	6
+
+#endif  /* PG_ROWSECURITY_H */
diff --git a/src/include/commands/policy.h b/src/include/commands/policy.h
new file mode 100644
index 0000000000000000000000000000000000000000..95d8a6d11751612b3cbcf17ec79f6ab5308f2762
--- /dev/null
+++ b/src/include/commands/policy.h
@@ -0,0 +1,33 @@
+/*-------------------------------------------------------------------------
+ *
+ * policy.h
+ *	  prototypes for policy.c.
+ *
+ *
+ * Portions Copyright (c) 1996-2014, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/commands/policy.h
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef POLICY_H
+#define POLICY_H
+
+#include "nodes/parsenodes.h"
+
+extern void RelationBuildRowSecurity(Relation relation);
+
+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);
+
+Oid rename_policy(RenameStmt *stmt);
+
+
+#endif   /* POLICY_H */
diff --git a/src/include/miscadmin.h b/src/include/miscadmin.h
index 3807955c8ebb7b654a19e98fd75ed0a81e7d40bc..2ba98856ff27782d778bacc4da3cb9a080acc269 100644
--- a/src/include/miscadmin.h
+++ b/src/include/miscadmin.h
@@ -272,6 +272,7 @@ extern int	trace_recovery(int trace_level);
 /* flags to be OR'd to form sec_context */
 #define SECURITY_LOCAL_USERID_CHANGE	0x0001
 #define SECURITY_RESTRICTED_OPERATION	0x0002
+#define SECURITY_ROW_LEVEL_DISABLED		0x0004
 
 extern char *DatabasePath;
 
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index a031b88b8713b46e6a33ec20dc2b7f823ce8c222..154d943d581473f0a10d2271dd073b0af84f2f6b 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -366,6 +366,8 @@ typedef enum NodeTag
 	T_RefreshMatViewStmt,
 	T_ReplicaIdentityStmt,
 	T_AlterSystemStmt,
+	T_CreatePolicyStmt,
+	T_AlterPolicyStmt,
 
 	/*
 	 * TAGS FOR PARSE TREE NODES (parsenodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index d2c0b29c0d7e3a2b18119ef208c7b82e3a7f1258..f3aa69e4a1b9698f6ef3a4fc4f0e6e24a5b0e42d 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -120,6 +120,7 @@ typedef struct Query
 	bool		hasRecursive;	/* WITH RECURSIVE was specified */
 	bool		hasModifyingCTE;	/* has INSERT/UPDATE/DELETE in WITH */
 	bool		hasForUpdate;	/* FOR [KEY] UPDATE/SHARE was specified */
+	bool		hasRowSecurity;	/* Row-security policy is applied */
 
 	List	   *cteList;		/* WITH list (of CommonTableExpr's) */
 
@@ -1224,6 +1225,7 @@ typedef enum ObjectType
 	OBJECT_OPCLASS,
 	OBJECT_OPERATOR,
 	OBJECT_OPFAMILY,
+	OBJECT_POLICY,
 	OBJECT_ROLE,
 	OBJECT_RULE,
 	OBJECT_SCHEMA,
@@ -1333,6 +1335,8 @@ typedef enum AlterTableType
 	AT_AddOf,					/* OF <type_name> */
 	AT_DropOf,					/* NOT OF */
 	AT_ReplicaIdentity,			/* REPLICA IDENTITY */
+	AT_EnableRowSecurity,		/* ENABLE ROW SECURITY */
+	AT_DisableRowSecurity,		/* DISABLE ROW SECURITY */
 	AT_GenericOptions			/* OPTIONS (...) */
 } AlterTableType;
 
@@ -1855,6 +1859,35 @@ typedef struct ImportForeignSchemaStmt
 	List	   *options;		/* list of options to pass to FDW */
 } ImportForeignSchemaStmt;
 
+/*----------------------
+ *		Create POLICY Statement
+ *----------------------
+ */
+typedef struct CreatePolicyStmt
+{
+	NodeTag		type;
+	char	   *policy_name;	/* Policy's name */
+	RangeVar   *table;			/* the table name the policy applies to */
+	char	   *cmd;			/* the command name the policy applies to */
+	List	   *roles;			/* the roles associated with the policy */
+	Node	   *qual;			/* the policy's condition */
+	Node	   *with_check;		/* the policy's WITH CHECK condition. */
+} CreatePolicyStmt;
+
+/*----------------------
+ *		Alter POLICY Statement
+ *----------------------
+ */
+typedef struct AlterPolicyStmt
+{
+	NodeTag		type;
+	char	   *policy_name;	/* Policy's name */
+	RangeVar   *table;			/* the table name the policy applies to */
+	List	   *roles;			/* the roles associated with the policy */
+	Node	   *qual;			/* the policy's condition */
+	Node	   *with_check;		/* the policy's WITH CHECK condition. */
+} AlterPolicyStmt;
+
 /* ----------------------
  *		Create TRIGGER Statement
  * ----------------------
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index 3b9c68382955377fbaa7897d33f7dd6bce491ed9..18394946f860dfb277f899e34083e44ad7ea589c 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -67,6 +67,9 @@ typedef struct PlannedStmt
 	List	   *invalItems;		/* other dependencies, as PlanInvalItems */
 
 	int			nParamExec;		/* number of PARAM_EXEC Params used */
+
+	bool		has_rls;		/* row-security applied? */
+
 } PlannedStmt;
 
 /* macro for fetching the Plan associated with a SubPlan node */
diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h
index dacbe9cc0b998c02d7523b27aedccff41e58eb2c..f1a0504c0d4af2aeab107416107118a4d8235fc9 100644
--- a/src/include/nodes/relation.h
+++ b/src/include/nodes/relation.h
@@ -99,6 +99,9 @@ typedef struct PlannerGlobal
 	Index		lastRowMarkId;	/* highest PlanRowMark ID assigned */
 
 	bool		transientPlan;	/* redo plan when TransactionXmin changes? */
+
+	bool		has_rls;		/* row-security is applied? */
+
 } PlannerGlobal;
 
 /* macro for fetching the Plan associated with a SubPlan node */
diff --git a/src/include/optimizer/planmain.h b/src/include/optimizer/planmain.h
index 450425046a50b38993f57cddf73a185ed848c069..3fdc2cba0ed16ec0057743d93e08a1d0cff5ff3c 100644
--- a/src/include/optimizer/planmain.h
+++ b/src/include/optimizer/planmain.h
@@ -135,6 +135,7 @@ extern void set_sa_opfuncid(ScalarArrayOpExpr *opexpr);
 extern void record_plan_function_dependency(PlannerInfo *root, Oid funcid);
 extern void extract_query_dependencies(Node *query,
 						   List **relationOids,
-						   List **invalItems);
+						   List **invalItems,
+						   bool *hasRowSecurity);
 
 #endif   /* PLANMAIN_H */
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 17888ad0ec513d96bca1ed698a84ee5f095151e4..3c8c1b9e252a5d97716641066efafe87d2b34ece 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -284,6 +284,7 @@ PG_KEYWORD("passing", PASSING, UNRESERVED_KEYWORD)
 PG_KEYWORD("password", PASSWORD, UNRESERVED_KEYWORD)
 PG_KEYWORD("placing", PLACING, RESERVED_KEYWORD)
 PG_KEYWORD("plans", PLANS, UNRESERVED_KEYWORD)
+PG_KEYWORD("policy", POLICY, UNRESERVED_KEYWORD)
 PG_KEYWORD("position", POSITION, COL_NAME_KEYWORD)
 PG_KEYWORD("preceding", PRECEDING, UNRESERVED_KEYWORD)
 PG_KEYWORD("precision", PRECISION, COL_NAME_KEYWORD)
diff --git a/src/include/rewrite/rowsecurity.h b/src/include/rewrite/rowsecurity.h
new file mode 100644
index 0000000000000000000000000000000000000000..245005cae2ebdc0061041dec7839a71d130fc11a
--- /dev/null
+++ b/src/include/rewrite/rowsecurity.h
@@ -0,0 +1,80 @@
+/* -------------------------------------------------------------------------
+ *
+ * rowsecurity.h
+ *    prototypes for optimizer/rowsecurity.c
+ *
+ * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * -------------------------------------------------------------------------
+ */
+#ifndef ROWSECURITY_H
+#define ROWSECURITY_H
+
+#include "nodes/execnodes.h"
+#include "nodes/parsenodes.h"
+#include "nodes/relation.h"
+#include "utils/array.h"
+
+typedef struct RowSecurityPolicy
+{
+	Oid					rsecid;
+	char			   *policy_name;
+	char				cmd;
+	ArrayType		   *roles;
+	Expr			   *qual;
+	Expr			   *with_check_qual;
+	bool				hassublinks;
+} RowSecurityPolicy;
+
+typedef struct RowSecurityDesc
+{
+	MemoryContext		rscxt;		/* row-security memory context */
+	List			   *policies;	/* list of row-security policies */
+} RowSecurityDesc;
+
+/* GUC variable */
+extern int row_security;
+
+/* Possible values for row_security GUC */
+typedef enum RowSecurityConfigType
+{
+	ROW_SECURITY_OFF,
+	ROW_SECURITY_ON,
+	ROW_SECURITY_FORCE
+} RowSecurityConfigType;
+
+/*
+ * Used by callers of check_enable_rls.
+ *
+ * RLS could be completely disabled on the tables involved in the query,
+ * which is the simple case, or it may depend on the current environment
+ * (the role which is running the query or the value of the row_security
+ * GUC- on, off, or force), or it might be simply enabled as usual.
+ *
+ * If RLS isn't on the table involved then RLS_NONE is returned to indicate
+ * that we don't need to worry about invalidating the query plan for RLS
+ * reasons.  If RLS is on the table, but we are bypassing it for now, then
+ * we return RLS_NONE_ENV to indicate that, if the environment changes,
+ * we need to invalidate and replan.  Finally, if RLS should be turned on
+ * for the query, then we return RLS_ENABLED, which means we also need to
+ * invalidate if the environment changes.
+ */
+enum CheckEnableRlsResult
+{
+	RLS_NONE,
+	RLS_NONE_ENV,
+	RLS_ENABLED
+};
+
+typedef List *(*row_security_policy_hook_type)(CmdType cmdtype,
+											   Relation relation);
+
+extern PGDLLIMPORT row_security_policy_hook_type row_security_policy_hook;
+
+extern bool prepend_row_security_policies(Query* root, RangeTblEntry* rte,
+									   int rt_index);
+
+extern int check_enable_rls(Oid relid, Oid checkAsUser);
+
+#endif	/* ROWSECURITY_H */
diff --git a/src/include/utils/acl.h b/src/include/utils/acl.h
index 9430baa4a0b97f68ea2a9712f596dd58a448f60d..a8e3164659c6dc0bf1f6178ad8d64a4dc956bfaf 100644
--- a/src/include/utils/acl.h
+++ b/src/include/utils/acl.h
@@ -228,6 +228,7 @@ extern bool is_member_of_role_nosuper(Oid member, Oid role);
 extern bool is_admin_of_role(Oid member, Oid role);
 extern void check_is_member_of_role(Oid member, Oid role);
 extern Oid	get_role_oid(const char *rolname, bool missing_ok);
+extern Oid	get_role_oid_or_public(const char *rolname);
 
 extern void select_best_grantor(Oid roleId, AclMode privileges,
 					const Acl *acl, Oid ownerId,
@@ -326,5 +327,6 @@ extern bool pg_foreign_server_ownercheck(Oid srv_oid, Oid roleid);
 extern bool pg_event_trigger_ownercheck(Oid et_oid, Oid roleid);
 extern bool pg_extension_ownercheck(Oid ext_oid, Oid roleid);
 extern bool has_createrole_privilege(Oid roleid);
+extern bool has_bypassrls_privilege(Oid roleid);
 
 #endif   /* ACL_H */
diff --git a/src/include/utils/plancache.h b/src/include/utils/plancache.h
index cfbfaa26cc34cd58800cb909ba97e5103d6c62b2..56bf4bb7afc69de065a71d40ce7e6009794300ef 100644
--- a/src/include/utils/plancache.h
+++ b/src/include/utils/plancache.h
@@ -93,6 +93,7 @@ typedef struct CachedPlanSource
 	List	   *invalItems;		/* other dependencies, as PlanInvalItems */
 	struct OverrideSearchPath *search_path;		/* search_path used for
 												 * parsing and planning */
+	Oid			planUserId;		/* User-id that the plan depends on */
 	MemoryContext query_context;	/* context holding the above, or NULL */
 	/* If we have a generic plan, this is a reference-counted link to it: */
 	struct CachedPlan *gplan;	/* generic plan, or NULL if not valid */
@@ -108,6 +109,9 @@ typedef struct CachedPlanSource
 	double		generic_cost;	/* cost of generic plan, or -1 if not known */
 	double		total_custom_cost;		/* total cost of custom plans so far */
 	int			num_custom_plans;		/* number of plans included in total */
+	bool		has_rls;				/* planned with row-security? */
+	int			row_security_env;		/* row security setting when planned */
+	bool		rowSecurityDisabled;	/* is row-security disabled? */
 } CachedPlanSource;
 
 /*
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 37b6cbbb4d0674511f7a94cdf7846c92e0ae0327..198b98f2f8a8080c7c5815056064347c629f0632 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -21,6 +21,7 @@
 #include "fmgr.h"
 #include "nodes/bitmapset.h"
 #include "rewrite/prs2lock.h"
+#include "rewrite/rowsecurity.h"
 #include "storage/block.h"
 #include "storage/relfilenode.h"
 #include "utils/relcache.h"
@@ -105,6 +106,7 @@ typedef struct RelationData
 	RuleLock   *rd_rules;		/* rewrite rules */
 	MemoryContext rd_rulescxt;	/* private memory cxt for rd_rules, if any */
 	TriggerDesc *trigdesc;		/* Trigger info, or NULL if rel has none */
+	RowSecurityDesc *rsdesc;	/* Row-security policy, or NULL */
 
 	/* data managed by RelationGetIndexList: */
 	List	   *rd_indexlist;	/* list of OIDs of indexes on relation */
diff --git a/src/test/regress/expected/dependency.out b/src/test/regress/expected/dependency.out
index bcff8ddc17c0eb8e77f915dd8dc7e271d86d2bed..09b02127f94fbb8fc27d83c79fcf346cde4c2ca3 100644
--- a/src/test/regress/expected/dependency.out
+++ b/src/test/regress/expected/dependency.out
@@ -63,21 +63,21 @@ CREATE TABLE deptest (a serial primary key, b text);
 GRANT ALL ON deptest1 TO regression_user2;
 RESET SESSION AUTHORIZATION;
 \z deptest1
-                                            Access privileges
- Schema |   Name   | Type  |                Access privileges                 | Column access privileges 
---------+----------+-------+--------------------------------------------------+--------------------------
- public | deptest1 | table | regression_user0=arwdDxt/regression_user0       +| 
-        |          |       | regression_user1=a*r*w*d*D*x*t*/regression_user0+| 
-        |          |       | regression_user2=arwdDxt/regression_user1        | 
+                                              Access privileges
+ Schema |   Name   | Type  |                Access privileges                 | Column privileges | Policies 
+--------+----------+-------+--------------------------------------------------+-------------------+----------
+ public | deptest1 | table | regression_user0=arwdDxt/regression_user0       +|                   | 
+        |          |       | regression_user1=a*r*w*d*D*x*t*/regression_user0+|                   | 
+        |          |       | regression_user2=arwdDxt/regression_user1        |                   | 
 (1 row)
 
 DROP OWNED BY regression_user1;
 -- all grants revoked
 \z deptest1
-                                        Access privileges
- Schema |   Name   | Type  |             Access privileges             | Column access privileges 
---------+----------+-------+-------------------------------------------+--------------------------
- public | deptest1 | table | regression_user0=arwdDxt/regression_user0 | 
+                                          Access privileges
+ Schema |   Name   | Type  |             Access privileges             | Column privileges | Policies 
+--------+----------+-------+-------------------------------------------+-------------------+----------
+ public | deptest1 | table | regression_user0=arwdDxt/regression_user0 |                   | 
 (1 row)
 
 -- table was dropped
diff --git a/src/test/regress/expected/privileges.out b/src/test/regress/expected/privileges.out
index 1675075f6824e504e4274a62f3f95c7a5c69f610..5359dd8536adac4fa156bba0ee44ca4eb7b7877d 100644
--- a/src/test/regress/expected/privileges.out
+++ b/src/test/regress/expected/privileges.out
@@ -1425,39 +1425,39 @@ grant select on dep_priv_test to regressuser4 with grant option;
 set session role regressuser4;
 grant select on dep_priv_test to regressuser5;
 \dp dep_priv_test
-                                       Access privileges
- Schema |     Name      | Type  |         Access privileges         | Column access privileges 
---------+---------------+-------+-----------------------------------+--------------------------
- public | dep_priv_test | table | regressuser1=arwdDxt/regressuser1+| 
-        |               |       | regressuser2=r*/regressuser1     +| 
-        |               |       | regressuser3=r*/regressuser1     +| 
-        |               |       | regressuser4=r*/regressuser2     +| 
-        |               |       | regressuser4=r*/regressuser3     +| 
-        |               |       | regressuser5=r/regressuser4       | 
+                                         Access privileges
+ Schema |     Name      | Type  |         Access privileges         | Column privileges | Policies 
+--------+---------------+-------+-----------------------------------+-------------------+----------
+ public | dep_priv_test | table | regressuser1=arwdDxt/regressuser1+|                   | 
+        |               |       | regressuser2=r*/regressuser1     +|                   | 
+        |               |       | regressuser3=r*/regressuser1     +|                   | 
+        |               |       | regressuser4=r*/regressuser2     +|                   | 
+        |               |       | regressuser4=r*/regressuser3     +|                   | 
+        |               |       | regressuser5=r/regressuser4       |                   | 
 (1 row)
 
 set session role regressuser2;
 revoke select on dep_priv_test from regressuser4 cascade;
 \dp dep_priv_test
-                                       Access privileges
- Schema |     Name      | Type  |         Access privileges         | Column access privileges 
---------+---------------+-------+-----------------------------------+--------------------------
- public | dep_priv_test | table | regressuser1=arwdDxt/regressuser1+| 
-        |               |       | regressuser2=r*/regressuser1     +| 
-        |               |       | regressuser3=r*/regressuser1     +| 
-        |               |       | regressuser4=r*/regressuser3     +| 
-        |               |       | regressuser5=r/regressuser4       | 
+                                         Access privileges
+ Schema |     Name      | Type  |         Access privileges         | Column privileges | Policies 
+--------+---------------+-------+-----------------------------------+-------------------+----------
+ public | dep_priv_test | table | regressuser1=arwdDxt/regressuser1+|                   | 
+        |               |       | regressuser2=r*/regressuser1     +|                   | 
+        |               |       | regressuser3=r*/regressuser1     +|                   | 
+        |               |       | regressuser4=r*/regressuser3     +|                   | 
+        |               |       | regressuser5=r/regressuser4       |                   | 
 (1 row)
 
 set session role regressuser3;
 revoke select on dep_priv_test from regressuser4 cascade;
 \dp dep_priv_test
-                                       Access privileges
- Schema |     Name      | Type  |         Access privileges         | Column access privileges 
---------+---------------+-------+-----------------------------------+--------------------------
- public | dep_priv_test | table | regressuser1=arwdDxt/regressuser1+| 
-        |               |       | regressuser2=r*/regressuser1     +| 
-        |               |       | regressuser3=r*/regressuser1      | 
+                                         Access privileges
+ Schema |     Name      | Type  |         Access privileges         | Column privileges | Policies 
+--------+---------------+-------+-----------------------------------+-------------------+----------
+ public | dep_priv_test | table | regressuser1=arwdDxt/regressuser1+|                   | 
+        |               |       | regressuser2=r*/regressuser1     +|                   | 
+        |               |       | regressuser3=r*/regressuser1      |                   | 
 (1 row)
 
 set session role regressuser1;
diff --git a/src/test/regress/expected/rowsecurity.out b/src/test/regress/expected/rowsecurity.out
new file mode 100644
index 0000000000000000000000000000000000000000..007afc606b76a3aaf49550df910ce1bad1efbd81
--- /dev/null
+++ b/src/test/regress/expected/rowsecurity.out
@@ -0,0 +1,2236 @@
+--
+-- Test of Row-level security feature
+--
+-- Clean up in case a prior regression run failed
+-- Suppress NOTICE messages when users/groups don't exist
+SET client_min_messages TO 'warning';
+DROP USER IF EXISTS rls_regress_user0;
+DROP USER IF EXISTS rls_regress_user1;
+DROP USER IF EXISTS rls_regress_user2;
+DROP USER IF EXISTS rls_regress_exempt_user;
+DROP ROLE IF EXISTS rls_regress_group1;
+DROP ROLE IF EXISTS rls_regress_group2;
+DROP SCHEMA IF EXISTS rls_regress_schema CASCADE;
+RESET client_min_messages;
+-- initial setup
+CREATE USER rls_regress_user0;
+CREATE USER rls_regress_user1;
+CREATE USER rls_regress_user2;
+CREATE USER rls_regress_exempt_user BYPASSRLS;
+CREATE ROLE rls_regress_group1 NOLOGIN;
+CREATE ROLE rls_regress_group2 NOLOGIN;
+GRANT rls_regress_group1 TO rls_regress_user1;
+GRANT rls_regress_group2 TO rls_regress_user2;
+CREATE SCHEMA rls_regress_schema;
+GRANT ALL ON SCHEMA rls_regress_schema to public;
+SET search_path = rls_regress_schema;
+-- setup of malicious function
+CREATE OR REPLACE FUNCTION f_leak(text) RETURNS bool
+    COST 0.0000001 LANGUAGE plpgsql
+    AS 'BEGIN RAISE NOTICE ''f_leak => %'', $1; RETURN true; END';
+GRANT EXECUTE ON FUNCTION f_leak(text) TO public;
+-- BASIC Row-Level Security Scenario
+SET SESSION AUTHORIZATION rls_regress_user0;
+CREATE TABLE uaccount (
+    pguser      name primary key,
+    seclv       int
+);
+GRANT SELECT ON uaccount TO public;
+INSERT INTO uaccount VALUES
+    ('rls_regress_user0', 99),
+    ('rls_regress_user1', 1),
+    ('rls_regress_user2', 2),
+    ('rls_regress_user3', 3);
+CREATE TABLE category (
+    cid        int primary key,
+    cname      text
+);
+GRANT ALL ON category TO public;
+INSERT INTO category VALUES
+    (11, 'novel'),
+    (22, 'science fiction'),
+    (33, 'technology'),
+    (44, 'manga');
+CREATE TABLE document (
+    did         int primary key,
+    cid         int references category(cid),
+    dlevel      int not null,
+    dauthor     name,
+    dtitle      text
+);
+GRANT ALL ON document TO public;
+INSERT INTO document VALUES
+    ( 1, 11, 1, 'rls_regress_user1', 'my first novel'),
+    ( 2, 11, 2, 'rls_regress_user1', 'my second novel'),
+    ( 3, 22, 2, 'rls_regress_user1', 'my science fiction'),
+    ( 4, 44, 1, 'rls_regress_user1', 'my first manga'),
+    ( 5, 44, 2, 'rls_regress_user1', 'my second manga'),
+    ( 6, 22, 1, 'rls_regress_user2', 'great science fiction'),
+    ( 7, 33, 2, 'rls_regress_user2', 'great technology book'),
+    ( 8, 44, 1, 'rls_regress_user2', 'great manga');
+ALTER TABLE document ENABLE ROW LEVEL SECURITY;
+-- user's security level must be higher that or equal to document's
+CREATE POLICY p1 ON document
+    USING (dlevel <= (SELECT seclv FROM uaccount WHERE pguser = current_user));
+-- viewpoint from rls_regress_user1
+SET SESSION AUTHORIZATION rls_regress_user1;
+SET row_security TO ON;
+SELECT * FROM document WHERE f_leak(dtitle) ORDER BY did;
+NOTICE:  f_leak => my first novel
+NOTICE:  f_leak => my first manga
+NOTICE:  f_leak => great science fiction
+NOTICE:  f_leak => great manga
+ did | cid | dlevel |      dauthor      |        dtitle         
+-----+-----+--------+-------------------+-----------------------
+   1 |  11 |      1 | rls_regress_user1 | my first novel
+   4 |  44 |      1 | rls_regress_user1 | my first manga
+   6 |  22 |      1 | rls_regress_user2 | great science fiction
+   8 |  44 |      1 | rls_regress_user2 | great manga
+(4 rows)
+
+SELECT * FROM document NATURAL JOIN category WHERE f_leak(dtitle) ORDER BY did;
+NOTICE:  f_leak => my first novel
+NOTICE:  f_leak => my first manga
+NOTICE:  f_leak => great science fiction
+NOTICE:  f_leak => great manga
+ cid | did | dlevel |      dauthor      |        dtitle         |      cname      
+-----+-----+--------+-------------------+-----------------------+-----------------
+  11 |   1 |      1 | rls_regress_user1 | my first novel        | novel
+  44 |   4 |      1 | rls_regress_user1 | my first manga        | manga
+  22 |   6 |      1 | rls_regress_user2 | great science fiction | science fiction
+  44 |   8 |      1 | rls_regress_user2 | great manga           | manga
+(4 rows)
+
+-- viewpoint from rls_regress_user2
+SET SESSION AUTHORIZATION rls_regress_user2;
+SELECT * FROM document WHERE f_leak(dtitle) ORDER BY did;
+NOTICE:  f_leak => my first novel
+NOTICE:  f_leak => my second novel
+NOTICE:  f_leak => my science fiction
+NOTICE:  f_leak => my first manga
+NOTICE:  f_leak => my second manga
+NOTICE:  f_leak => great science fiction
+NOTICE:  f_leak => great technology book
+NOTICE:  f_leak => great manga
+ did | cid | dlevel |      dauthor      |        dtitle         
+-----+-----+--------+-------------------+-----------------------
+   1 |  11 |      1 | rls_regress_user1 | my first novel
+   2 |  11 |      2 | rls_regress_user1 | my second novel
+   3 |  22 |      2 | rls_regress_user1 | my science fiction
+   4 |  44 |      1 | rls_regress_user1 | my first manga
+   5 |  44 |      2 | rls_regress_user1 | my second manga
+   6 |  22 |      1 | rls_regress_user2 | great science fiction
+   7 |  33 |      2 | rls_regress_user2 | great technology book
+   8 |  44 |      1 | rls_regress_user2 | great manga
+(8 rows)
+
+SELECT * FROM document NATURAL JOIN category WHERE f_leak(dtitle) ORDER BY did;
+NOTICE:  f_leak => my first novel
+NOTICE:  f_leak => my second novel
+NOTICE:  f_leak => my science fiction
+NOTICE:  f_leak => my first manga
+NOTICE:  f_leak => my second manga
+NOTICE:  f_leak => great science fiction
+NOTICE:  f_leak => great technology book
+NOTICE:  f_leak => great manga
+ cid | did | dlevel |      dauthor      |        dtitle         |      cname      
+-----+-----+--------+-------------------+-----------------------+-----------------
+  11 |   1 |      1 | rls_regress_user1 | my first novel        | novel
+  11 |   2 |      2 | rls_regress_user1 | my second novel       | novel
+  22 |   3 |      2 | rls_regress_user1 | my science fiction    | science fiction
+  44 |   4 |      1 | rls_regress_user1 | my first manga        | manga
+  44 |   5 |      2 | rls_regress_user1 | my second manga       | manga
+  22 |   6 |      1 | rls_regress_user2 | great science fiction | science fiction
+  33 |   7 |      2 | rls_regress_user2 | great technology book | technology
+  44 |   8 |      1 | rls_regress_user2 | great manga           | manga
+(8 rows)
+
+EXPLAIN (COSTS OFF) SELECT * FROM document WHERE f_leak(dtitle);
+                        QUERY PLAN                        
+----------------------------------------------------------
+ Subquery Scan on document
+   Filter: f_leak(document.dtitle)
+   ->  Seq Scan on document document_1
+         Filter: (dlevel <= $0)
+         InitPlan 1 (returns $0)
+           ->  Index Scan using uaccount_pkey on uaccount
+                 Index Cond: (pguser = "current_user"())
+(7 rows)
+
+EXPLAIN (COSTS OFF) SELECT * FROM document NATURAL JOIN category WHERE f_leak(dtitle);
+                              QUERY PLAN                              
+----------------------------------------------------------------------
+ Hash Join
+   Hash Cond: (category.cid = document.cid)
+   ->  Seq Scan on category
+   ->  Hash
+         ->  Subquery Scan on document
+               Filter: f_leak(document.dtitle)
+               ->  Seq Scan on document document_1
+                     Filter: (dlevel <= $0)
+                     InitPlan 1 (returns $0)
+                       ->  Index Scan using uaccount_pkey on uaccount
+                             Index Cond: (pguser = "current_user"())
+(11 rows)
+
+-- only owner can change row-level security
+ALTER POLICY p1 ON document USING (true);    --fail
+ERROR:  must be owner of relation document
+DROP POLICY p1 ON document;                  --fail
+ERROR:  must be owner of relation document
+SET SESSION AUTHORIZATION rls_regress_user0;
+ALTER POLICY p1 ON document USING (dauthor = current_user);
+-- viewpoint from rls_regress_user1 again
+SET SESSION AUTHORIZATION rls_regress_user1;
+SELECT * FROM document WHERE f_leak(dtitle) ORDER BY did;
+NOTICE:  f_leak => my first novel
+NOTICE:  f_leak => my second novel
+NOTICE:  f_leak => my science fiction
+NOTICE:  f_leak => my first manga
+NOTICE:  f_leak => my second manga
+ did | cid | dlevel |      dauthor      |       dtitle       
+-----+-----+--------+-------------------+--------------------
+   1 |  11 |      1 | rls_regress_user1 | my first novel
+   2 |  11 |      2 | rls_regress_user1 | my second novel
+   3 |  22 |      2 | rls_regress_user1 | my science fiction
+   4 |  44 |      1 | rls_regress_user1 | my first manga
+   5 |  44 |      2 | rls_regress_user1 | my second manga
+(5 rows)
+
+SELECT * FROM document NATURAL JOIN category WHERE f_leak(dtitle) ORDER by did;
+NOTICE:  f_leak => my first novel
+NOTICE:  f_leak => my second novel
+NOTICE:  f_leak => my science fiction
+NOTICE:  f_leak => my first manga
+NOTICE:  f_leak => my second manga
+ cid | did | dlevel |      dauthor      |       dtitle       |      cname      
+-----+-----+--------+-------------------+--------------------+-----------------
+  11 |   1 |      1 | rls_regress_user1 | my first novel     | novel
+  11 |   2 |      2 | rls_regress_user1 | my second novel    | novel
+  22 |   3 |      2 | rls_regress_user1 | my science fiction | science fiction
+  44 |   4 |      1 | rls_regress_user1 | my first manga     | manga
+  44 |   5 |      2 | rls_regress_user1 | my second manga    | manga
+(5 rows)
+
+-- viewpoint from rls_regres_user2 again
+SET SESSION AUTHORIZATION rls_regress_user2;
+SELECT * FROM document WHERE f_leak(dtitle) ORDER BY did;
+NOTICE:  f_leak => great science fiction
+NOTICE:  f_leak => great technology book
+NOTICE:  f_leak => great manga
+ did | cid | dlevel |      dauthor      |        dtitle         
+-----+-----+--------+-------------------+-----------------------
+   6 |  22 |      1 | rls_regress_user2 | great science fiction
+   7 |  33 |      2 | rls_regress_user2 | great technology book
+   8 |  44 |      1 | rls_regress_user2 | great manga
+(3 rows)
+
+SELECT * FROM document NATURAL JOIN category WHERE f_leak(dtitle) ORDER by did;
+NOTICE:  f_leak => great science fiction
+NOTICE:  f_leak => great technology book
+NOTICE:  f_leak => great manga
+ cid | did | dlevel |      dauthor      |        dtitle         |      cname      
+-----+-----+--------+-------------------+-----------------------+-----------------
+  22 |   6 |      1 | rls_regress_user2 | great science fiction | science fiction
+  33 |   7 |      2 | rls_regress_user2 | great technology book | technology
+  44 |   8 |      1 | rls_regress_user2 | great manga           | manga
+(3 rows)
+
+EXPLAIN (COSTS OFF) SELECT * FROM document WHERE f_leak(dtitle);
+                  QUERY PLAN                  
+----------------------------------------------
+ Subquery Scan on document
+   Filter: f_leak(document.dtitle)
+   ->  Seq Scan on document document_1
+         Filter: (dauthor = "current_user"())
+(4 rows)
+
+EXPLAIN (COSTS OFF) SELECT * FROM document NATURAL JOIN category WHERE f_leak(dtitle);
+                     QUERY PLAN                     
+----------------------------------------------------
+ Nested Loop
+   ->  Subquery Scan on document
+         Filter: f_leak(document.dtitle)
+         ->  Seq Scan on document document_1
+               Filter: (dauthor = "current_user"())
+   ->  Index Scan using category_pkey on category
+         Index Cond: (cid = document.cid)
+(7 rows)
+
+-- interaction of FK/PK constraints
+SET SESSION AUTHORIZATION rls_regress_user0;
+CREATE POLICY p2 ON category
+    USING (CASE WHEN current_user = 'rls_regress_user1' THEN cid IN (11, 33)
+           WHEN current_user = 'rls_regress_user2' THEN cid IN (22, 44)
+           ELSE false END);
+ALTER TABLE category ENABLE ROW LEVEL SECURITY;
+-- cannot delete PK referenced by invisible FK
+SET SESSION AUTHORIZATION rls_regress_user1;
+SELECT * FROM document d FULL OUTER JOIN category c on d.cid = c.cid;
+ did | cid | dlevel |      dauthor      |       dtitle       | cid |   cname    
+-----+-----+--------+-------------------+--------------------+-----+------------
+   2 |  11 |      2 | rls_regress_user1 | my second novel    |  11 | novel
+   1 |  11 |      1 | rls_regress_user1 | my first novel     |  11 | novel
+     |     |        |                   |                    |  33 | technology
+   5 |  44 |      2 | rls_regress_user1 | my second manga    |     | 
+   4 |  44 |      1 | rls_regress_user1 | my first manga     |     | 
+   3 |  22 |      2 | rls_regress_user1 | my science fiction |     | 
+(6 rows)
+
+DELETE FROM category WHERE cid = 33;    -- fails with FK violation
+ERROR:  update or delete on table "category" violates foreign key constraint "document_cid_fkey" on table "document"
+DETAIL:  Key (cid)=(33) is still referenced from table "document".
+-- cannot insert FK referencing invisible PK
+SET SESSION AUTHORIZATION rls_regress_user2;
+SELECT * FROM document d FULL OUTER JOIN category c on d.cid = c.cid;
+ did | cid | dlevel |      dauthor      |        dtitle         | cid |      cname      
+-----+-----+--------+-------------------+-----------------------+-----+-----------------
+   6 |  22 |      1 | rls_regress_user2 | great science fiction |  22 | science fiction
+   8 |  44 |      1 | rls_regress_user2 | great manga           |  44 | manga
+   7 |  33 |      2 | rls_regress_user2 | great technology book |     | 
+(3 rows)
+
+INSERT INTO document VALUES (10, 33, 1, current_user, 'hoge');
+-- UNIQUE or PRIMARY KEY constraint violation DOES reveal presence of row
+SET SESSION AUTHORIZATION rls_regress_user1;
+INSERT INTO document VALUES (8, 44, 1, 'rls_regress_user1', 'my third manga'); -- Must fail with unique violation, revealing presence of did we can't see
+ERROR:  duplicate key value violates unique constraint "document_pkey"
+DETAIL:  Key (did)=(8) already exists.
+SELECT * FROM document WHERE did = 8; -- and confirm we can't see it
+ did | cid | dlevel | dauthor | dtitle 
+-----+-----+--------+---------+--------
+(0 rows)
+
+-- database superuser cannot bypass RLS policy when enabled
+RESET SESSION AUTHORIZATION;
+SET row_security TO ON;
+SELECT * FROM document;
+ did | cid | dlevel |      dauthor      |        dtitle         
+-----+-----+--------+-------------------+-----------------------
+   1 |  11 |      1 | rls_regress_user1 | my first novel
+   2 |  11 |      2 | rls_regress_user1 | my second novel
+   3 |  22 |      2 | rls_regress_user1 | my science fiction
+   4 |  44 |      1 | rls_regress_user1 | my first manga
+   5 |  44 |      2 | rls_regress_user1 | my second manga
+   6 |  22 |      1 | rls_regress_user2 | great science fiction
+   7 |  33 |      2 | rls_regress_user2 | great technology book
+   8 |  44 |      1 | rls_regress_user2 | great manga
+  10 |  33 |      1 | rls_regress_user2 | hoge
+(9 rows)
+
+SELECT * FROM category;
+ cid |      cname      
+-----+-----------------
+  11 | novel
+  22 | science fiction
+  33 | technology
+  44 | manga
+(4 rows)
+
+-- database superuser cannot bypass RLS policy when FORCE enabled.
+RESET SESSION AUTHORIZATION;
+SET row_security TO FORCE;
+SELECT * FROM document;
+ did | cid | dlevel | dauthor | dtitle 
+-----+-----+--------+---------+--------
+(0 rows)
+
+SELECT * FROM category;
+ cid | cname 
+-----+-------
+(0 rows)
+
+-- database superuser can bypass RLS policy when disabled
+RESET SESSION AUTHORIZATION;
+SET row_security TO OFF;
+SELECT * FROM document;
+ did | cid | dlevel |      dauthor      |        dtitle         
+-----+-----+--------+-------------------+-----------------------
+   1 |  11 |      1 | rls_regress_user1 | my first novel
+   2 |  11 |      2 | rls_regress_user1 | my second novel
+   3 |  22 |      2 | rls_regress_user1 | my science fiction
+   4 |  44 |      1 | rls_regress_user1 | my first manga
+   5 |  44 |      2 | rls_regress_user1 | my second manga
+   6 |  22 |      1 | rls_regress_user2 | great science fiction
+   7 |  33 |      2 | rls_regress_user2 | great technology book
+   8 |  44 |      1 | rls_regress_user2 | great manga
+  10 |  33 |      1 | rls_regress_user2 | hoge
+(9 rows)
+
+SELECT * FROM category;
+ cid |      cname      
+-----+-----------------
+  11 | novel
+  22 | science fiction
+  33 | technology
+  44 | manga
+(4 rows)
+
+-- database non-superuser with bypass privilege can bypass RLS policy when disabled
+SET SESSION AUTHORIZATION rls_regress_exempt_user;
+SET row_security TO OFF;
+SELECT * FROM document;
+ did | cid | dlevel |      dauthor      |        dtitle         
+-----+-----+--------+-------------------+-----------------------
+   1 |  11 |      1 | rls_regress_user1 | my first novel
+   2 |  11 |      2 | rls_regress_user1 | my second novel
+   3 |  22 |      2 | rls_regress_user1 | my science fiction
+   4 |  44 |      1 | rls_regress_user1 | my first manga
+   5 |  44 |      2 | rls_regress_user1 | my second manga
+   6 |  22 |      1 | rls_regress_user2 | great science fiction
+   7 |  33 |      2 | rls_regress_user2 | great technology book
+   8 |  44 |      1 | rls_regress_user2 | great manga
+  10 |  33 |      1 | rls_regress_user2 | hoge
+(9 rows)
+
+SELECT * FROM category;
+ cid |      cname      
+-----+-----------------
+  11 | novel
+  22 | science fiction
+  33 | technology
+  44 | manga
+(4 rows)
+
+-- RLS policy applies to table owner when FORCE enabled.
+SET SESSION AUTHORIZATION rls_regress_user0;
+SET row_security TO FORCE;
+SELECT * FROM document;
+ did | cid | dlevel | dauthor | dtitle 
+-----+-----+--------+---------+--------
+(0 rows)
+
+SELECT * FROM category;
+ cid | cname 
+-----+-------
+(0 rows)
+
+-- RLS policy does not apply to table owner when RLS enabled.
+SET SESSION AUTHORIZATION rls_regress_user0;
+SET row_security TO ON;
+SELECT * FROM document;
+ did | cid | dlevel |      dauthor      |        dtitle         
+-----+-----+--------+-------------------+-----------------------
+   1 |  11 |      1 | rls_regress_user1 | my first novel
+   2 |  11 |      2 | rls_regress_user1 | my second novel
+   3 |  22 |      2 | rls_regress_user1 | my science fiction
+   4 |  44 |      1 | rls_regress_user1 | my first manga
+   5 |  44 |      2 | rls_regress_user1 | my second manga
+   6 |  22 |      1 | rls_regress_user2 | great science fiction
+   7 |  33 |      2 | rls_regress_user2 | great technology book
+   8 |  44 |      1 | rls_regress_user2 | great manga
+  10 |  33 |      1 | rls_regress_user2 | hoge
+(9 rows)
+
+SELECT * FROM category;
+ cid |      cname      
+-----+-----------------
+  11 | novel
+  22 | science fiction
+  33 | technology
+  44 | manga
+(4 rows)
+
+-- RLS policy does not apply to table owner when RLS disabled.
+SET SESSION AUTHORIZATION rls_regress_user0;
+SET row_security TO OFF;
+SELECT * FROM document;
+ did | cid | dlevel |      dauthor      |        dtitle         
+-----+-----+--------+-------------------+-----------------------
+   1 |  11 |      1 | rls_regress_user1 | my first novel
+   2 |  11 |      2 | rls_regress_user1 | my second novel
+   3 |  22 |      2 | rls_regress_user1 | my science fiction
+   4 |  44 |      1 | rls_regress_user1 | my first manga
+   5 |  44 |      2 | rls_regress_user1 | my second manga
+   6 |  22 |      1 | rls_regress_user2 | great science fiction
+   7 |  33 |      2 | rls_regress_user2 | great technology book
+   8 |  44 |      1 | rls_regress_user2 | great manga
+  10 |  33 |      1 | rls_regress_user2 | hoge
+(9 rows)
+
+SELECT * FROM category;
+ cid |      cname      
+-----+-----------------
+  11 | novel
+  22 | science fiction
+  33 | technology
+  44 | manga
+(4 rows)
+
+--
+-- Table inheritance and RLS policy
+--
+SET SESSION AUTHORIZATION rls_regress_user0;
+SET row_security TO ON;
+CREATE TABLE t1 (a int, junk1 text, b text) WITH OIDS;
+ALTER TABLE t1 DROP COLUMN junk1;    -- just a disturbing factor
+GRANT ALL ON t1 TO public;
+COPY t1 FROM stdin WITH (oids);
+CREATE TABLE t2 (c float) INHERITS (t1);
+COPY t2 FROM stdin WITH (oids);
+CREATE TABLE t3 (c text, b text, a int) WITH OIDS;
+ALTER TABLE t3 INHERIT t1;
+COPY t3(a,b,c) FROM stdin WITH (oids);
+CREATE POLICY p1 ON t1 FOR ALL TO PUBLIC USING (a % 2 = 0); -- be even number
+CREATE POLICY p2 ON t2 FOR ALL TO PUBLIC USING (a % 2 = 1); -- be odd number
+ALTER TABLE t1 ENABLE ROW LEVEL SECURITY;
+ALTER TABLE t2 ENABLE ROW LEVEL SECURITY;
+SET SESSION AUTHORIZATION rls_regress_user1;
+SELECT * FROM t1;
+ a |  b  
+---+-----
+ 2 | bbb
+ 4 | ddd
+ 2 | bcd
+ 4 | def
+ 2 | yyy
+(5 rows)
+
+EXPLAIN (COSTS OFF) SELECT * FROM t1;
+          QUERY PLAN           
+-------------------------------
+ Append
+   ->  Seq Scan on t1
+         Filter: ((a % 2) = 0)
+   ->  Seq Scan on t2
+         Filter: ((a % 2) = 0)
+   ->  Seq Scan on t3
+         Filter: ((a % 2) = 0)
+(7 rows)
+
+SELECT * FROM t1 WHERE f_leak(b);
+NOTICE:  f_leak => bbb
+NOTICE:  f_leak => ddd
+NOTICE:  f_leak => bcd
+NOTICE:  f_leak => def
+NOTICE:  f_leak => yyy
+ a |  b  
+---+-----
+ 2 | bbb
+ 4 | ddd
+ 2 | bcd
+ 4 | def
+ 2 | yyy
+(5 rows)
+
+EXPLAIN (COSTS OFF) SELECT * FROM t1 WHERE f_leak(b);
+             QUERY PLAN              
+-------------------------------------
+ Subquery Scan on t1
+   Filter: f_leak(t1.b)
+   ->  Append
+         ->  Seq Scan on t1 t1_1
+               Filter: ((a % 2) = 0)
+         ->  Seq Scan on t2
+               Filter: ((a % 2) = 0)
+         ->  Seq Scan on t3
+               Filter: ((a % 2) = 0)
+(9 rows)
+
+-- reference to system column
+SELECT oid, * FROM t1;
+ oid | a |  b  
+-----+---+-----
+ 102 | 2 | bbb
+ 104 | 4 | ddd
+ 202 | 2 | bcd
+ 204 | 4 | def
+ 302 | 2 | yyy
+(5 rows)
+
+EXPLAIN (COSTS OFF) SELECT *, t1 FROM t1;
+          QUERY PLAN           
+-------------------------------
+ Append
+   ->  Seq Scan on t1
+         Filter: ((a % 2) = 0)
+   ->  Seq Scan on t2
+         Filter: ((a % 2) = 0)
+   ->  Seq Scan on t3
+         Filter: ((a % 2) = 0)
+(7 rows)
+
+-- reference to whole-row reference
+SELECT *, t1 FROM t1;
+ a |  b  |   t1    
+---+-----+---------
+ 2 | bbb | (2,bbb)
+ 4 | ddd | (4,ddd)
+ 2 | bcd | (2,bcd)
+ 4 | def | (4,def)
+ 2 | yyy | (2,yyy)
+(5 rows)
+
+EXPLAIN (COSTS OFF) SELECT *, t1 FROM t1;
+          QUERY PLAN           
+-------------------------------
+ Append
+   ->  Seq Scan on t1
+         Filter: ((a % 2) = 0)
+   ->  Seq Scan on t2
+         Filter: ((a % 2) = 0)
+   ->  Seq Scan on t3
+         Filter: ((a % 2) = 0)
+(7 rows)
+
+-- for share/update lock
+SELECT * FROM t1 FOR SHARE;
+ a |  b  
+---+-----
+ 2 | bbb
+ 4 | ddd
+ 2 | bcd
+ 4 | def
+ 2 | yyy
+(5 rows)
+
+EXPLAIN (COSTS OFF) SELECT * FROM t1 FOR SHARE;
+                      QUERY PLAN                       
+-------------------------------------------------------
+ LockRows
+   ->  Subquery Scan on t1
+         ->  LockRows
+               ->  Result
+                     ->  Append
+                           ->  Seq Scan on t1 t1_1
+                                 Filter: ((a % 2) = 0)
+                           ->  Seq Scan on t2
+                                 Filter: ((a % 2) = 0)
+                           ->  Seq Scan on t3
+                                 Filter: ((a % 2) = 0)
+(11 rows)
+
+SELECT * FROM t1 WHERE f_leak(b) FOR SHARE;
+NOTICE:  f_leak => bbb
+NOTICE:  f_leak => ddd
+NOTICE:  f_leak => bcd
+NOTICE:  f_leak => def
+NOTICE:  f_leak => yyy
+ a |  b  
+---+-----
+ 2 | bbb
+ 4 | ddd
+ 2 | bcd
+ 4 | def
+ 2 | yyy
+(5 rows)
+
+EXPLAIN (COSTS OFF) SELECT * FROM t1 WHERE f_leak(b) FOR SHARE;
+                      QUERY PLAN                       
+-------------------------------------------------------
+ LockRows
+   ->  Subquery Scan on t1
+         Filter: f_leak(t1.b)
+         ->  LockRows
+               ->  Result
+                     ->  Append
+                           ->  Seq Scan on t1 t1_1
+                                 Filter: ((a % 2) = 0)
+                           ->  Seq Scan on t2
+                                 Filter: ((a % 2) = 0)
+                           ->  Seq Scan on t3
+                                 Filter: ((a % 2) = 0)
+(12 rows)
+
+-- superuser is allowed to bypass RLS checks
+RESET SESSION AUTHORIZATION;
+SET row_security TO OFF;
+SELECT * FROM t1 WHERE f_leak(b);
+NOTICE:  f_leak => aaa
+NOTICE:  f_leak => bbb
+NOTICE:  f_leak => ccc
+NOTICE:  f_leak => ddd
+NOTICE:  f_leak => abc
+NOTICE:  f_leak => bcd
+NOTICE:  f_leak => cde
+NOTICE:  f_leak => def
+NOTICE:  f_leak => xxx
+NOTICE:  f_leak => yyy
+NOTICE:  f_leak => zzz
+ a |  b  
+---+-----
+ 1 | aaa
+ 2 | bbb
+ 3 | ccc
+ 4 | ddd
+ 1 | abc
+ 2 | bcd
+ 3 | cde
+ 4 | def
+ 1 | xxx
+ 2 | yyy
+ 3 | zzz
+(11 rows)
+
+EXPLAIN (COSTS OFF) SELECT * FROM t1 WHERE f_leak(b);
+        QUERY PLAN         
+---------------------------
+ Append
+   ->  Seq Scan on t1
+         Filter: f_leak(b)
+   ->  Seq Scan on t2
+         Filter: f_leak(b)
+   ->  Seq Scan on t3
+         Filter: f_leak(b)
+(7 rows)
+
+-- non-superuser with bypass privilege can bypass RLS policy when disabled
+SET SESSION AUTHORIZATION rls_regress_exempt_user;
+SET row_security TO OFF;
+SELECT * FROM t1 WHERE f_leak(b);
+NOTICE:  f_leak => aaa
+NOTICE:  f_leak => bbb
+NOTICE:  f_leak => ccc
+NOTICE:  f_leak => ddd
+NOTICE:  f_leak => abc
+NOTICE:  f_leak => bcd
+NOTICE:  f_leak => cde
+NOTICE:  f_leak => def
+NOTICE:  f_leak => xxx
+NOTICE:  f_leak => yyy
+NOTICE:  f_leak => zzz
+ a |  b  
+---+-----
+ 1 | aaa
+ 2 | bbb
+ 3 | ccc
+ 4 | ddd
+ 1 | abc
+ 2 | bcd
+ 3 | cde
+ 4 | def
+ 1 | xxx
+ 2 | yyy
+ 3 | zzz
+(11 rows)
+
+EXPLAIN (COSTS OFF) SELECT * FROM t1 WHERE f_leak(b);
+        QUERY PLAN         
+---------------------------
+ Append
+   ->  Seq Scan on t1
+         Filter: f_leak(b)
+   ->  Seq Scan on t2
+         Filter: f_leak(b)
+   ->  Seq Scan on t3
+         Filter: f_leak(b)
+(7 rows)
+
+----- Dependencies -----
+SET SESSION AUTHORIZATION rls_regress_user0;
+SET row_security TO ON;
+CREATE TABLE dependee (x integer, y integer);
+CREATE TABLE dependent (x integer, y integer);
+CREATE POLICY d1 ON dependent FOR ALL
+    TO PUBLIC
+    USING (x = (SELECT d.x FROM dependee d WHERE d.y = y));
+DROP TABLE dependee; -- Should fail without CASCADE due to dependency on row-security qual?
+ERROR:  cannot drop table dependee because other objects depend on it
+DETAIL:  policy d1 on table dependent depends on table dependee
+HINT:  Use DROP ... CASCADE to drop the dependent objects too.
+DROP TABLE dependee CASCADE;
+NOTICE:  drop cascades to policy d1 on table dependent
+EXPLAIN (COSTS OFF) SELECT * FROM dependent; -- After drop, should be unqualified
+      QUERY PLAN       
+-----------------------
+ Seq Scan on dependent
+(1 row)
+
+-----   RECURSION    ----
+--
+-- Simple recursion
+--
+SET SESSION AUTHORIZATION rls_regress_user0;
+CREATE TABLE rec1 (x integer, y integer);
+CREATE POLICY r1 ON rec1 USING (x = (SELECT r.x FROM rec1 r WHERE y = r.y));
+ALTER TABLE rec1 ENABLE ROW LEVEL SECURITY;
+SET SESSION AUTHORIZATION rls_regress_user1;
+SELECT * FROM rec1; -- fail, direct recursion
+ERROR:  infinite recursion detected in row-security policy for relation "rec1"
+--
+-- Mutual recursion
+--
+SET SESSION AUTHORIZATION rls_regress_user0;
+CREATE TABLE rec2 (a integer, b integer);
+ALTER POLICY r1 ON rec1 USING (x = (SELECT a FROM rec2 WHERE b = y));
+CREATE POLICY r2 ON rec2 USING (a = (SELECT x FROM rec1 WHERE y = b));
+ALTER TABLE rec2 ENABLE ROW LEVEL SECURITY;
+SET SESSION AUTHORIZATION rls_regress_user1;
+SELECT * FROM rec1;    -- fail, mutual recursion
+ERROR:  infinite recursion detected in row-security policy for relation "rec1"
+--
+-- Mutual recursion via views
+--
+SET SESSION AUTHORIZATION rls_regress_user1;
+CREATE VIEW rec1v AS SELECT * FROM rec1;
+CREATE VIEW rec2v AS SELECT * FROM rec2;
+SET SESSION AUTHORIZATION rls_regress_user0;
+ALTER POLICY r1 ON rec1 USING (x = (SELECT a FROM rec2v WHERE b = y));
+ALTER POLICY r2 ON rec2 USING (a = (SELECT x FROM rec1v WHERE y = b));
+SET SESSION AUTHORIZATION rls_regress_user1;
+SELECT * FROM rec1;    -- fail, mutual recursion via views
+ERROR:  infinite recursion detected in row-security policy for relation "rec1"
+--
+-- Mutual recursion via .s.b views
+--
+SET SESSION AUTHORIZATION rls_regress_user1;
+DROP VIEW rec1v, rec2v CASCADE;
+NOTICE:  drop cascades to 2 other objects
+DETAIL:  drop cascades to policy r1 on table rec1
+drop cascades to policy r2 on table rec2
+CREATE VIEW rec1v WITH (security_barrier) AS SELECT * FROM rec1;
+CREATE VIEW rec2v WITH (security_barrier) AS SELECT * FROM rec2;
+SET SESSION AUTHORIZATION rls_regress_user0;
+CREATE POLICY r1 ON rec1 USING (x = (SELECT a FROM rec2v WHERE b = y));
+CREATE POLICY r2 ON rec2 USING (a = (SELECT x FROM rec1v WHERE y = b));
+SET SESSION AUTHORIZATION rls_regress_user1;
+SELECT * FROM rec1;    -- fail, mutual recursion via s.b. views
+ERROR:  infinite recursion detected in row-security policy for relation "rec1"
+--
+-- recursive RLS and VIEWs in policy
+--
+SET SESSION AUTHORIZATION rls_regress_user0;
+CREATE TABLE s1 (a int, b text);
+INSERT INTO s1 (SELECT x, md5(x::text) FROM generate_series(-10,10) x);
+CREATE TABLE s2 (x int, y text);
+INSERT INTO s2 (SELECT x, md5(x::text) FROM generate_series(-6,6) x);
+GRANT SELECT ON s1, s2 TO rls_regress_user1;
+CREATE POLICY p1 ON s1 USING (a in (select x from s2 where y like '%2f%'));
+CREATE POLICY p2 ON s2 USING (x in (select a from s1 where b like '%22%'));
+CREATE POLICY p3 ON s1 FOR INSERT WITH CHECK (a = (SELECT a FROM s1));
+ALTER TABLE s1 ENABLE ROW LEVEL SECURITY;
+ALTER TABLE s2 ENABLE ROW LEVEL SECURITY;
+SET SESSION AUTHORIZATION rls_regress_user1;
+CREATE VIEW v2 AS SELECT * FROM s2 WHERE y like '%af%';
+SELECT * FROM s1 WHERE f_leak(b); -- fail (infinite recursion)
+ERROR:  infinite recursion detected in row-security policy for relation "s1"
+INSERT INTO s1 VALUES (1, 'foo'); -- fail (infinite recursion)
+ERROR:  infinite recursion detected in row-security policy for relation "s1"
+SET SESSION AUTHORIZATION rls_regress_user0;
+DROP POLICY p3 on s1;
+ALTER POLICY p2 ON s2 USING (x % 2 = 0);
+SET SESSION AUTHORIZATION rls_regress_user1;
+SELECT * FROM s1 WHERE f_leak(b);	-- OK
+NOTICE:  f_leak => c81e728d9d4c2f636f067f89cc14862c
+NOTICE:  f_leak => a87ff679a2f3e71d9181a67b7542122c
+ a |                b                 
+---+----------------------------------
+ 2 | c81e728d9d4c2f636f067f89cc14862c
+ 4 | a87ff679a2f3e71d9181a67b7542122c
+(2 rows)
+
+EXPLAIN (COSTS OFF) SELECT * FROM only s1 WHERE f_leak(b);
+                        QUERY PLAN                        
+----------------------------------------------------------
+ Subquery Scan on s1
+   Filter: f_leak(s1.b)
+   ->  Hash Join
+         Hash Cond: (s1_1.a = s2.x)
+         ->  Seq Scan on s1 s1_1
+         ->  Hash
+               ->  HashAggregate
+                     Group Key: s2.x
+                     ->  Subquery Scan on s2
+                           Filter: (s2.y ~~ '%2f%'::text)
+                           ->  Seq Scan on s2 s2_1
+                                 Filter: ((x % 2) = 0)
+(12 rows)
+
+SET SESSION AUTHORIZATION rls_regress_user0;
+ALTER POLICY p1 ON s1 USING (a in (select x from v2)); -- using VIEW in RLS policy
+SET SESSION AUTHORIZATION rls_regress_user1;
+SELECT * FROM s1 WHERE f_leak(b);	-- OK
+NOTICE:  f_leak => 0267aaf632e87a63288a08331f22c7c3
+NOTICE:  f_leak => 1679091c5a880faf6fb5e6087eb1b2dc
+ a  |                b                 
+----+----------------------------------
+ -4 | 0267aaf632e87a63288a08331f22c7c3
+  6 | 1679091c5a880faf6fb5e6087eb1b2dc
+(2 rows)
+
+EXPLAIN (COSTS OFF) SELECT * FROM s1 WHERE f_leak(b);
+                        QUERY PLAN                        
+----------------------------------------------------------
+ Subquery Scan on s1
+   Filter: f_leak(s1.b)
+   ->  Hash Join
+         Hash Cond: (s1_1.a = s2.x)
+         ->  Seq Scan on s1 s1_1
+         ->  Hash
+               ->  HashAggregate
+                     Group Key: s2.x
+                     ->  Subquery Scan on s2
+                           Filter: (s2.y ~~ '%af%'::text)
+                           ->  Seq Scan on s2 s2_1
+                                 Filter: ((x % 2) = 0)
+(12 rows)
+
+SELECT (SELECT x FROM s1 LIMIT 1) xx, * FROM s2 WHERE y like '%28%';
+ xx | x  |                y                 
+----+----+----------------------------------
+ -6 | -6 | 596a3d04481816330f07e4f97510c28f
+ -4 | -4 | 0267aaf632e87a63288a08331f22c7c3
+  2 |  2 | c81e728d9d4c2f636f067f89cc14862c
+(3 rows)
+
+EXPLAIN (COSTS OFF) SELECT (SELECT x FROM s1 LIMIT 1) xx, * FROM s2 WHERE y like '%28%';
+                             QUERY PLAN                             
+--------------------------------------------------------------------
+ Subquery Scan on s2
+   Filter: (s2.y ~~ '%28%'::text)
+   ->  Seq Scan on s2 s2_1
+         Filter: ((x % 2) = 0)
+   SubPlan 1
+     ->  Limit
+           ->  Subquery Scan on s1
+                 ->  Nested Loop Semi Join
+                       Join Filter: (s1_1.a = s2_2.x)
+                       ->  Seq Scan on s1 s1_1
+                       ->  Materialize
+                             ->  Subquery Scan on s2_2
+                                   Filter: (s2_2.y ~~ '%af%'::text)
+                                   ->  Seq Scan on s2 s2_3
+                                         Filter: ((x % 2) = 0)
+(15 rows)
+
+SET SESSION AUTHORIZATION rls_regress_user0;
+ALTER POLICY p2 ON s2 USING (x in (select a from s1 where b like '%d2%'));
+SET SESSION AUTHORIZATION rls_regress_user1;
+SELECT * FROM s1 WHERE f_leak(b);	-- fail (infinite recursion via view)
+ERROR:  infinite recursion detected in row-security policy for relation "s1"
+-- prepared statement with rls_regress_user0 privilege
+PREPARE p1(int) AS SELECT * FROM t1 WHERE a <= $1;
+EXECUTE p1(2);
+ a |  b  
+---+-----
+ 2 | bbb
+ 2 | bcd
+ 2 | yyy
+(3 rows)
+
+EXPLAIN (COSTS OFF) EXECUTE p1(2);
+                  QUERY PLAN                  
+----------------------------------------------
+ Append
+   ->  Seq Scan on t1
+         Filter: ((a <= 2) AND ((a % 2) = 0))
+   ->  Seq Scan on t2
+         Filter: ((a <= 2) AND ((a % 2) = 0))
+   ->  Seq Scan on t3
+         Filter: ((a <= 2) AND ((a % 2) = 0))
+(7 rows)
+
+-- superuser is allowed to bypass RLS checks
+RESET SESSION AUTHORIZATION;
+SET row_security TO OFF;
+SELECT * FROM t1 WHERE f_leak(b);
+NOTICE:  f_leak => aaa
+NOTICE:  f_leak => bbb
+NOTICE:  f_leak => ccc
+NOTICE:  f_leak => ddd
+NOTICE:  f_leak => abc
+NOTICE:  f_leak => bcd
+NOTICE:  f_leak => cde
+NOTICE:  f_leak => def
+NOTICE:  f_leak => xxx
+NOTICE:  f_leak => yyy
+NOTICE:  f_leak => zzz
+ a |  b  
+---+-----
+ 1 | aaa
+ 2 | bbb
+ 3 | ccc
+ 4 | ddd
+ 1 | abc
+ 2 | bcd
+ 3 | cde
+ 4 | def
+ 1 | xxx
+ 2 | yyy
+ 3 | zzz
+(11 rows)
+
+EXPLAIN (COSTS OFF) SELECT * FROM t1 WHERE f_leak(b);
+        QUERY PLAN         
+---------------------------
+ Append
+   ->  Seq Scan on t1
+         Filter: f_leak(b)
+   ->  Seq Scan on t2
+         Filter: f_leak(b)
+   ->  Seq Scan on t3
+         Filter: f_leak(b)
+(7 rows)
+
+-- plan cache should be invalidated
+EXECUTE p1(2);
+ a |  b  
+---+-----
+ 1 | aaa
+ 2 | bbb
+ 1 | abc
+ 2 | bcd
+ 1 | xxx
+ 2 | yyy
+(6 rows)
+
+EXPLAIN (COSTS OFF) EXECUTE p1(2);
+        QUERY PLAN        
+--------------------------
+ Append
+   ->  Seq Scan on t1
+         Filter: (a <= 2)
+   ->  Seq Scan on t2
+         Filter: (a <= 2)
+   ->  Seq Scan on t3
+         Filter: (a <= 2)
+(7 rows)
+
+PREPARE p2(int) AS SELECT * FROM t1 WHERE a = $1;
+EXECUTE p2(2);
+ a |  b  
+---+-----
+ 2 | bbb
+ 2 | bcd
+ 2 | yyy
+(3 rows)
+
+EXPLAIN (COSTS OFF) EXECUTE p2(2);
+       QUERY PLAN        
+-------------------------
+ Append
+   ->  Seq Scan on t1
+         Filter: (a = 2)
+   ->  Seq Scan on t2
+         Filter: (a = 2)
+   ->  Seq Scan on t3
+         Filter: (a = 2)
+(7 rows)
+
+-- also, case when privilege switch from superuser
+SET SESSION AUTHORIZATION rls_regress_user1;
+SET row_security TO ON;
+EXECUTE p2(2);
+ a |  b  
+---+-----
+ 2 | bbb
+ 2 | bcd
+ 2 | yyy
+(3 rows)
+
+EXPLAIN (COSTS OFF) EXECUTE p2(2);
+                 QUERY PLAN                  
+---------------------------------------------
+ Append
+   ->  Seq Scan on t1
+         Filter: ((a = 2) AND ((a % 2) = 0))
+   ->  Seq Scan on t2
+         Filter: ((a = 2) AND ((a % 2) = 0))
+   ->  Seq Scan on t3
+         Filter: ((a = 2) AND ((a % 2) = 0))
+(7 rows)
+
+--
+-- UPDATE / DELETE and Row-level security
+--
+SET SESSION AUTHORIZATION rls_regress_user1;
+EXPLAIN (COSTS OFF) UPDATE t1 SET b = b || b WHERE f_leak(b);
+             QUERY PLAN              
+-------------------------------------
+ Update on t1 t1_3
+   ->  Subquery Scan on t1
+         Filter: f_leak(t1.b)
+         ->  Seq Scan on t1 t1_4
+               Filter: ((a % 2) = 0)
+   ->  Subquery Scan on t1_1
+         Filter: f_leak(t1_1.b)
+         ->  Seq Scan on t2
+               Filter: ((a % 2) = 0)
+   ->  Subquery Scan on t1_2
+         Filter: f_leak(t1_2.b)
+         ->  Seq Scan on t3
+               Filter: ((a % 2) = 0)
+(13 rows)
+
+UPDATE t1 SET b = b || b WHERE f_leak(b);
+NOTICE:  f_leak => bbb
+NOTICE:  f_leak => ddd
+NOTICE:  f_leak => bcd
+NOTICE:  f_leak => def
+NOTICE:  f_leak => yyy
+EXPLAIN (COSTS OFF) UPDATE only t1 SET b = b || '_updt' WHERE f_leak(b);
+             QUERY PLAN              
+-------------------------------------
+ Update on t1 t1_1
+   ->  Subquery Scan on t1
+         Filter: f_leak(t1.b)
+         ->  Seq Scan on t1 t1_2
+               Filter: ((a % 2) = 0)
+(5 rows)
+
+UPDATE only t1 SET b = b || '_updt' WHERE f_leak(b);
+NOTICE:  f_leak => bbbbbb
+NOTICE:  f_leak => dddddd
+-- returning clause with system column
+UPDATE only t1 SET b = b WHERE f_leak(b) RETURNING oid, *, t1;
+NOTICE:  f_leak => bbbbbb_updt
+NOTICE:  f_leak => dddddd_updt
+ oid | a |      b      |       t1        
+-----+---+-------------+-----------------
+ 102 | 2 | bbbbbb_updt | (2,bbbbbb_updt)
+ 104 | 4 | dddddd_updt | (4,dddddd_updt)
+(2 rows)
+
+UPDATE t1 SET b = b WHERE f_leak(b) RETURNING *;
+NOTICE:  f_leak => bbbbbb_updt
+NOTICE:  f_leak => dddddd_updt
+NOTICE:  f_leak => bcdbcd
+NOTICE:  f_leak => defdef
+NOTICE:  f_leak => yyyyyy
+ a |      b      
+---+-------------
+ 2 | bbbbbb_updt
+ 4 | dddddd_updt
+ 2 | bcdbcd
+ 4 | defdef
+ 2 | yyyyyy
+(5 rows)
+
+UPDATE t1 SET b = b WHERE f_leak(b) RETURNING oid, *, t1;
+NOTICE:  f_leak => bbbbbb_updt
+NOTICE:  f_leak => dddddd_updt
+NOTICE:  f_leak => bcdbcd
+NOTICE:  f_leak => defdef
+NOTICE:  f_leak => yyyyyy
+ oid | a |      b      |       t1        
+-----+---+-------------+-----------------
+ 102 | 2 | bbbbbb_updt | (2,bbbbbb_updt)
+ 104 | 4 | dddddd_updt | (4,dddddd_updt)
+ 202 | 2 | bcdbcd      | (2,bcdbcd)
+ 204 | 4 | defdef      | (4,defdef)
+ 302 | 2 | yyyyyy      | (2,yyyyyy)
+(5 rows)
+
+RESET SESSION AUTHORIZATION;
+SET row_security TO OFF;
+SELECT * FROM t1;
+ a |      b      
+---+-------------
+ 1 | aaa
+ 3 | ccc
+ 2 | bbbbbb_updt
+ 4 | dddddd_updt
+ 1 | abc
+ 3 | cde
+ 2 | bcdbcd
+ 4 | defdef
+ 1 | xxx
+ 3 | zzz
+ 2 | yyyyyy
+(11 rows)
+
+SET SESSION AUTHORIZATION rls_regress_user1;
+SET row_security TO ON;
+EXPLAIN (COSTS OFF) DELETE FROM only t1 WHERE f_leak(b);
+             QUERY PLAN              
+-------------------------------------
+ Delete on t1 t1_1
+   ->  Subquery Scan on t1
+         Filter: f_leak(t1.b)
+         ->  Seq Scan on t1 t1_2
+               Filter: ((a % 2) = 0)
+(5 rows)
+
+EXPLAIN (COSTS OFF) DELETE FROM t1 WHERE f_leak(b);
+             QUERY PLAN              
+-------------------------------------
+ Delete on t1 t1_3
+   ->  Subquery Scan on t1
+         Filter: f_leak(t1.b)
+         ->  Seq Scan on t1 t1_4
+               Filter: ((a % 2) = 0)
+   ->  Subquery Scan on t1_1
+         Filter: f_leak(t1_1.b)
+         ->  Seq Scan on t2
+               Filter: ((a % 2) = 0)
+   ->  Subquery Scan on t1_2
+         Filter: f_leak(t1_2.b)
+         ->  Seq Scan on t3
+               Filter: ((a % 2) = 0)
+(13 rows)
+
+DELETE FROM only t1 WHERE f_leak(b) RETURNING oid, *, t1;
+NOTICE:  f_leak => bbbbbb_updt
+NOTICE:  f_leak => dddddd_updt
+ oid | a |      b      |       t1        
+-----+---+-------------+-----------------
+ 102 | 2 | bbbbbb_updt | (2,bbbbbb_updt)
+ 104 | 4 | dddddd_updt | (4,dddddd_updt)
+(2 rows)
+
+DELETE FROM t1 WHERE f_leak(b) RETURNING oid, *, t1;
+NOTICE:  f_leak => bcdbcd
+NOTICE:  f_leak => defdef
+NOTICE:  f_leak => yyyyyy
+ oid | a |   b    |     t1     
+-----+---+--------+------------
+ 202 | 2 | bcdbcd | (2,bcdbcd)
+ 204 | 4 | defdef | (4,defdef)
+ 302 | 2 | yyyyyy | (2,yyyyyy)
+(3 rows)
+
+--
+-- ROLE/GROUP
+--
+SET SESSION AUTHORIZATION rls_regress_user0;
+CREATE TABLE z1 (a int, b text);
+GRANT SELECT ON z1 TO rls_regress_group1, rls_regress_group2,
+    rls_regress_user1, rls_regress_user2;
+INSERT INTO z1 VALUES
+    (1, 'aaa'),
+    (2, 'bbb'),
+    (3, 'ccc'),
+    (4, 'ddd');
+CREATE POLICY p1 ON z1 TO rls_regress_group1 USING (a % 2 = 0);
+CREATE POLICY p2 ON z1 TO rls_regress_group2 USING (a % 2 = 1);
+ALTER TABLE z1 ENABLE ROW LEVEL SECURITY;
+SET SESSION AUTHORIZATION rls_regress_user1;
+SELECT * FROM z1 WHERE f_leak(b);
+NOTICE:  f_leak => bbb
+NOTICE:  f_leak => ddd
+ a |  b  
+---+-----
+ 2 | bbb
+ 4 | ddd
+(2 rows)
+
+EXPLAIN (COSTS OFF) SELECT * FROM z1 WHERE f_leak(b);
+          QUERY PLAN           
+-------------------------------
+ Subquery Scan on z1
+   Filter: f_leak(z1.b)
+   ->  Seq Scan on z1 z1_1
+         Filter: ((a % 2) = 0)
+(4 rows)
+
+SET ROLE rls_regress_group1;
+SELECT * FROM z1 WHERE f_leak(b);
+NOTICE:  f_leak => bbb
+NOTICE:  f_leak => ddd
+ a |  b  
+---+-----
+ 2 | bbb
+ 4 | ddd
+(2 rows)
+
+EXPLAIN (COSTS OFF) SELECT * FROM z1 WHERE f_leak(b);
+          QUERY PLAN           
+-------------------------------
+ Subquery Scan on z1
+   Filter: f_leak(z1.b)
+   ->  Seq Scan on z1 z1_1
+         Filter: ((a % 2) = 0)
+(4 rows)
+
+SET SESSION AUTHORIZATION rls_regress_user2;
+SELECT * FROM z1 WHERE f_leak(b);
+NOTICE:  f_leak => aaa
+NOTICE:  f_leak => ccc
+ a |  b  
+---+-----
+ 1 | aaa
+ 3 | ccc
+(2 rows)
+
+EXPLAIN (COSTS OFF) SELECT * FROM z1 WHERE f_leak(b);
+          QUERY PLAN           
+-------------------------------
+ Subquery Scan on z1
+   Filter: f_leak(z1.b)
+   ->  Seq Scan on z1 z1_1
+         Filter: ((a % 2) = 1)
+(4 rows)
+
+SET ROLE rls_regress_group2;
+SELECT * FROM z1 WHERE f_leak(b);
+NOTICE:  f_leak => aaa
+NOTICE:  f_leak => ccc
+ a |  b  
+---+-----
+ 1 | aaa
+ 3 | ccc
+(2 rows)
+
+EXPLAIN (COSTS OFF) SELECT * FROM z1 WHERE f_leak(b);
+          QUERY PLAN           
+-------------------------------
+ Subquery Scan on z1
+   Filter: f_leak(z1.b)
+   ->  Seq Scan on z1 z1_1
+         Filter: ((a % 2) = 1)
+(4 rows)
+
+--
+-- Views should follow policy for view owner.
+--
+-- View and Table owner are the same.
+SET SESSION AUTHORIZATION rls_regress_user0;
+CREATE VIEW rls_view AS SELECT * FROM z1 WHERE f_leak(b);
+GRANT SELECT ON rls_view TO rls_regress_user1;
+-- Query as role that is not owner of view or table.  Should return all records.
+SET SESSION AUTHORIZATION rls_regress_user1;
+SELECT * FROM rls_view;
+NOTICE:  f_leak => aaa
+NOTICE:  f_leak => bbb
+NOTICE:  f_leak => ccc
+NOTICE:  f_leak => ddd
+ a |  b  
+---+-----
+ 1 | aaa
+ 2 | bbb
+ 3 | ccc
+ 4 | ddd
+(4 rows)
+
+EXPLAIN (COSTS OFF) SELECT * FROM rls_view;
+     QUERY PLAN      
+---------------------
+ Seq Scan on z1
+   Filter: f_leak(b)
+(2 rows)
+
+-- Query as view/table owner.  Should return all records.
+SET SESSION AUTHORIZATION rls_regress_user0;
+SELECT * FROM rls_view;
+NOTICE:  f_leak => aaa
+NOTICE:  f_leak => bbb
+NOTICE:  f_leak => ccc
+NOTICE:  f_leak => ddd
+ a |  b  
+---+-----
+ 1 | aaa
+ 2 | bbb
+ 3 | ccc
+ 4 | ddd
+(4 rows)
+
+EXPLAIN (COSTS OFF) SELECT * FROM rls_view;
+     QUERY PLAN      
+---------------------
+ Seq Scan on z1
+   Filter: f_leak(b)
+(2 rows)
+
+DROP VIEW rls_view;
+-- View and Table owners are different.
+SET SESSION AUTHORIZATION rls_regress_user1;
+CREATE VIEW rls_view AS SELECT * FROM z1 WHERE f_leak(b);
+GRANT SELECT ON rls_view TO rls_regress_user0;
+-- Query as role that is not owner of view but is owner of table.
+-- Should return records based on view owner policies.
+SET SESSION AUTHORIZATION rls_regress_user0;
+SELECT * FROM rls_view;
+NOTICE:  f_leak => bbb
+NOTICE:  f_leak => ddd
+ a |  b  
+---+-----
+ 2 | bbb
+ 4 | ddd
+(2 rows)
+
+EXPLAIN (COSTS OFF) SELECT * FROM rls_view;
+          QUERY PLAN           
+-------------------------------
+ Subquery Scan on z1
+   Filter: f_leak(z1.b)
+   ->  Seq Scan on z1 z1_1
+         Filter: ((a % 2) = 0)
+(4 rows)
+
+-- Query as role that is not owner of table but is owner of view.
+-- Should return records based on view owner policies.
+SET SESSION AUTHORIZATION rls_regress_user1;
+SELECT * FROM rls_view;
+NOTICE:  f_leak => bbb
+NOTICE:  f_leak => ddd
+ a |  b  
+---+-----
+ 2 | bbb
+ 4 | ddd
+(2 rows)
+
+EXPLAIN (COSTS OFF) SELECT * FROM rls_view;
+          QUERY PLAN           
+-------------------------------
+ Subquery Scan on z1
+   Filter: f_leak(z1.b)
+   ->  Seq Scan on z1 z1_1
+         Filter: ((a % 2) = 0)
+(4 rows)
+
+-- Query as role that is not the owner of the table or view without permissions.
+SET SESSION AUTHORIZATION rls_regress_user2;
+SELECT * FROM rls_view; --fail - permission denied.
+ERROR:  permission denied for relation rls_view
+EXPLAIN (COSTS OFF) SELECT * FROM rls_view; --fail - permission denied.
+ERROR:  permission denied for relation rls_view
+-- Query as role that is not the owner of the table or view with permissions.
+SET SESSION AUTHORIZATION rls_regress_user1;
+GRANT SELECT ON rls_view TO rls_regress_user2;
+SELECT * FROM rls_view;
+NOTICE:  f_leak => bbb
+NOTICE:  f_leak => ddd
+ a |  b  
+---+-----
+ 2 | bbb
+ 4 | ddd
+(2 rows)
+
+EXPLAIN (COSTS OFF) SELECT * FROM rls_view;
+          QUERY PLAN           
+-------------------------------
+ Subquery Scan on z1
+   Filter: f_leak(z1.b)
+   ->  Seq Scan on z1 z1_1
+         Filter: ((a % 2) = 0)
+(4 rows)
+
+SET SESSION AUTHORIZATION rls_regress_user1;
+DROP VIEW rls_view;
+--
+-- Command specific
+--
+SET SESSION AUTHORIZATION rls_regress_user0;
+CREATE TABLE x1 (a int, b text, c text);
+GRANT ALL ON x1 TO PUBLIC;
+INSERT INTO x1 VALUES
+    (1, 'abc', 'rls_regress_user1'),
+    (2, 'bcd', 'rls_regress_user1'),
+    (3, 'cde', 'rls_regress_user2'),
+    (4, 'def', 'rls_regress_user2'),
+    (5, 'efg', 'rls_regress_user1'),
+    (6, 'fgh', 'rls_regress_user1'),
+    (7, 'fgh', 'rls_regress_user2'),
+    (8, 'fgh', 'rls_regress_user2');
+CREATE POLICY p0 ON x1 FOR ALL USING (c = current_user);
+CREATE POLICY p1 ON x1 FOR SELECT USING (a % 2 = 0);
+CREATE POLICY p2 ON x1 FOR INSERT WITH CHECK (a % 2 = 1);
+CREATE POLICY p3 ON x1 FOR UPDATE USING (a % 2 = 0);
+CREATE POLICY p4 ON x1 FOR DELETE USING (a < 8);
+ALTER TABLE x1 ENABLE ROW LEVEL SECURITY;
+SET SESSION AUTHORIZATION rls_regress_user1;
+SELECT * FROM x1 WHERE f_leak(b) ORDER BY a ASC;
+NOTICE:  f_leak => abc
+NOTICE:  f_leak => bcd
+NOTICE:  f_leak => def
+NOTICE:  f_leak => efg
+NOTICE:  f_leak => fgh
+NOTICE:  f_leak => fgh
+ a |  b  |         c         
+---+-----+-------------------
+ 1 | abc | rls_regress_user1
+ 2 | bcd | rls_regress_user1
+ 4 | def | rls_regress_user2
+ 5 | efg | rls_regress_user1
+ 6 | fgh | rls_regress_user1
+ 8 | fgh | rls_regress_user2
+(6 rows)
+
+UPDATE x1 SET b = b || '_updt' WHERE f_leak(b) RETURNING *;
+NOTICE:  f_leak => abc
+NOTICE:  f_leak => bcd
+NOTICE:  f_leak => def
+NOTICE:  f_leak => efg
+NOTICE:  f_leak => fgh
+NOTICE:  f_leak => fgh
+ a |    b     |         c         
+---+----------+-------------------
+ 1 | abc_updt | rls_regress_user1
+ 2 | bcd_updt | rls_regress_user1
+ 4 | def_updt | rls_regress_user2
+ 5 | efg_updt | rls_regress_user1
+ 6 | fgh_updt | rls_regress_user1
+ 8 | fgh_updt | rls_regress_user2
+(6 rows)
+
+SET SESSION AUTHORIZATION rls_regress_user2;
+SELECT * FROM x1 WHERE f_leak(b) ORDER BY a ASC;
+NOTICE:  f_leak => cde
+NOTICE:  f_leak => fgh
+NOTICE:  f_leak => bcd_updt
+NOTICE:  f_leak => def_updt
+NOTICE:  f_leak => fgh_updt
+NOTICE:  f_leak => fgh_updt
+ a |    b     |         c         
+---+----------+-------------------
+ 2 | bcd_updt | rls_regress_user1
+ 3 | cde      | rls_regress_user2
+ 4 | def_updt | rls_regress_user2
+ 6 | fgh_updt | rls_regress_user1
+ 7 | fgh      | rls_regress_user2
+ 8 | fgh_updt | rls_regress_user2
+(6 rows)
+
+UPDATE x1 SET b = b || '_updt' WHERE f_leak(b) RETURNING *;
+NOTICE:  f_leak => cde
+NOTICE:  f_leak => fgh
+NOTICE:  f_leak => bcd_updt
+NOTICE:  f_leak => def_updt
+NOTICE:  f_leak => fgh_updt
+NOTICE:  f_leak => fgh_updt
+ a |       b       |         c         
+---+---------------+-------------------
+ 3 | cde_updt      | rls_regress_user2
+ 7 | fgh_updt      | rls_regress_user2
+ 2 | bcd_updt_updt | rls_regress_user1
+ 4 | def_updt_updt | rls_regress_user2
+ 6 | fgh_updt_updt | rls_regress_user1
+ 8 | fgh_updt_updt | rls_regress_user2
+(6 rows)
+
+DELETE FROM x1 WHERE f_leak(b) RETURNING *;
+NOTICE:  f_leak => abc_updt
+NOTICE:  f_leak => efg_updt
+NOTICE:  f_leak => cde_updt
+NOTICE:  f_leak => fgh_updt
+NOTICE:  f_leak => bcd_updt_updt
+NOTICE:  f_leak => def_updt_updt
+NOTICE:  f_leak => fgh_updt_updt
+NOTICE:  f_leak => fgh_updt_updt
+ a |       b       |         c         
+---+---------------+-------------------
+ 1 | abc_updt      | rls_regress_user1
+ 5 | efg_updt      | rls_regress_user1
+ 3 | cde_updt      | rls_regress_user2
+ 7 | fgh_updt      | rls_regress_user2
+ 2 | bcd_updt_updt | rls_regress_user1
+ 4 | def_updt_updt | rls_regress_user2
+ 6 | fgh_updt_updt | rls_regress_user1
+ 8 | fgh_updt_updt | rls_regress_user2
+(8 rows)
+
+--
+-- Duplicate Policy Names
+--
+SET SESSION AUTHORIZATION rls_regress_user0;
+CREATE TABLE y1 (a int, b text);
+CREATE TABLE y2 (a int, b text);
+GRANT ALL ON y1, y2 TO rls_regress_user1;
+CREATE POLICY p1 ON y1 FOR ALL USING (a % 2 = 0);
+CREATE POLICY p2 ON y1 FOR SELECT USING (a > 2);
+CREATE POLICY p1 ON y1 FOR SELECT USING (a % 2 = 1);  --fail
+ERROR:  policy "p1" for relation "y1" already exists
+CREATE POLICY p1 ON y2 FOR ALL USING (a % 2 = 0);  --OK
+ALTER TABLE y1 ENABLE ROW LEVEL SECURITY;
+ALTER TABLE y2 ENABLE ROW LEVEL SECURITY;
+--
+-- Expression structure with SBV
+--
+-- Create view as table owner.  RLS should NOT be applied.
+SET SESSION AUTHORIZATION rls_regress_user0;
+CREATE VIEW rls_sbv WITH (security_barrier) AS
+    SELECT * FROM y1 WHERE f_leak(b);
+EXPLAIN (COSTS OFF) SELECT * FROM rls_sbv WHERE (a = 1);
+            QUERY PLAN             
+-----------------------------------
+ Seq Scan on y1
+   Filter: (f_leak(b) AND (a = 1))
+(2 rows)
+
+DROP VIEW rls_sbv;
+-- Create view as role that does not own table.  RLS should be applied.
+SET SESSION AUTHORIZATION rls_regress_user1;
+CREATE VIEW rls_sbv WITH (security_barrier) AS
+    SELECT * FROM y1 WHERE f_leak(b);
+EXPLAIN (COSTS OFF) SELECT * FROM rls_sbv WHERE (a = 1);
+                        QUERY PLAN                        
+----------------------------------------------------------
+ Subquery Scan on y1
+   Filter: f_leak(y1.b)
+   ->  Seq Scan on y1 y1_1
+         Filter: ((a = 1) AND ((a > 2) OR ((a % 2) = 0)))
+(4 rows)
+
+DROP VIEW rls_sbv;
+--
+-- Expression structure
+--
+SET SESSION AUTHORIZATION rls_regress_user0;
+INSERT INTO y2 (SELECT x, md5(x::text) FROM generate_series(0,20) x);
+CREATE POLICY p2 ON y2 USING (a % 3 = 0);
+CREATE POLICY p3 ON y2 USING (a % 4 = 0);
+SET SESSION AUTHORIZATION rls_regress_user1;
+SELECT * FROM y2 WHERE f_leak(b);
+NOTICE:  f_leak => cfcd208495d565ef66e7dff9f98764da
+NOTICE:  f_leak => c81e728d9d4c2f636f067f89cc14862c
+NOTICE:  f_leak => eccbc87e4b5ce2fe28308fd9f2a7baf3
+NOTICE:  f_leak => a87ff679a2f3e71d9181a67b7542122c
+NOTICE:  f_leak => 1679091c5a880faf6fb5e6087eb1b2dc
+NOTICE:  f_leak => c9f0f895fb98ab9159f51fd0297e236d
+NOTICE:  f_leak => 45c48cce2e2d7fbdea1afc51c7c6ad26
+NOTICE:  f_leak => d3d9446802a44259755d38e6d163e820
+NOTICE:  f_leak => c20ad4d76fe97759aa27a0c99bff6710
+NOTICE:  f_leak => aab3238922bcc25a6f606eb525ffdc56
+NOTICE:  f_leak => 9bf31c7ff062936a96d3c8bd1f8f2ff3
+NOTICE:  f_leak => c74d97b01eae257e44aa9d5bade97baf
+NOTICE:  f_leak => 6f4922f45568161a8cdf4ad2299f6d23
+NOTICE:  f_leak => 98f13708210194c475687be6106a3b84
+ a  |                b                 
+----+----------------------------------
+  0 | cfcd208495d565ef66e7dff9f98764da
+  2 | c81e728d9d4c2f636f067f89cc14862c
+  3 | eccbc87e4b5ce2fe28308fd9f2a7baf3
+  4 | a87ff679a2f3e71d9181a67b7542122c
+  6 | 1679091c5a880faf6fb5e6087eb1b2dc
+  8 | c9f0f895fb98ab9159f51fd0297e236d
+  9 | 45c48cce2e2d7fbdea1afc51c7c6ad26
+ 10 | d3d9446802a44259755d38e6d163e820
+ 12 | c20ad4d76fe97759aa27a0c99bff6710
+ 14 | aab3238922bcc25a6f606eb525ffdc56
+ 15 | 9bf31c7ff062936a96d3c8bd1f8f2ff3
+ 16 | c74d97b01eae257e44aa9d5bade97baf
+ 18 | 6f4922f45568161a8cdf4ad2299f6d23
+ 20 | 98f13708210194c475687be6106a3b84
+(14 rows)
+
+EXPLAIN (COSTS OFF) SELECT * FROM y2 WHERE f_leak(b);
+                            QUERY PLAN                             
+-------------------------------------------------------------------
+ Subquery Scan on y2
+   Filter: f_leak(y2.b)
+   ->  Seq Scan on y2 y2_1
+         Filter: (((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0))
+(4 rows)
+
+--
+-- Plancache invalidate on user change.
+--
+RESET SESSION AUTHORIZATION;
+DROP TABLE t1 CASCADE;
+NOTICE:  drop cascades to 2 other objects
+DETAIL:  drop cascades to table t2
+drop cascades to table t3
+CREATE TABLE t1 (a integer);
+GRANT SELECT ON t1 TO rls_regress_user1, rls_regress_user2;
+CREATE POLICY p1 ON t1 TO rls_regress_user1 USING ((a % 2) = 0);
+CREATE POLICY p2 ON t1 TO rls_regress_user2 USING ((a % 4) = 0);
+ALTER TABLE t1 ENABLE ROW LEVEL SECURITY;
+SET ROLE rls_regress_user1;
+PREPARE role_inval AS SELECT * FROM t1;
+EXPLAIN (COSTS OFF) EXECUTE role_inval;
+       QUERY PLAN        
+-------------------------
+ Seq Scan on t1
+   Filter: ((a % 2) = 0)
+(2 rows)
+
+SET ROLE rls_regress_user2;
+EXPLAIN (COSTS OFF) EXECUTE role_inval;
+       QUERY PLAN        
+-------------------------
+ Seq Scan on t1
+   Filter: ((a % 4) = 0)
+(2 rows)
+
+--
+-- CTE and RLS
+--
+RESET SESSION AUTHORIZATION;
+DROP TABLE t1 CASCADE;
+CREATE TABLE t1 (a integer, b text);
+CREATE POLICY p1 ON t1 USING (a % 2 = 0);
+ALTER TABLE t1 ENABLE ROW LEVEL SECURITY;
+GRANT ALL ON t1 TO rls_regress_user1;
+INSERT INTO t1 (SELECT x, md5(x::text) FROM generate_series(0,20) x);
+SET SESSION AUTHORIZATION rls_regress_user1;
+WITH cte1 AS (SELECT * FROM t1 WHERE f_leak(b)) SELECT * FROM cte1;
+NOTICE:  f_leak => cfcd208495d565ef66e7dff9f98764da
+NOTICE:  f_leak => c81e728d9d4c2f636f067f89cc14862c
+NOTICE:  f_leak => a87ff679a2f3e71d9181a67b7542122c
+NOTICE:  f_leak => 1679091c5a880faf6fb5e6087eb1b2dc
+NOTICE:  f_leak => c9f0f895fb98ab9159f51fd0297e236d
+NOTICE:  f_leak => d3d9446802a44259755d38e6d163e820
+NOTICE:  f_leak => c20ad4d76fe97759aa27a0c99bff6710
+NOTICE:  f_leak => aab3238922bcc25a6f606eb525ffdc56
+NOTICE:  f_leak => c74d97b01eae257e44aa9d5bade97baf
+NOTICE:  f_leak => 6f4922f45568161a8cdf4ad2299f6d23
+NOTICE:  f_leak => 98f13708210194c475687be6106a3b84
+ a  |                b                 
+----+----------------------------------
+  0 | cfcd208495d565ef66e7dff9f98764da
+  2 | c81e728d9d4c2f636f067f89cc14862c
+  4 | a87ff679a2f3e71d9181a67b7542122c
+  6 | 1679091c5a880faf6fb5e6087eb1b2dc
+  8 | c9f0f895fb98ab9159f51fd0297e236d
+ 10 | d3d9446802a44259755d38e6d163e820
+ 12 | c20ad4d76fe97759aa27a0c99bff6710
+ 14 | aab3238922bcc25a6f606eb525ffdc56
+ 16 | c74d97b01eae257e44aa9d5bade97baf
+ 18 | 6f4922f45568161a8cdf4ad2299f6d23
+ 20 | 98f13708210194c475687be6106a3b84
+(11 rows)
+
+EXPLAIN (COSTS OFF) WITH cte1 AS (SELECT * FROM t1 WHERE f_leak(b)) SELECT * FROM cte1;
+              QUERY PLAN               
+---------------------------------------
+ CTE Scan on cte1
+   CTE cte1
+     ->  Subquery Scan on t1
+           Filter: f_leak(t1.b)
+           ->  Seq Scan on t1 t1_1
+                 Filter: ((a % 2) = 0)
+(6 rows)
+
+WITH cte1 AS (UPDATE t1 SET a = a + 1 RETURNING *) SELECT * FROM cte1; --fail
+ERROR:  new row violates WITH CHECK OPTION for "t1"
+DETAIL:  Failing row contains (1, cfcd208495d565ef66e7dff9f98764da).
+WITH cte1 AS (UPDATE t1 SET a = a RETURNING *) SELECT * FROM cte1; --ok
+ a  |                b                 
+----+----------------------------------
+  0 | cfcd208495d565ef66e7dff9f98764da
+  2 | c81e728d9d4c2f636f067f89cc14862c
+  4 | a87ff679a2f3e71d9181a67b7542122c
+  6 | 1679091c5a880faf6fb5e6087eb1b2dc
+  8 | c9f0f895fb98ab9159f51fd0297e236d
+ 10 | d3d9446802a44259755d38e6d163e820
+ 12 | c20ad4d76fe97759aa27a0c99bff6710
+ 14 | aab3238922bcc25a6f606eb525ffdc56
+ 16 | c74d97b01eae257e44aa9d5bade97baf
+ 18 | 6f4922f45568161a8cdf4ad2299f6d23
+ 20 | 98f13708210194c475687be6106a3b84
+(11 rows)
+
+WITH cte1 AS (INSERT INTO t1 VALUES (21, 'Fail') RETURNING *) SELECT * FROM cte1; --fail
+ERROR:  new row violates WITH CHECK OPTION for "t1"
+DETAIL:  Failing row contains (21, Fail).
+WITH cte1 AS (INSERT INTO t1 VALUES (20, 'Success') RETURNING *) SELECT * FROM cte1; --ok
+ a  |    b    
+----+---------
+ 20 | Success
+(1 row)
+
+--
+-- Rename Policy
+--
+RESET SESSION AUTHORIZATION;
+ALTER POLICY p1 ON t1 RENAME TO p1; --fail
+ERROR:  row-policy "p1" for table "t1" already exists
+SELECT rsecpolname, relname
+    FROM pg_rowsecurity rs
+    JOIN pg_class pc ON (pc.oid = rs.rsecrelid)
+    WHERE relname = 't1';
+ rsecpolname | relname 
+-------------+---------
+ p1          | t1
+(1 row)
+
+ALTER POLICY p1 ON t1 RENAME TO p2; --ok
+SELECT rsecpolname, relname
+    FROM pg_rowsecurity rs
+    JOIN pg_class pc ON (pc.oid = rs.rsecrelid)
+    WHERE relname = 't1';
+ rsecpolname | relname 
+-------------+---------
+ p2          | t1
+(1 row)
+
+--
+-- Check INSERT SELECT
+--
+SET SESSION AUTHORIZATION rls_regress_user1;
+CREATE TABLE t2 (a integer, b text);
+INSERT INTO t2 (SELECT * FROM t1);
+EXPLAIN (COSTS OFF) INSERT INTO t2 (SELECT * FROM t1);
+          QUERY PLAN           
+-------------------------------
+ Insert on t2
+   ->  Seq Scan on t1
+         Filter: ((a % 2) = 0)
+(3 rows)
+
+SELECT * FROM t2;
+ a  |                b                 
+----+----------------------------------
+  0 | cfcd208495d565ef66e7dff9f98764da
+  2 | c81e728d9d4c2f636f067f89cc14862c
+  4 | a87ff679a2f3e71d9181a67b7542122c
+  6 | 1679091c5a880faf6fb5e6087eb1b2dc
+  8 | c9f0f895fb98ab9159f51fd0297e236d
+ 10 | d3d9446802a44259755d38e6d163e820
+ 12 | c20ad4d76fe97759aa27a0c99bff6710
+ 14 | aab3238922bcc25a6f606eb525ffdc56
+ 16 | c74d97b01eae257e44aa9d5bade97baf
+ 18 | 6f4922f45568161a8cdf4ad2299f6d23
+ 20 | 98f13708210194c475687be6106a3b84
+ 20 | Success
+(12 rows)
+
+EXPLAIN (COSTS OFF) SELECT * FROM t2;
+   QUERY PLAN   
+----------------
+ Seq Scan on t2
+(1 row)
+
+CREATE TABLE t3 AS SELECT * FROM t1;
+SELECT * FROM t3;
+ a  |                b                 
+----+----------------------------------
+  0 | cfcd208495d565ef66e7dff9f98764da
+  2 | c81e728d9d4c2f636f067f89cc14862c
+  4 | a87ff679a2f3e71d9181a67b7542122c
+  6 | 1679091c5a880faf6fb5e6087eb1b2dc
+  8 | c9f0f895fb98ab9159f51fd0297e236d
+ 10 | d3d9446802a44259755d38e6d163e820
+ 12 | c20ad4d76fe97759aa27a0c99bff6710
+ 14 | aab3238922bcc25a6f606eb525ffdc56
+ 16 | c74d97b01eae257e44aa9d5bade97baf
+ 18 | 6f4922f45568161a8cdf4ad2299f6d23
+ 20 | 98f13708210194c475687be6106a3b84
+ 20 | Success
+(12 rows)
+
+SELECT * INTO t4 FROM t1;
+SELECT * FROM t4;
+ a  |                b                 
+----+----------------------------------
+  0 | cfcd208495d565ef66e7dff9f98764da
+  2 | c81e728d9d4c2f636f067f89cc14862c
+  4 | a87ff679a2f3e71d9181a67b7542122c
+  6 | 1679091c5a880faf6fb5e6087eb1b2dc
+  8 | c9f0f895fb98ab9159f51fd0297e236d
+ 10 | d3d9446802a44259755d38e6d163e820
+ 12 | c20ad4d76fe97759aa27a0c99bff6710
+ 14 | aab3238922bcc25a6f606eb525ffdc56
+ 16 | c74d97b01eae257e44aa9d5bade97baf
+ 18 | 6f4922f45568161a8cdf4ad2299f6d23
+ 20 | 98f13708210194c475687be6106a3b84
+ 20 | Success
+(12 rows)
+
+--
+-- RLS with JOIN
+--
+SET SESSION AUTHORIZATION rls_regress_user0;
+CREATE TABLE blog (id integer, author text, post text);
+CREATE TABLE comment (blog_id integer, message text);
+GRANT ALL ON blog, comment TO rls_regress_user1;
+CREATE POLICY blog_1 ON blog USING (id % 2 = 0);
+ALTER TABLE blog ENABLE ROW LEVEL SECURITY;
+INSERT INTO blog VALUES
+    (1, 'alice', 'blog #1'),
+    (2, 'bob', 'blog #1'),
+    (3, 'alice', 'blog #2'),
+    (4, 'alice', 'blog #3'),
+    (5, 'john', 'blog #1');
+INSERT INTO comment VALUES
+    (1, 'cool blog'),
+    (1, 'fun blog'),
+    (3, 'crazy blog'),
+    (5, 'what?'),
+    (4, 'insane!'),
+    (2, 'who did it?');
+SET SESSION AUTHORIZATION rls_regress_user1;
+-- Check RLS JOIN with Non-RLS.
+SELECT id, author, message FROM blog JOIN comment ON id = blog_id;
+ id | author |   message   
+----+--------+-------------
+  4 | alice  | insane!
+  2 | bob    | who did it?
+(2 rows)
+
+-- Check Non-RLS JOIN with RLS.
+SELECT id, author, message FROM comment JOIN blog ON id = blog_id;
+ id | author |   message   
+----+--------+-------------
+  4 | alice  | insane!
+  2 | bob    | who did it?
+(2 rows)
+
+SET SESSION AUTHORIZATION rls_regress_user0;
+CREATE POLICY comment_1 ON comment USING (blog_id < 4);
+ALTER TABLE comment ENABLE ROW LEVEL SECURITY;
+SET SESSION AUTHORIZATION rls_regress_user1;
+-- Check RLS JOIN RLS
+SELECT id, author, message FROM blog JOIN comment ON id = blog_id;
+ id | author |   message   
+----+--------+-------------
+  2 | bob    | who did it?
+(1 row)
+
+SELECT id, author, message FROM comment JOIN blog ON id = blog_id;
+ id | author |   message   
+----+--------+-------------
+  2 | bob    | who did it?
+(1 row)
+
+SET SESSION AUTHORIZATION rls_regress_user0;
+DROP TABLE blog, comment;
+--
+-- Default Deny Policy
+--
+RESET SESSION AUTHORIZATION;
+DROP POLICY p2 ON t1;
+ALTER TABLE t1 OWNER TO rls_regress_user0;
+-- Check that default deny does not apply to superuser.
+RESET SESSION AUTHORIZATION;
+SELECT * FROM t1;
+ a  |                b                 
+----+----------------------------------
+  1 | c4ca4238a0b923820dcc509a6f75849b
+  3 | eccbc87e4b5ce2fe28308fd9f2a7baf3
+  5 | e4da3b7fbbce2345d7772b0674a318d5
+  7 | 8f14e45fceea167a5a36dedd4bea2543
+  9 | 45c48cce2e2d7fbdea1afc51c7c6ad26
+ 11 | 6512bd43d9caa6e02c990b0a82652dca
+ 13 | c51ce410c124a10e0db5e4b97fc2af39
+ 15 | 9bf31c7ff062936a96d3c8bd1f8f2ff3
+ 17 | 70efdf2ec9b086079795c442636b55fb
+ 19 | 1f0e3dad99908345f7439f8ffabdffc4
+  0 | cfcd208495d565ef66e7dff9f98764da
+  2 | c81e728d9d4c2f636f067f89cc14862c
+  4 | a87ff679a2f3e71d9181a67b7542122c
+  6 | 1679091c5a880faf6fb5e6087eb1b2dc
+  8 | c9f0f895fb98ab9159f51fd0297e236d
+ 10 | d3d9446802a44259755d38e6d163e820
+ 12 | c20ad4d76fe97759aa27a0c99bff6710
+ 14 | aab3238922bcc25a6f606eb525ffdc56
+ 16 | c74d97b01eae257e44aa9d5bade97baf
+ 18 | 6f4922f45568161a8cdf4ad2299f6d23
+ 20 | 98f13708210194c475687be6106a3b84
+ 20 | Success
+(22 rows)
+
+EXPLAIN (COSTS OFF) SELECT * FROM t1;
+   QUERY PLAN   
+----------------
+ Seq Scan on t1
+(1 row)
+
+-- Check that default deny does not apply to table owner.
+SET SESSION AUTHORIZATION rls_regress_user0;
+SELECT * FROM t1;
+ a  |                b                 
+----+----------------------------------
+  1 | c4ca4238a0b923820dcc509a6f75849b
+  3 | eccbc87e4b5ce2fe28308fd9f2a7baf3
+  5 | e4da3b7fbbce2345d7772b0674a318d5
+  7 | 8f14e45fceea167a5a36dedd4bea2543
+  9 | 45c48cce2e2d7fbdea1afc51c7c6ad26
+ 11 | 6512bd43d9caa6e02c990b0a82652dca
+ 13 | c51ce410c124a10e0db5e4b97fc2af39
+ 15 | 9bf31c7ff062936a96d3c8bd1f8f2ff3
+ 17 | 70efdf2ec9b086079795c442636b55fb
+ 19 | 1f0e3dad99908345f7439f8ffabdffc4
+  0 | cfcd208495d565ef66e7dff9f98764da
+  2 | c81e728d9d4c2f636f067f89cc14862c
+  4 | a87ff679a2f3e71d9181a67b7542122c
+  6 | 1679091c5a880faf6fb5e6087eb1b2dc
+  8 | c9f0f895fb98ab9159f51fd0297e236d
+ 10 | d3d9446802a44259755d38e6d163e820
+ 12 | c20ad4d76fe97759aa27a0c99bff6710
+ 14 | aab3238922bcc25a6f606eb525ffdc56
+ 16 | c74d97b01eae257e44aa9d5bade97baf
+ 18 | 6f4922f45568161a8cdf4ad2299f6d23
+ 20 | 98f13708210194c475687be6106a3b84
+ 20 | Success
+(22 rows)
+
+EXPLAIN (COSTS OFF) SELECT * FROM t1;
+   QUERY PLAN   
+----------------
+ Seq Scan on t1
+(1 row)
+
+-- Check that default deny does apply to superuser when RLS force.
+SET row_security TO FORCE;
+RESET SESSION AUTHORIZATION;
+SELECT * FROM t1;
+ a | b 
+---+---
+(0 rows)
+
+EXPLAIN (COSTS OFF) SELECT * FROM t1;
+        QUERY PLAN        
+--------------------------
+ Result
+   One-Time Filter: false
+(2 rows)
+
+-- Check that default deny does apply to table owner when RLS force.
+SET SESSION AUTHORIZATION rls_regress_user0;
+SELECT * FROM t1;
+ a | b 
+---+---
+(0 rows)
+
+EXPLAIN (COSTS OFF) SELECT * FROM t1;
+        QUERY PLAN        
+--------------------------
+ Result
+   One-Time Filter: false
+(2 rows)
+
+-- Check that default deny applies to non-owner/non-superuser when RLS on.
+SET SESSION AUTHORIZATION rls_regress_user1;
+SET row_security TO ON;
+SELECT * FROM t1;
+ a | b 
+---+---
+(0 rows)
+
+EXPLAIN (COSTS OFF) SELECT * FROM t1;
+        QUERY PLAN        
+--------------------------
+ Result
+   One-Time Filter: false
+(2 rows)
+
+SET SESSION AUTHORIZATION rls_regress_user1;
+SELECT * FROM t1;
+ a | b 
+---+---
+(0 rows)
+
+EXPLAIN (COSTS OFF) SELECT * FROM t1;
+        QUERY PLAN        
+--------------------------
+ Result
+   One-Time Filter: false
+(2 rows)
+
+--
+-- Event Triggers
+--
+RESET SESSION AUTHORIZATION;
+CREATE TABLE event_trigger_test (a integer, b text);
+CREATE OR REPLACE FUNCTION start_command()
+RETURNS event_trigger AS $$
+BEGIN
+RAISE NOTICE '% - ddl_command_start', tg_tag;
+END;
+$$ LANGUAGE plpgsql;
+CREATE OR REPLACE FUNCTION end_command()
+RETURNS event_trigger AS $$
+BEGIN
+RAISE NOTICE '% - ddl_command_end', tg_tag;
+END;
+$$ LANGUAGE plpgsql;
+CREATE OR REPLACE FUNCTION drop_sql_command()
+RETURNS event_trigger AS $$
+BEGIN
+RAISE NOTICE '% - sql_drop', tg_tag;
+END;
+$$ LANGUAGE plpgsql;
+CREATE EVENT TRIGGER start_rls_command ON ddl_command_start
+    WHEN TAG IN ('CREATE POLICY', 'ALTER POLICY', 'DROP POLICY') EXECUTE PROCEDURE start_command();
+CREATE EVENT TRIGGER end_rls_command ON ddl_command_end
+    WHEN TAG IN ('CREATE POLICY', 'ALTER POLICY', 'DROP POLICY') EXECUTE PROCEDURE end_command();
+CREATE EVENT TRIGGER sql_drop_command ON sql_drop
+    WHEN TAG IN ('DROP POLICY') EXECUTE PROCEDURE drop_sql_command();
+CREATE POLICY p1 ON event_trigger_test USING (FALSE);
+NOTICE:  CREATE POLICY - ddl_command_start
+NOTICE:  CREATE POLICY - ddl_command_end
+ALTER POLICY p1 ON event_trigger_test USING (TRUE);
+NOTICE:  ALTER POLICY - ddl_command_start
+NOTICE:  ALTER POLICY - ddl_command_end
+ALTER POLICY p1 ON event_trigger_test RENAME TO p2;
+NOTICE:  ALTER POLICY - ddl_command_start
+NOTICE:  ALTER POLICY - ddl_command_end
+DROP POLICY p2 ON event_trigger_test;
+NOTICE:  DROP POLICY - ddl_command_start
+NOTICE:  DROP POLICY - sql_drop
+NOTICE:  DROP POLICY - ddl_command_end
+DROP EVENT TRIGGER start_rls_command;
+DROP EVENT TRIGGER end_rls_command;
+DROP EVENT TRIGGER sql_drop_command;
+--
+-- COPY TO/FROM
+--
+RESET SESSION AUTHORIZATION;
+DROP TABLE copy_t CASCADE;
+ERROR:  table "copy_t" does not exist
+CREATE TABLE copy_t (a integer, b text);
+CREATE POLICY p1 ON copy_t USING (a % 2 = 0);
+ALTER TABLE copy_t ENABLE ROW LEVEL SECURITY;
+GRANT ALL ON copy_t TO rls_regress_user1, rls_regress_exempt_user;
+INSERT INTO copy_t (SELECT x, md5(x::text) FROM generate_series(0,10) x);
+-- Check COPY TO as Superuser/owner.
+RESET SESSION AUTHORIZATION;
+SET row_security TO OFF;
+COPY (SELECT * FROM copy_t ORDER BY a ASC) TO STDOUT WITH DELIMITER ',';
+0,cfcd208495d565ef66e7dff9f98764da
+1,c4ca4238a0b923820dcc509a6f75849b
+2,c81e728d9d4c2f636f067f89cc14862c
+3,eccbc87e4b5ce2fe28308fd9f2a7baf3
+4,a87ff679a2f3e71d9181a67b7542122c
+5,e4da3b7fbbce2345d7772b0674a318d5
+6,1679091c5a880faf6fb5e6087eb1b2dc
+7,8f14e45fceea167a5a36dedd4bea2543
+8,c9f0f895fb98ab9159f51fd0297e236d
+9,45c48cce2e2d7fbdea1afc51c7c6ad26
+10,d3d9446802a44259755d38e6d163e820
+SET row_security TO ON;
+COPY (SELECT * FROM copy_t ORDER BY a ASC) TO STDOUT WITH DELIMITER ',';
+0,cfcd208495d565ef66e7dff9f98764da
+1,c4ca4238a0b923820dcc509a6f75849b
+2,c81e728d9d4c2f636f067f89cc14862c
+3,eccbc87e4b5ce2fe28308fd9f2a7baf3
+4,a87ff679a2f3e71d9181a67b7542122c
+5,e4da3b7fbbce2345d7772b0674a318d5
+6,1679091c5a880faf6fb5e6087eb1b2dc
+7,8f14e45fceea167a5a36dedd4bea2543
+8,c9f0f895fb98ab9159f51fd0297e236d
+9,45c48cce2e2d7fbdea1afc51c7c6ad26
+10,d3d9446802a44259755d38e6d163e820
+SET row_security TO FORCE;
+COPY (SELECT * FROM copy_t ORDER BY a ASC) TO STDOUT WITH DELIMITER ',';
+0,cfcd208495d565ef66e7dff9f98764da
+2,c81e728d9d4c2f636f067f89cc14862c
+4,a87ff679a2f3e71d9181a67b7542122c
+6,1679091c5a880faf6fb5e6087eb1b2dc
+8,c9f0f895fb98ab9159f51fd0297e236d
+10,d3d9446802a44259755d38e6d163e820
+-- Check COPY TO as user with permissions.
+SET SESSION AUTHORIZATION rls_regress_user1;
+SET row_security TO OFF;
+COPY (SELECT * FROM copy_t ORDER BY a ASC) TO STDOUT WITH DELIMITER ','; --fail - insufficient to bypass rls
+ERROR:  insufficient privilege to bypass row security.
+SET row_security TO ON;
+COPY (SELECT * FROM copy_t ORDER BY a ASC) TO STDOUT WITH DELIMITER ','; --ok
+0,cfcd208495d565ef66e7dff9f98764da
+2,c81e728d9d4c2f636f067f89cc14862c
+4,a87ff679a2f3e71d9181a67b7542122c
+6,1679091c5a880faf6fb5e6087eb1b2dc
+8,c9f0f895fb98ab9159f51fd0297e236d
+10,d3d9446802a44259755d38e6d163e820
+SET row_security TO FORCE;
+COPY (SELECT * FROM copy_t ORDER BY a ASC) TO STDOUT WITH DELIMITER ','; --ok
+0,cfcd208495d565ef66e7dff9f98764da
+2,c81e728d9d4c2f636f067f89cc14862c
+4,a87ff679a2f3e71d9181a67b7542122c
+6,1679091c5a880faf6fb5e6087eb1b2dc
+8,c9f0f895fb98ab9159f51fd0297e236d
+10,d3d9446802a44259755d38e6d163e820
+-- Check COPY TO as user with permissions and BYPASSRLS
+SET SESSION AUTHORIZATION rls_regress_exempt_user;
+SET row_security TO OFF;
+COPY (SELECT * FROM copy_t ORDER BY a ASC) TO STDOUT WITH DELIMITER ','; --ok
+0,cfcd208495d565ef66e7dff9f98764da
+1,c4ca4238a0b923820dcc509a6f75849b
+2,c81e728d9d4c2f636f067f89cc14862c
+3,eccbc87e4b5ce2fe28308fd9f2a7baf3
+4,a87ff679a2f3e71d9181a67b7542122c
+5,e4da3b7fbbce2345d7772b0674a318d5
+6,1679091c5a880faf6fb5e6087eb1b2dc
+7,8f14e45fceea167a5a36dedd4bea2543
+8,c9f0f895fb98ab9159f51fd0297e236d
+9,45c48cce2e2d7fbdea1afc51c7c6ad26
+10,d3d9446802a44259755d38e6d163e820
+SET row_security TO ON;
+COPY (SELECT * FROM copy_t ORDER BY a ASC) TO STDOUT WITH DELIMITER ','; --ok
+0,cfcd208495d565ef66e7dff9f98764da
+2,c81e728d9d4c2f636f067f89cc14862c
+4,a87ff679a2f3e71d9181a67b7542122c
+6,1679091c5a880faf6fb5e6087eb1b2dc
+8,c9f0f895fb98ab9159f51fd0297e236d
+10,d3d9446802a44259755d38e6d163e820
+SET row_security TO FORCE;
+COPY (SELECT * FROM copy_t ORDER BY a ASC) TO STDOUT WITH DELIMITER ','; --ok
+0,cfcd208495d565ef66e7dff9f98764da
+2,c81e728d9d4c2f636f067f89cc14862c
+4,a87ff679a2f3e71d9181a67b7542122c
+6,1679091c5a880faf6fb5e6087eb1b2dc
+8,c9f0f895fb98ab9159f51fd0297e236d
+10,d3d9446802a44259755d38e6d163e820
+-- Check COPY TO as user without permissions.SET row_security TO OFF;
+SET SESSION AUTHORIZATION rls_regress_user2;
+SET row_security TO OFF;
+COPY (SELECT * FROM copy_t ORDER BY a ASC) TO STDOUT WITH DELIMITER ','; --fail - insufficient to bypass rls
+ERROR:  insufficient privilege to bypass row security.
+SET row_security TO ON;
+COPY (SELECT * FROM copy_t ORDER BY a ASC) TO STDOUT WITH DELIMITER ','; --fail - permission denied
+ERROR:  permission denied for relation copy_t
+SET row_security TO FORCE;
+COPY (SELECT * FROM copy_t ORDER BY a ASC) TO STDOUT WITH DELIMITER ','; --fail - permission denied
+ERROR:  permission denied for relation copy_t
+-- Check COPY FROM as Superuser/owner.
+RESET SESSION AUTHORIZATION;
+SET row_security TO OFF;
+COPY copy_t FROM STDIN; --ok
+SET row_security TO ON;
+COPY copy_t FROM STDIN; --ok
+SET row_security TO FORCE;
+COPY copy_t FROM STDIN; --fail - COPY FROM not supported by RLS.
+ERROR:  COPY FROM not supported with row security.
+HINT:  Use direct INSERT statements instead.
+-- Check COPY FROM as user with permissions.
+SET SESSION AUTHORIZATION rls_regress_user1;
+SET row_security TO OFF;
+COPY copy_t FROM STDIN; --fail - insufficient privilege to bypass rls.
+ERROR:  insufficient privilege to bypass row security.
+SET row_security TO ON;
+COPY copy_t FROM STDIN; --fail - COPY FROM not supported by RLS.
+ERROR:  COPY FROM not supported with row security.
+HINT:  Use direct INSERT statements instead.
+SET row_security TO FORCE;
+COPY copy_t FROM STDIN; --fail - COPY FROM not supported by RLS.
+ERROR:  COPY FROM not supported with row security.
+HINT:  Use direct INSERT statements instead.
+-- Check COPY TO as user with permissions and BYPASSRLS
+SET SESSION AUTHORIZATION rls_regress_exempt_user;
+SET row_security TO OFF;
+COPY copy_t FROM STDIN; --ok
+SET row_security TO ON;
+COPY copy_t FROM STDIN; --fail - COPY FROM not supported by RLS.
+ERROR:  COPY FROM not supported with row security.
+HINT:  Use direct INSERT statements instead.
+SET row_security TO FORCE;
+COPY copy_t FROM STDIN; --fail - COPY FROM not supported by RLS.
+ERROR:  COPY FROM not supported with row security.
+HINT:  Use direct INSERT statements instead.
+-- Check COPY FROM as user without permissions.
+SET SESSION AUTHORIZATION rls_regress_user2;
+SET row_security TO OFF;
+COPY copy_t FROM STDIN; --fail - permission denied.
+ERROR:  permission denied for relation copy_t
+SET row_security TO ON;
+COPY copy_t FROM STDIN; --fail - permission denied.
+ERROR:  permission denied for relation copy_t
+SET row_security TO FORCE;
+COPY copy_t FROM STDIN; --fail - permission denied.
+ERROR:  permission denied for relation copy_t
+RESET SESSION AUTHORIZATION;
+DROP TABLE copy_t;
+--
+-- Clean up objects
+--
+RESET SESSION AUTHORIZATION;
+DROP SCHEMA rls_regress_schema CASCADE;
+NOTICE:  drop cascades to 24 other objects
+DETAIL:  drop cascades to function f_leak(text)
+drop cascades to table uaccount
+drop cascades to table category
+drop cascades to table document
+drop cascades to table dependent
+drop cascades to table rec1
+drop cascades to table rec2
+drop cascades to view rec1v
+drop cascades to view rec2v
+drop cascades to table s1
+drop cascades to table s2
+drop cascades to view v2
+drop cascades to table z1
+drop cascades to table x1
+drop cascades to table y1
+drop cascades to table y2
+drop cascades to table t1
+drop cascades to table t2
+drop cascades to table t3
+drop cascades to table t4
+drop cascades to table event_trigger_test
+drop cascades to function start_command()
+drop cascades to function end_command()
+drop cascades to function drop_sql_command()
+DROP USER rls_regress_user0;
+DROP USER rls_regress_user1;
+DROP USER rls_regress_user2;
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index ca56b47618bf7bdded93b4f883330a1b676fdb91..889bcd201fcfd3ea288f893ad8e97391f10bdd94 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1353,6 +1353,32 @@ pg_matviews| SELECT n.nspname AS schemaname,
      LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace)))
      LEFT JOIN pg_tablespace t ON ((t.oid = c.reltablespace)))
   WHERE (c.relkind = 'm'::"char");
+pg_policies| SELECT rs.rsecpolname AS policyname,
+    ( SELECT pg_class.relname
+           FROM pg_class
+          WHERE (pg_class.oid = rs.rsecrelid)) AS tablename,
+        CASE
+            WHEN (rs.rsecroles = '{0}'::oid[]) THEN (string_to_array('public'::text, ''::text))::name[]
+            ELSE ARRAY( SELECT pg_authid.rolname
+               FROM pg_authid
+              WHERE (pg_authid.oid = ANY (rs.rsecroles))
+              ORDER BY pg_authid.rolname)
+        END AS roles,
+        CASE
+            WHEN (rs.rseccmd IS NULL) THEN 'ALL'::text
+            ELSE
+            CASE rs.rseccmd
+                WHEN 'r'::"char" THEN 'SELECT'::text
+                WHEN 'a'::"char" THEN 'INSERT'::text
+                WHEN 'u'::"char" THEN 'UPDATE'::text
+                WHEN 'd'::"char" THEN 'DELETE'::text
+                ELSE NULL::text
+            END
+        END AS cmd,
+    pg_get_expr(rs.rsecqual, rs.rsecrelid) AS qual,
+    pg_get_expr(rs.rsecwithcheck, rs.rsecrelid) AS with_check
+   FROM pg_rowsecurity rs
+  ORDER BY rs.rsecpolname;
 pg_prepared_statements| SELECT p.name,
     p.statement,
     p.prepare_time,
@@ -1389,6 +1415,7 @@ pg_roles| SELECT pg_authid.rolname,
     pg_authid.rolconnlimit,
     '********'::text AS rolpassword,
     pg_authid.rolvaliduntil,
+    pg_authid.rolbypassrls,
     s.setconfig AS rolconfig,
     pg_authid.oid
    FROM (pg_authid
@@ -2018,7 +2045,8 @@ pg_tables| SELECT n.nspname AS schemaname,
     t.spcname AS tablespace,
     c.relhasindex AS hasindexes,
     c.relhasrules AS hasrules,
-    c.relhastriggers AS hastriggers
+    c.relhastriggers AS hastriggers,
+    c.relhasrowsecurity AS hasrowsecurity
    FROM ((pg_class c
      LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace)))
      LEFT JOIN pg_tablespace t ON ((t.oid = c.reltablespace)))
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index 111d24ca6b8b5c2c4e5ab5bd1f0fdcf1cd5e5111..2c8ec118a754ccc296720f446b7069daf5daa638 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -121,6 +121,7 @@ pg_pltemplate|t
 pg_proc|t
 pg_range|t
 pg_rewrite|t
+pg_rowsecurity|t
 pg_seclabel|t
 pg_shdepend|t
 pg_shdescription|t
diff --git a/src/test/regress/expected/updatable_views.out b/src/test/regress/expected/updatable_views.out
index 6576c474510fc4ca007a222ceb41291a4a4fc37d..6a359251695e3aeaa16a93b5216c9bba7aa6ee07 100644
--- a/src/test/regress/expected/updatable_views.out
+++ b/src/test/regress/expected/updatable_views.out
@@ -1395,18 +1395,18 @@ SELECT * FROM information_schema.views WHERE table_name = 'rw_view1';
 
 INSERT INTO rw_view1 VALUES(3,4); -- ok
 INSERT INTO rw_view1 VALUES(4,3); -- should fail
-ERROR:  new row violates WITH CHECK OPTION for view "rw_view1"
+ERROR:  new row violates WITH CHECK OPTION for "rw_view1"
 DETAIL:  Failing row contains (4, 3).
 INSERT INTO rw_view1 VALUES(5,null); -- should fail
-ERROR:  new row violates WITH CHECK OPTION for view "rw_view1"
+ERROR:  new row violates WITH CHECK OPTION for "rw_view1"
 DETAIL:  Failing row contains (5, null).
 UPDATE rw_view1 SET b = 5 WHERE a = 3; -- ok
 UPDATE rw_view1 SET b = -5 WHERE a = 3; -- should fail
-ERROR:  new row violates WITH CHECK OPTION for view "rw_view1"
+ERROR:  new row violates WITH CHECK OPTION for "rw_view1"
 DETAIL:  Failing row contains (3, -5).
 INSERT INTO rw_view1(a) VALUES (9); -- ok
 INSERT INTO rw_view1(a) VALUES (10); -- should fail
-ERROR:  new row violates WITH CHECK OPTION for view "rw_view1"
+ERROR:  new row violates WITH CHECK OPTION for "rw_view1"
 DETAIL:  Failing row contains (10, 10).
 SELECT * FROM base_tbl;
  a | b  
@@ -1445,11 +1445,11 @@ SELECT * FROM information_schema.views WHERE table_name = 'rw_view2';
 (1 row)
 
 INSERT INTO rw_view2 VALUES (-5); -- should fail
-ERROR:  new row violates WITH CHECK OPTION for view "rw_view1"
+ERROR:  new row violates WITH CHECK OPTION for "rw_view1"
 DETAIL:  Failing row contains (-5).
 INSERT INTO rw_view2 VALUES (5); -- ok
 INSERT INTO rw_view2 VALUES (15); -- should fail
-ERROR:  new row violates WITH CHECK OPTION for view "rw_view2"
+ERROR:  new row violates WITH CHECK OPTION for "rw_view2"
 DETAIL:  Failing row contains (15).
 SELECT * FROM base_tbl;
  a 
@@ -1458,10 +1458,10 @@ SELECT * FROM base_tbl;
 (1 row)
 
 UPDATE rw_view2 SET a = a - 10; -- should fail
-ERROR:  new row violates WITH CHECK OPTION for view "rw_view1"
+ERROR:  new row violates WITH CHECK OPTION for "rw_view1"
 DETAIL:  Failing row contains (-5).
 UPDATE rw_view2 SET a = a + 10; -- should fail
-ERROR:  new row violates WITH CHECK OPTION for view "rw_view2"
+ERROR:  new row violates WITH CHECK OPTION for "rw_view2"
 DETAIL:  Failing row contains (15).
 CREATE OR REPLACE VIEW rw_view2 AS SELECT * FROM rw_view1 WHERE a < 10
   WITH LOCAL CHECK OPTION;
@@ -1486,7 +1486,7 @@ SELECT * FROM information_schema.views WHERE table_name = 'rw_view2';
 
 INSERT INTO rw_view2 VALUES (-10); -- ok, but not in view
 INSERT INTO rw_view2 VALUES (20); -- should fail
-ERROR:  new row violates WITH CHECK OPTION for view "rw_view2"
+ERROR:  new row violates WITH CHECK OPTION for "rw_view2"
 DETAIL:  Failing row contains (20).
 SELECT * FROM base_tbl;
   a  
@@ -1500,10 +1500,10 @@ ERROR:  invalid value for "check_option" option
 DETAIL:  Valid values are "local" and "cascaded".
 ALTER VIEW rw_view1 SET (check_option=local);
 INSERT INTO rw_view2 VALUES (-20); -- should fail
-ERROR:  new row violates WITH CHECK OPTION for view "rw_view1"
+ERROR:  new row violates WITH CHECK OPTION for "rw_view1"
 DETAIL:  Failing row contains (-20).
 INSERT INTO rw_view2 VALUES (30); -- should fail
-ERROR:  new row violates WITH CHECK OPTION for view "rw_view2"
+ERROR:  new row violates WITH CHECK OPTION for "rw_view2"
 DETAIL:  Failing row contains (30).
 ALTER VIEW rw_view2 RESET (check_option);
 \d+ rw_view2
@@ -1559,7 +1559,7 @@ INSERT INTO rw_view1 VALUES (1); -- ok
 INSERT INTO rw_view2 VALUES (-2); -- ok, but not in view
 INSERT INTO rw_view2 VALUES (2); -- ok
 INSERT INTO rw_view3 VALUES (-3); -- should fail
-ERROR:  new row violates WITH CHECK OPTION for view "rw_view2"
+ERROR:  new row violates WITH CHECK OPTION for "rw_view2"
 DETAIL:  Failing row contains (-3).
 INSERT INTO rw_view3 VALUES (3); -- ok
 DROP TABLE base_tbl CASCADE;
@@ -1577,11 +1577,11 @@ CREATE VIEW rw_view1 AS
   WITH CHECK OPTION;
 INSERT INTO rw_view1 VALUES (5); -- ok
 INSERT INTO rw_view1 VALUES (15); -- should fail
-ERROR:  new row violates WITH CHECK OPTION for view "rw_view1"
+ERROR:  new row violates WITH CHECK OPTION for "rw_view1"
 DETAIL:  Failing row contains (15).
 UPDATE rw_view1 SET a = a + 5; -- ok
 UPDATE rw_view1 SET a = a + 5; -- should fail
-ERROR:  new row violates WITH CHECK OPTION for view "rw_view1"
+ERROR:  new row violates WITH CHECK OPTION for "rw_view1"
 DETAIL:  Failing row contains (15).
 EXPLAIN (costs off) INSERT INTO rw_view1 VALUES (5);
                           QUERY PLAN                           
@@ -1629,10 +1629,10 @@ CREATE TRIGGER base_tbl_trig BEFORE INSERT OR UPDATE ON base_tbl
 CREATE VIEW rw_view1 AS SELECT * FROM base_tbl WHERE a < b WITH CHECK OPTION;
 INSERT INTO rw_view1 VALUES (5,0); -- ok
 INSERT INTO rw_view1 VALUES (15, 20); -- should fail
-ERROR:  new row violates WITH CHECK OPTION for view "rw_view1"
+ERROR:  new row violates WITH CHECK OPTION for "rw_view1"
 DETAIL:  Failing row contains (15, 10).
 UPDATE rw_view1 SET a = 20, b = 30; -- should fail
-ERROR:  new row violates WITH CHECK OPTION for view "rw_view1"
+ERROR:  new row violates WITH CHECK OPTION for "rw_view1"
 DETAIL:  Failing row contains (20, 10).
 DROP TABLE base_tbl CASCADE;
 NOTICE:  drop cascades to view rw_view1
@@ -1663,12 +1663,12 @@ CREATE TRIGGER rw_view1_trig
 CREATE VIEW rw_view2 AS
   SELECT * FROM rw_view1 WHERE a > 0 WITH LOCAL CHECK OPTION;
 INSERT INTO rw_view2 VALUES (-5); -- should fail
-ERROR:  new row violates WITH CHECK OPTION for view "rw_view2"
+ERROR:  new row violates WITH CHECK OPTION for "rw_view2"
 DETAIL:  Failing row contains (-5).
 INSERT INTO rw_view2 VALUES (5); -- ok
 INSERT INTO rw_view2 VALUES (50); -- ok, but not in view
 UPDATE rw_view2 SET a = a - 10; -- should fail
-ERROR:  new row violates WITH CHECK OPTION for view "rw_view2"
+ERROR:  new row violates WITH CHECK OPTION for "rw_view2"
 DETAIL:  Failing row contains (-5).
 SELECT * FROM base_tbl;
  a  | b  
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index c0416f4fb9d640fdbbe6233e72691f1bb05bcd81..ab6c4e2ceed40b44bbd32527750cc8bf861c8f66 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -83,7 +83,7 @@ test: select_into select_distinct select_distinct_on select_implicit select_havi
 # ----------
 # Another group of parallel tests
 # ----------
-test: privileges security_label collate matview lock replica_identity
+test: privileges security_label collate matview lock replica_identity rowsecurity
 
 # ----------
 # Another group of parallel tests
diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule
index 16a190507d556c2860ff70c6aa576847bb17da7c..5ed2bf0ffb7bb2c157477145cb74084df572ffab 100644
--- a/src/test/regress/serial_schedule
+++ b/src/test/regress/serial_schedule
@@ -101,6 +101,7 @@ test: collate
 test: matview
 test: lock
 test: replica_identity
+test: rowsecurity
 test: alter_generic
 test: misc
 test: psql
diff --git a/src/test/regress/sql/rowsecurity.sql b/src/test/regress/sql/rowsecurity.sql
new file mode 100644
index 0000000000000000000000000000000000000000..5409bb055adf5bc0bbbdcabcddae5117909e63dd
--- /dev/null
+++ b/src/test/regress/sql/rowsecurity.sql
@@ -0,0 +1,921 @@
+--
+-- Test of Row-level security feature
+--
+
+-- Clean up in case a prior regression run failed
+
+-- Suppress NOTICE messages when users/groups don't exist
+SET client_min_messages TO 'warning';
+
+DROP USER IF EXISTS rls_regress_user0;
+DROP USER IF EXISTS rls_regress_user1;
+DROP USER IF EXISTS rls_regress_user2;
+DROP USER IF EXISTS rls_regress_exempt_user;
+DROP ROLE IF EXISTS rls_regress_group1;
+DROP ROLE IF EXISTS rls_regress_group2;
+
+DROP SCHEMA IF EXISTS rls_regress_schema CASCADE;
+
+RESET client_min_messages;
+
+-- initial setup
+CREATE USER rls_regress_user0;
+CREATE USER rls_regress_user1;
+CREATE USER rls_regress_user2;
+CREATE USER rls_regress_exempt_user BYPASSRLS;
+CREATE ROLE rls_regress_group1 NOLOGIN;
+CREATE ROLE rls_regress_group2 NOLOGIN;
+
+GRANT rls_regress_group1 TO rls_regress_user1;
+GRANT rls_regress_group2 TO rls_regress_user2;
+
+CREATE SCHEMA rls_regress_schema;
+GRANT ALL ON SCHEMA rls_regress_schema to public;
+SET search_path = rls_regress_schema;
+
+-- setup of malicious function
+CREATE OR REPLACE FUNCTION f_leak(text) RETURNS bool
+    COST 0.0000001 LANGUAGE plpgsql
+    AS 'BEGIN RAISE NOTICE ''f_leak => %'', $1; RETURN true; END';
+GRANT EXECUTE ON FUNCTION f_leak(text) TO public;
+
+-- BASIC Row-Level Security Scenario
+
+SET SESSION AUTHORIZATION rls_regress_user0;
+CREATE TABLE uaccount (
+    pguser      name primary key,
+    seclv       int
+);
+GRANT SELECT ON uaccount TO public;
+INSERT INTO uaccount VALUES
+    ('rls_regress_user0', 99),
+    ('rls_regress_user1', 1),
+    ('rls_regress_user2', 2),
+    ('rls_regress_user3', 3);
+
+CREATE TABLE category (
+    cid        int primary key,
+    cname      text
+);
+GRANT ALL ON category TO public;
+INSERT INTO category VALUES
+    (11, 'novel'),
+    (22, 'science fiction'),
+    (33, 'technology'),
+    (44, 'manga');
+
+CREATE TABLE document (
+    did         int primary key,
+    cid         int references category(cid),
+    dlevel      int not null,
+    dauthor     name,
+    dtitle      text
+);
+GRANT ALL ON document TO public;
+INSERT INTO document VALUES
+    ( 1, 11, 1, 'rls_regress_user1', 'my first novel'),
+    ( 2, 11, 2, 'rls_regress_user1', 'my second novel'),
+    ( 3, 22, 2, 'rls_regress_user1', 'my science fiction'),
+    ( 4, 44, 1, 'rls_regress_user1', 'my first manga'),
+    ( 5, 44, 2, 'rls_regress_user1', 'my second manga'),
+    ( 6, 22, 1, 'rls_regress_user2', 'great science fiction'),
+    ( 7, 33, 2, 'rls_regress_user2', 'great technology book'),
+    ( 8, 44, 1, 'rls_regress_user2', 'great manga');
+
+ALTER TABLE document ENABLE ROW LEVEL SECURITY;
+
+-- user's security level must be higher that or equal to document's
+CREATE POLICY p1 ON document
+    USING (dlevel <= (SELECT seclv FROM uaccount WHERE pguser = current_user));
+
+-- viewpoint from rls_regress_user1
+SET SESSION AUTHORIZATION rls_regress_user1;
+SET row_security TO ON;
+SELECT * FROM document WHERE f_leak(dtitle) ORDER BY did;
+SELECT * FROM document NATURAL JOIN category WHERE f_leak(dtitle) ORDER BY did;
+
+-- viewpoint from rls_regress_user2
+SET SESSION AUTHORIZATION rls_regress_user2;
+SELECT * FROM document WHERE f_leak(dtitle) ORDER BY did;
+SELECT * FROM document NATURAL JOIN category WHERE f_leak(dtitle) ORDER BY did;
+
+EXPLAIN (COSTS OFF) SELECT * FROM document WHERE f_leak(dtitle);
+EXPLAIN (COSTS OFF) SELECT * FROM document NATURAL JOIN category WHERE f_leak(dtitle);
+
+-- only owner can change row-level security
+ALTER POLICY p1 ON document USING (true);    --fail
+DROP POLICY p1 ON document;                  --fail
+
+SET SESSION AUTHORIZATION rls_regress_user0;
+ALTER POLICY p1 ON document USING (dauthor = current_user);
+
+-- viewpoint from rls_regress_user1 again
+SET SESSION AUTHORIZATION rls_regress_user1;
+SELECT * FROM document WHERE f_leak(dtitle) ORDER BY did;
+SELECT * FROM document NATURAL JOIN category WHERE f_leak(dtitle) ORDER by did;
+
+-- viewpoint from rls_regres_user2 again
+SET SESSION AUTHORIZATION rls_regress_user2;
+SELECT * FROM document WHERE f_leak(dtitle) ORDER BY did;
+SELECT * FROM document NATURAL JOIN category WHERE f_leak(dtitle) ORDER by did;
+
+EXPLAIN (COSTS OFF) SELECT * FROM document WHERE f_leak(dtitle);
+EXPLAIN (COSTS OFF) SELECT * FROM document NATURAL JOIN category WHERE f_leak(dtitle);
+
+-- interaction of FK/PK constraints
+SET SESSION AUTHORIZATION rls_regress_user0;
+CREATE POLICY p2 ON category
+    USING (CASE WHEN current_user = 'rls_regress_user1' THEN cid IN (11, 33)
+           WHEN current_user = 'rls_regress_user2' THEN cid IN (22, 44)
+           ELSE false END);
+
+ALTER TABLE category ENABLE ROW LEVEL SECURITY;
+
+-- cannot delete PK referenced by invisible FK
+SET SESSION AUTHORIZATION rls_regress_user1;
+SELECT * FROM document d FULL OUTER JOIN category c on d.cid = c.cid;
+DELETE FROM category WHERE cid = 33;    -- fails with FK violation
+
+-- cannot insert FK referencing invisible PK
+SET SESSION AUTHORIZATION rls_regress_user2;
+SELECT * FROM document d FULL OUTER JOIN category c on d.cid = c.cid;
+INSERT INTO document VALUES (10, 33, 1, current_user, 'hoge');
+
+-- UNIQUE or PRIMARY KEY constraint violation DOES reveal presence of row
+SET SESSION AUTHORIZATION rls_regress_user1;
+INSERT INTO document VALUES (8, 44, 1, 'rls_regress_user1', 'my third manga'); -- Must fail with unique violation, revealing presence of did we can't see
+SELECT * FROM document WHERE did = 8; -- and confirm we can't see it
+
+-- database superuser cannot bypass RLS policy when enabled
+RESET SESSION AUTHORIZATION;
+SET row_security TO ON;
+SELECT * FROM document;
+SELECT * FROM category;
+
+-- database superuser cannot bypass RLS policy when FORCE enabled.
+RESET SESSION AUTHORIZATION;
+SET row_security TO FORCE;
+SELECT * FROM document;
+SELECT * FROM category;
+
+-- database superuser can bypass RLS policy when disabled
+RESET SESSION AUTHORIZATION;
+SET row_security TO OFF;
+SELECT * FROM document;
+SELECT * FROM category;
+
+-- database non-superuser with bypass privilege can bypass RLS policy when disabled
+SET SESSION AUTHORIZATION rls_regress_exempt_user;
+SET row_security TO OFF;
+SELECT * FROM document;
+SELECT * FROM category;
+
+-- RLS policy applies to table owner when FORCE enabled.
+SET SESSION AUTHORIZATION rls_regress_user0;
+SET row_security TO FORCE;
+SELECT * FROM document;
+SELECT * FROM category;
+
+-- RLS policy does not apply to table owner when RLS enabled.
+SET SESSION AUTHORIZATION rls_regress_user0;
+SET row_security TO ON;
+SELECT * FROM document;
+SELECT * FROM category;
+
+-- RLS policy does not apply to table owner when RLS disabled.
+SET SESSION AUTHORIZATION rls_regress_user0;
+SET row_security TO OFF;
+SELECT * FROM document;
+SELECT * FROM category;
+
+--
+-- Table inheritance and RLS policy
+--
+SET SESSION AUTHORIZATION rls_regress_user0;
+
+SET row_security TO ON;
+
+CREATE TABLE t1 (a int, junk1 text, b text) WITH OIDS;
+ALTER TABLE t1 DROP COLUMN junk1;    -- just a disturbing factor
+GRANT ALL ON t1 TO public;
+
+COPY t1 FROM stdin WITH (oids);
+101	1	aaa
+102	2	bbb
+103	3	ccc
+104	4	ddd
+\.
+
+CREATE TABLE t2 (c float) INHERITS (t1);
+COPY t2 FROM stdin WITH (oids);
+201	1	abc	1.1
+202	2	bcd	2.2
+203	3	cde	3.3
+204	4	def	4.4
+\.
+
+CREATE TABLE t3 (c text, b text, a int) WITH OIDS;
+ALTER TABLE t3 INHERIT t1;
+COPY t3(a,b,c) FROM stdin WITH (oids);
+301	1	xxx	X
+302	2	yyy	Y
+303	3	zzz	Z
+\.
+
+CREATE POLICY p1 ON t1 FOR ALL TO PUBLIC USING (a % 2 = 0); -- be even number
+CREATE POLICY p2 ON t2 FOR ALL TO PUBLIC USING (a % 2 = 1); -- be odd number
+
+ALTER TABLE t1 ENABLE ROW LEVEL SECURITY;
+ALTER TABLE t2 ENABLE ROW LEVEL SECURITY;
+
+SET SESSION AUTHORIZATION rls_regress_user1;
+
+SELECT * FROM t1;
+EXPLAIN (COSTS OFF) SELECT * FROM t1;
+
+SELECT * FROM t1 WHERE f_leak(b);
+EXPLAIN (COSTS OFF) SELECT * FROM t1 WHERE f_leak(b);
+
+-- reference to system column
+SELECT oid, * FROM t1;
+EXPLAIN (COSTS OFF) SELECT *, t1 FROM t1;
+
+-- reference to whole-row reference
+SELECT *, t1 FROM t1;
+EXPLAIN (COSTS OFF) SELECT *, t1 FROM t1;
+
+-- for share/update lock
+SELECT * FROM t1 FOR SHARE;
+EXPLAIN (COSTS OFF) SELECT * FROM t1 FOR SHARE;
+
+SELECT * FROM t1 WHERE f_leak(b) FOR SHARE;
+EXPLAIN (COSTS OFF) SELECT * FROM t1 WHERE f_leak(b) FOR SHARE;
+
+-- superuser is allowed to bypass RLS checks
+RESET SESSION AUTHORIZATION;
+SET row_security TO OFF;
+SELECT * FROM t1 WHERE f_leak(b);
+EXPLAIN (COSTS OFF) SELECT * FROM t1 WHERE f_leak(b);
+
+-- non-superuser with bypass privilege can bypass RLS policy when disabled
+SET SESSION AUTHORIZATION rls_regress_exempt_user;
+SET row_security TO OFF;
+SELECT * FROM t1 WHERE f_leak(b);
+EXPLAIN (COSTS OFF) SELECT * FROM t1 WHERE f_leak(b);
+
+----- Dependencies -----
+SET SESSION AUTHORIZATION rls_regress_user0;
+SET row_security TO ON;
+
+CREATE TABLE dependee (x integer, y integer);
+
+CREATE TABLE dependent (x integer, y integer);
+CREATE POLICY d1 ON dependent FOR ALL
+    TO PUBLIC
+    USING (x = (SELECT d.x FROM dependee d WHERE d.y = y));
+
+DROP TABLE dependee; -- Should fail without CASCADE due to dependency on row-security qual?
+
+DROP TABLE dependee CASCADE;
+
+EXPLAIN (COSTS OFF) SELECT * FROM dependent; -- After drop, should be unqualified
+
+-----   RECURSION    ----
+
+--
+-- Simple recursion
+--
+SET SESSION AUTHORIZATION rls_regress_user0;
+CREATE TABLE rec1 (x integer, y integer);
+CREATE POLICY r1 ON rec1 USING (x = (SELECT r.x FROM rec1 r WHERE y = r.y));
+ALTER TABLE rec1 ENABLE ROW LEVEL SECURITY;
+SET SESSION AUTHORIZATION rls_regress_user1;
+SELECT * FROM rec1; -- fail, direct recursion
+
+--
+-- Mutual recursion
+--
+SET SESSION AUTHORIZATION rls_regress_user0;
+CREATE TABLE rec2 (a integer, b integer);
+ALTER POLICY r1 ON rec1 USING (x = (SELECT a FROM rec2 WHERE b = y));
+CREATE POLICY r2 ON rec2 USING (a = (SELECT x FROM rec1 WHERE y = b));
+ALTER TABLE rec2 ENABLE ROW LEVEL SECURITY;
+
+SET SESSION AUTHORIZATION rls_regress_user1;
+SELECT * FROM rec1;    -- fail, mutual recursion
+
+--
+-- Mutual recursion via views
+--
+SET SESSION AUTHORIZATION rls_regress_user1;
+CREATE VIEW rec1v AS SELECT * FROM rec1;
+CREATE VIEW rec2v AS SELECT * FROM rec2;
+SET SESSION AUTHORIZATION rls_regress_user0;
+ALTER POLICY r1 ON rec1 USING (x = (SELECT a FROM rec2v WHERE b = y));
+ALTER POLICY r2 ON rec2 USING (a = (SELECT x FROM rec1v WHERE y = b));
+
+SET SESSION AUTHORIZATION rls_regress_user1;
+SELECT * FROM rec1;    -- fail, mutual recursion via views
+
+--
+-- Mutual recursion via .s.b views
+--
+SET SESSION AUTHORIZATION rls_regress_user1;
+DROP VIEW rec1v, rec2v CASCADE;
+CREATE VIEW rec1v WITH (security_barrier) AS SELECT * FROM rec1;
+CREATE VIEW rec2v WITH (security_barrier) AS SELECT * FROM rec2;
+SET SESSION AUTHORIZATION rls_regress_user0;
+CREATE POLICY r1 ON rec1 USING (x = (SELECT a FROM rec2v WHERE b = y));
+CREATE POLICY r2 ON rec2 USING (a = (SELECT x FROM rec1v WHERE y = b));
+
+SET SESSION AUTHORIZATION rls_regress_user1;
+SELECT * FROM rec1;    -- fail, mutual recursion via s.b. views
+
+--
+-- recursive RLS and VIEWs in policy
+--
+SET SESSION AUTHORIZATION rls_regress_user0;
+CREATE TABLE s1 (a int, b text);
+INSERT INTO s1 (SELECT x, md5(x::text) FROM generate_series(-10,10) x);
+
+CREATE TABLE s2 (x int, y text);
+INSERT INTO s2 (SELECT x, md5(x::text) FROM generate_series(-6,6) x);
+
+GRANT SELECT ON s1, s2 TO rls_regress_user1;
+
+CREATE POLICY p1 ON s1 USING (a in (select x from s2 where y like '%2f%'));
+CREATE POLICY p2 ON s2 USING (x in (select a from s1 where b like '%22%'));
+CREATE POLICY p3 ON s1 FOR INSERT WITH CHECK (a = (SELECT a FROM s1));
+
+ALTER TABLE s1 ENABLE ROW LEVEL SECURITY;
+ALTER TABLE s2 ENABLE ROW LEVEL SECURITY;
+
+SET SESSION AUTHORIZATION rls_regress_user1;
+CREATE VIEW v2 AS SELECT * FROM s2 WHERE y like '%af%';
+SELECT * FROM s1 WHERE f_leak(b); -- fail (infinite recursion)
+
+INSERT INTO s1 VALUES (1, 'foo'); -- fail (infinite recursion)
+
+SET SESSION AUTHORIZATION rls_regress_user0;
+DROP POLICY p3 on s1;
+ALTER POLICY p2 ON s2 USING (x % 2 = 0);
+
+SET SESSION AUTHORIZATION rls_regress_user1;
+SELECT * FROM s1 WHERE f_leak(b);	-- OK
+EXPLAIN (COSTS OFF) SELECT * FROM only s1 WHERE f_leak(b);
+
+SET SESSION AUTHORIZATION rls_regress_user0;
+ALTER POLICY p1 ON s1 USING (a in (select x from v2)); -- using VIEW in RLS policy
+SET SESSION AUTHORIZATION rls_regress_user1;
+SELECT * FROM s1 WHERE f_leak(b);	-- OK
+EXPLAIN (COSTS OFF) SELECT * FROM s1 WHERE f_leak(b);
+
+SELECT (SELECT x FROM s1 LIMIT 1) xx, * FROM s2 WHERE y like '%28%';
+EXPLAIN (COSTS OFF) SELECT (SELECT x FROM s1 LIMIT 1) xx, * FROM s2 WHERE y like '%28%';
+
+SET SESSION AUTHORIZATION rls_regress_user0;
+ALTER POLICY p2 ON s2 USING (x in (select a from s1 where b like '%d2%'));
+SET SESSION AUTHORIZATION rls_regress_user1;
+SELECT * FROM s1 WHERE f_leak(b);	-- fail (infinite recursion via view)
+
+-- prepared statement with rls_regress_user0 privilege
+PREPARE p1(int) AS SELECT * FROM t1 WHERE a <= $1;
+EXECUTE p1(2);
+EXPLAIN (COSTS OFF) EXECUTE p1(2);
+
+-- superuser is allowed to bypass RLS checks
+RESET SESSION AUTHORIZATION;
+SET row_security TO OFF;
+SELECT * FROM t1 WHERE f_leak(b);
+EXPLAIN (COSTS OFF) SELECT * FROM t1 WHERE f_leak(b);
+
+-- plan cache should be invalidated
+EXECUTE p1(2);
+EXPLAIN (COSTS OFF) EXECUTE p1(2);
+
+PREPARE p2(int) AS SELECT * FROM t1 WHERE a = $1;
+EXECUTE p2(2);
+EXPLAIN (COSTS OFF) EXECUTE p2(2);
+
+-- also, case when privilege switch from superuser
+SET SESSION AUTHORIZATION rls_regress_user1;
+SET row_security TO ON;
+EXECUTE p2(2);
+EXPLAIN (COSTS OFF) EXECUTE p2(2);
+
+--
+-- UPDATE / DELETE and Row-level security
+--
+SET SESSION AUTHORIZATION rls_regress_user1;
+EXPLAIN (COSTS OFF) UPDATE t1 SET b = b || b WHERE f_leak(b);
+UPDATE t1 SET b = b || b WHERE f_leak(b);
+
+EXPLAIN (COSTS OFF) UPDATE only t1 SET b = b || '_updt' WHERE f_leak(b);
+UPDATE only t1 SET b = b || '_updt' WHERE f_leak(b);
+
+-- returning clause with system column
+UPDATE only t1 SET b = b WHERE f_leak(b) RETURNING oid, *, t1;
+UPDATE t1 SET b = b WHERE f_leak(b) RETURNING *;
+UPDATE t1 SET b = b WHERE f_leak(b) RETURNING oid, *, t1;
+
+RESET SESSION AUTHORIZATION;
+SET row_security TO OFF;
+SELECT * FROM t1;
+
+SET SESSION AUTHORIZATION rls_regress_user1;
+SET row_security TO ON;
+EXPLAIN (COSTS OFF) DELETE FROM only t1 WHERE f_leak(b);
+EXPLAIN (COSTS OFF) DELETE FROM t1 WHERE f_leak(b);
+
+DELETE FROM only t1 WHERE f_leak(b) RETURNING oid, *, t1;
+DELETE FROM t1 WHERE f_leak(b) RETURNING oid, *, t1;
+
+--
+-- ROLE/GROUP
+--
+SET SESSION AUTHORIZATION rls_regress_user0;
+CREATE TABLE z1 (a int, b text);
+
+GRANT SELECT ON z1 TO rls_regress_group1, rls_regress_group2,
+    rls_regress_user1, rls_regress_user2;
+
+INSERT INTO z1 VALUES
+    (1, 'aaa'),
+    (2, 'bbb'),
+    (3, 'ccc'),
+    (4, 'ddd');
+
+CREATE POLICY p1 ON z1 TO rls_regress_group1 USING (a % 2 = 0);
+CREATE POLICY p2 ON z1 TO rls_regress_group2 USING (a % 2 = 1);
+
+ALTER TABLE z1 ENABLE ROW LEVEL SECURITY;
+
+SET SESSION AUTHORIZATION rls_regress_user1;
+SELECT * FROM z1 WHERE f_leak(b);
+EXPLAIN (COSTS OFF) SELECT * FROM z1 WHERE f_leak(b);
+
+SET ROLE rls_regress_group1;
+SELECT * FROM z1 WHERE f_leak(b);
+EXPLAIN (COSTS OFF) SELECT * FROM z1 WHERE f_leak(b);
+
+SET SESSION AUTHORIZATION rls_regress_user2;
+SELECT * FROM z1 WHERE f_leak(b);
+EXPLAIN (COSTS OFF) SELECT * FROM z1 WHERE f_leak(b);
+
+SET ROLE rls_regress_group2;
+SELECT * FROM z1 WHERE f_leak(b);
+EXPLAIN (COSTS OFF) SELECT * FROM z1 WHERE f_leak(b);
+
+--
+-- Views should follow policy for view owner.
+--
+-- View and Table owner are the same.
+SET SESSION AUTHORIZATION rls_regress_user0;
+CREATE VIEW rls_view AS SELECT * FROM z1 WHERE f_leak(b);
+GRANT SELECT ON rls_view TO rls_regress_user1;
+
+-- Query as role that is not owner of view or table.  Should return all records.
+SET SESSION AUTHORIZATION rls_regress_user1;
+SELECT * FROM rls_view;
+EXPLAIN (COSTS OFF) SELECT * FROM rls_view;
+
+-- Query as view/table owner.  Should return all records.
+SET SESSION AUTHORIZATION rls_regress_user0;
+SELECT * FROM rls_view;
+EXPLAIN (COSTS OFF) SELECT * FROM rls_view;
+DROP VIEW rls_view;
+
+-- View and Table owners are different.
+SET SESSION AUTHORIZATION rls_regress_user1;
+CREATE VIEW rls_view AS SELECT * FROM z1 WHERE f_leak(b);
+GRANT SELECT ON rls_view TO rls_regress_user0;
+
+-- Query as role that is not owner of view but is owner of table.
+-- Should return records based on view owner policies.
+SET SESSION AUTHORIZATION rls_regress_user0;
+SELECT * FROM rls_view;
+EXPLAIN (COSTS OFF) SELECT * FROM rls_view;
+
+-- Query as role that is not owner of table but is owner of view.
+-- Should return records based on view owner policies.
+SET SESSION AUTHORIZATION rls_regress_user1;
+SELECT * FROM rls_view;
+EXPLAIN (COSTS OFF) SELECT * FROM rls_view;
+
+-- Query as role that is not the owner of the table or view without permissions.
+SET SESSION AUTHORIZATION rls_regress_user2;
+SELECT * FROM rls_view; --fail - permission denied.
+EXPLAIN (COSTS OFF) SELECT * FROM rls_view; --fail - permission denied.
+
+-- Query as role that is not the owner of the table or view with permissions.
+SET SESSION AUTHORIZATION rls_regress_user1;
+GRANT SELECT ON rls_view TO rls_regress_user2;
+SELECT * FROM rls_view;
+EXPLAIN (COSTS OFF) SELECT * FROM rls_view;
+
+SET SESSION AUTHORIZATION rls_regress_user1;
+DROP VIEW rls_view;
+
+--
+-- Command specific
+--
+SET SESSION AUTHORIZATION rls_regress_user0;
+
+CREATE TABLE x1 (a int, b text, c text);
+GRANT ALL ON x1 TO PUBLIC;
+
+INSERT INTO x1 VALUES
+    (1, 'abc', 'rls_regress_user1'),
+    (2, 'bcd', 'rls_regress_user1'),
+    (3, 'cde', 'rls_regress_user2'),
+    (4, 'def', 'rls_regress_user2'),
+    (5, 'efg', 'rls_regress_user1'),
+    (6, 'fgh', 'rls_regress_user1'),
+    (7, 'fgh', 'rls_regress_user2'),
+    (8, 'fgh', 'rls_regress_user2');
+
+CREATE POLICY p0 ON x1 FOR ALL USING (c = current_user);
+CREATE POLICY p1 ON x1 FOR SELECT USING (a % 2 = 0);
+CREATE POLICY p2 ON x1 FOR INSERT WITH CHECK (a % 2 = 1);
+CREATE POLICY p3 ON x1 FOR UPDATE USING (a % 2 = 0);
+CREATE POLICY p4 ON x1 FOR DELETE USING (a < 8);
+
+ALTER TABLE x1 ENABLE ROW LEVEL SECURITY;
+
+SET SESSION AUTHORIZATION rls_regress_user1;
+SELECT * FROM x1 WHERE f_leak(b) ORDER BY a ASC;
+UPDATE x1 SET b = b || '_updt' WHERE f_leak(b) RETURNING *;
+
+SET SESSION AUTHORIZATION rls_regress_user2;
+SELECT * FROM x1 WHERE f_leak(b) ORDER BY a ASC;
+UPDATE x1 SET b = b || '_updt' WHERE f_leak(b) RETURNING *;
+DELETE FROM x1 WHERE f_leak(b) RETURNING *;
+
+--
+-- Duplicate Policy Names
+--
+SET SESSION AUTHORIZATION rls_regress_user0;
+CREATE TABLE y1 (a int, b text);
+CREATE TABLE y2 (a int, b text);
+
+GRANT ALL ON y1, y2 TO rls_regress_user1;
+
+CREATE POLICY p1 ON y1 FOR ALL USING (a % 2 = 0);
+CREATE POLICY p2 ON y1 FOR SELECT USING (a > 2);
+CREATE POLICY p1 ON y1 FOR SELECT USING (a % 2 = 1);  --fail
+CREATE POLICY p1 ON y2 FOR ALL USING (a % 2 = 0);  --OK
+
+ALTER TABLE y1 ENABLE ROW LEVEL SECURITY;
+ALTER TABLE y2 ENABLE ROW LEVEL SECURITY;
+
+--
+-- Expression structure with SBV
+--
+-- Create view as table owner.  RLS should NOT be applied.
+SET SESSION AUTHORIZATION rls_regress_user0;
+CREATE VIEW rls_sbv WITH (security_barrier) AS
+    SELECT * FROM y1 WHERE f_leak(b);
+EXPLAIN (COSTS OFF) SELECT * FROM rls_sbv WHERE (a = 1);
+DROP VIEW rls_sbv;
+
+-- Create view as role that does not own table.  RLS should be applied.
+SET SESSION AUTHORIZATION rls_regress_user1;
+CREATE VIEW rls_sbv WITH (security_barrier) AS
+    SELECT * FROM y1 WHERE f_leak(b);
+EXPLAIN (COSTS OFF) SELECT * FROM rls_sbv WHERE (a = 1);
+DROP VIEW rls_sbv;
+
+--
+-- Expression structure
+--
+SET SESSION AUTHORIZATION rls_regress_user0;
+INSERT INTO y2 (SELECT x, md5(x::text) FROM generate_series(0,20) x);
+CREATE POLICY p2 ON y2 USING (a % 3 = 0);
+CREATE POLICY p3 ON y2 USING (a % 4 = 0);
+
+SET SESSION AUTHORIZATION rls_regress_user1;
+SELECT * FROM y2 WHERE f_leak(b);
+EXPLAIN (COSTS OFF) SELECT * FROM y2 WHERE f_leak(b);
+
+--
+-- Plancache invalidate on user change.
+--
+RESET SESSION AUTHORIZATION;
+DROP TABLE t1 CASCADE;
+CREATE TABLE t1 (a integer);
+
+GRANT SELECT ON t1 TO rls_regress_user1, rls_regress_user2;
+
+CREATE POLICY p1 ON t1 TO rls_regress_user1 USING ((a % 2) = 0);
+CREATE POLICY p2 ON t1 TO rls_regress_user2 USING ((a % 4) = 0);
+
+ALTER TABLE t1 ENABLE ROW LEVEL SECURITY;
+
+SET ROLE rls_regress_user1;
+PREPARE role_inval AS SELECT * FROM t1;
+EXPLAIN (COSTS OFF) EXECUTE role_inval;
+
+SET ROLE rls_regress_user2;
+EXPLAIN (COSTS OFF) EXECUTE role_inval;
+
+--
+-- CTE and RLS
+--
+RESET SESSION AUTHORIZATION;
+DROP TABLE t1 CASCADE;
+CREATE TABLE t1 (a integer, b text);
+CREATE POLICY p1 ON t1 USING (a % 2 = 0);
+
+ALTER TABLE t1 ENABLE ROW LEVEL SECURITY;
+
+GRANT ALL ON t1 TO rls_regress_user1;
+
+INSERT INTO t1 (SELECT x, md5(x::text) FROM generate_series(0,20) x);
+
+SET SESSION AUTHORIZATION rls_regress_user1;
+
+WITH cte1 AS (SELECT * FROM t1 WHERE f_leak(b)) SELECT * FROM cte1;
+EXPLAIN (COSTS OFF) WITH cte1 AS (SELECT * FROM t1 WHERE f_leak(b)) SELECT * FROM cte1;
+
+WITH cte1 AS (UPDATE t1 SET a = a + 1 RETURNING *) SELECT * FROM cte1; --fail
+WITH cte1 AS (UPDATE t1 SET a = a RETURNING *) SELECT * FROM cte1; --ok
+
+WITH cte1 AS (INSERT INTO t1 VALUES (21, 'Fail') RETURNING *) SELECT * FROM cte1; --fail
+WITH cte1 AS (INSERT INTO t1 VALUES (20, 'Success') RETURNING *) SELECT * FROM cte1; --ok
+
+--
+-- Rename Policy
+--
+RESET SESSION AUTHORIZATION;
+ALTER POLICY p1 ON t1 RENAME TO p1; --fail
+
+SELECT rsecpolname, relname
+    FROM pg_rowsecurity rs
+    JOIN pg_class pc ON (pc.oid = rs.rsecrelid)
+    WHERE relname = 't1';
+
+ALTER POLICY p1 ON t1 RENAME TO p2; --ok
+
+SELECT rsecpolname, relname
+    FROM pg_rowsecurity rs
+    JOIN pg_class pc ON (pc.oid = rs.rsecrelid)
+    WHERE relname = 't1';
+
+--
+-- Check INSERT SELECT
+--
+SET SESSION AUTHORIZATION rls_regress_user1;
+CREATE TABLE t2 (a integer, b text);
+INSERT INTO t2 (SELECT * FROM t1);
+EXPLAIN (COSTS OFF) INSERT INTO t2 (SELECT * FROM t1);
+SELECT * FROM t2;
+EXPLAIN (COSTS OFF) SELECT * FROM t2;
+CREATE TABLE t3 AS SELECT * FROM t1;
+SELECT * FROM t3;
+SELECT * INTO t4 FROM t1;
+SELECT * FROM t4;
+
+--
+-- RLS with JOIN
+--
+SET SESSION AUTHORIZATION rls_regress_user0;
+CREATE TABLE blog (id integer, author text, post text);
+CREATE TABLE comment (blog_id integer, message text);
+
+GRANT ALL ON blog, comment TO rls_regress_user1;
+
+CREATE POLICY blog_1 ON blog USING (id % 2 = 0);
+
+ALTER TABLE blog ENABLE ROW LEVEL SECURITY;
+
+INSERT INTO blog VALUES
+    (1, 'alice', 'blog #1'),
+    (2, 'bob', 'blog #1'),
+    (3, 'alice', 'blog #2'),
+    (4, 'alice', 'blog #3'),
+    (5, 'john', 'blog #1');
+
+INSERT INTO comment VALUES
+    (1, 'cool blog'),
+    (1, 'fun blog'),
+    (3, 'crazy blog'),
+    (5, 'what?'),
+    (4, 'insane!'),
+    (2, 'who did it?');
+
+SET SESSION AUTHORIZATION rls_regress_user1;
+-- Check RLS JOIN with Non-RLS.
+SELECT id, author, message FROM blog JOIN comment ON id = blog_id;
+-- Check Non-RLS JOIN with RLS.
+SELECT id, author, message FROM comment JOIN blog ON id = blog_id;
+
+SET SESSION AUTHORIZATION rls_regress_user0;
+CREATE POLICY comment_1 ON comment USING (blog_id < 4);
+
+ALTER TABLE comment ENABLE ROW LEVEL SECURITY;
+
+SET SESSION AUTHORIZATION rls_regress_user1;
+-- Check RLS JOIN RLS
+SELECT id, author, message FROM blog JOIN comment ON id = blog_id;
+SELECT id, author, message FROM comment JOIN blog ON id = blog_id;
+
+SET SESSION AUTHORIZATION rls_regress_user0;
+DROP TABLE blog, comment;
+
+--
+-- Default Deny Policy
+--
+RESET SESSION AUTHORIZATION;
+DROP POLICY p2 ON t1;
+ALTER TABLE t1 OWNER TO rls_regress_user0;
+
+-- Check that default deny does not apply to superuser.
+RESET SESSION AUTHORIZATION;
+SELECT * FROM t1;
+EXPLAIN (COSTS OFF) SELECT * FROM t1;
+
+-- Check that default deny does not apply to table owner.
+SET SESSION AUTHORIZATION rls_regress_user0;
+SELECT * FROM t1;
+EXPLAIN (COSTS OFF) SELECT * FROM t1;
+
+-- Check that default deny does apply to superuser when RLS force.
+SET row_security TO FORCE;
+RESET SESSION AUTHORIZATION;
+SELECT * FROM t1;
+EXPLAIN (COSTS OFF) SELECT * FROM t1;
+
+-- Check that default deny does apply to table owner when RLS force.
+SET SESSION AUTHORIZATION rls_regress_user0;
+SELECT * FROM t1;
+EXPLAIN (COSTS OFF) SELECT * FROM t1;
+
+-- Check that default deny applies to non-owner/non-superuser when RLS on.
+SET SESSION AUTHORIZATION rls_regress_user1;
+SET row_security TO ON;
+SELECT * FROM t1;
+EXPLAIN (COSTS OFF) SELECT * FROM t1;
+SET SESSION AUTHORIZATION rls_regress_user1;
+SELECT * FROM t1;
+EXPLAIN (COSTS OFF) SELECT * FROM t1;
+
+--
+-- Event Triggers
+--
+RESET SESSION AUTHORIZATION;
+CREATE TABLE event_trigger_test (a integer, b text);
+
+CREATE OR REPLACE FUNCTION start_command()
+RETURNS event_trigger AS $$
+BEGIN
+RAISE NOTICE '% - ddl_command_start', tg_tag;
+END;
+$$ LANGUAGE plpgsql;
+
+CREATE OR REPLACE FUNCTION end_command()
+RETURNS event_trigger AS $$
+BEGIN
+RAISE NOTICE '% - ddl_command_end', tg_tag;
+END;
+$$ LANGUAGE plpgsql;
+
+CREATE OR REPLACE FUNCTION drop_sql_command()
+RETURNS event_trigger AS $$
+BEGIN
+RAISE NOTICE '% - sql_drop', tg_tag;
+END;
+$$ LANGUAGE plpgsql;
+
+CREATE EVENT TRIGGER start_rls_command ON ddl_command_start
+    WHEN TAG IN ('CREATE POLICY', 'ALTER POLICY', 'DROP POLICY') EXECUTE PROCEDURE start_command();
+
+CREATE EVENT TRIGGER end_rls_command ON ddl_command_end
+    WHEN TAG IN ('CREATE POLICY', 'ALTER POLICY', 'DROP POLICY') EXECUTE PROCEDURE end_command();
+
+CREATE EVENT TRIGGER sql_drop_command ON sql_drop
+    WHEN TAG IN ('DROP POLICY') EXECUTE PROCEDURE drop_sql_command();
+
+CREATE POLICY p1 ON event_trigger_test USING (FALSE);
+ALTER POLICY p1 ON event_trigger_test USING (TRUE);
+ALTER POLICY p1 ON event_trigger_test RENAME TO p2;
+DROP POLICY p2 ON event_trigger_test;
+
+DROP EVENT TRIGGER start_rls_command;
+DROP EVENT TRIGGER end_rls_command;
+DROP EVENT TRIGGER sql_drop_command;
+
+--
+-- COPY TO/FROM
+--
+
+RESET SESSION AUTHORIZATION;
+DROP TABLE copy_t CASCADE;
+CREATE TABLE copy_t (a integer, b text);
+CREATE POLICY p1 ON copy_t USING (a % 2 = 0);
+
+ALTER TABLE copy_t ENABLE ROW LEVEL SECURITY;
+
+GRANT ALL ON copy_t TO rls_regress_user1, rls_regress_exempt_user;
+
+INSERT INTO copy_t (SELECT x, md5(x::text) FROM generate_series(0,10) x);
+
+-- Check COPY TO as Superuser/owner.
+RESET SESSION AUTHORIZATION;
+SET row_security TO OFF;
+COPY (SELECT * FROM copy_t ORDER BY a ASC) TO STDOUT WITH DELIMITER ',';
+SET row_security TO ON;
+COPY (SELECT * FROM copy_t ORDER BY a ASC) TO STDOUT WITH DELIMITER ',';
+SET row_security TO FORCE;
+COPY (SELECT * FROM copy_t ORDER BY a ASC) TO STDOUT WITH DELIMITER ',';
+
+-- Check COPY TO as user with permissions.
+SET SESSION AUTHORIZATION rls_regress_user1;
+SET row_security TO OFF;
+COPY (SELECT * FROM copy_t ORDER BY a ASC) TO STDOUT WITH DELIMITER ','; --fail - insufficient to bypass rls
+SET row_security TO ON;
+COPY (SELECT * FROM copy_t ORDER BY a ASC) TO STDOUT WITH DELIMITER ','; --ok
+SET row_security TO FORCE;
+COPY (SELECT * FROM copy_t ORDER BY a ASC) TO STDOUT WITH DELIMITER ','; --ok
+
+-- Check COPY TO as user with permissions and BYPASSRLS
+SET SESSION AUTHORIZATION rls_regress_exempt_user;
+SET row_security TO OFF;
+COPY (SELECT * FROM copy_t ORDER BY a ASC) TO STDOUT WITH DELIMITER ','; --ok
+SET row_security TO ON;
+COPY (SELECT * FROM copy_t ORDER BY a ASC) TO STDOUT WITH DELIMITER ','; --ok
+SET row_security TO FORCE;
+COPY (SELECT * FROM copy_t ORDER BY a ASC) TO STDOUT WITH DELIMITER ','; --ok
+
+-- Check COPY TO as user without permissions.SET row_security TO OFF;
+SET SESSION AUTHORIZATION rls_regress_user2;
+SET row_security TO OFF;
+COPY (SELECT * FROM copy_t ORDER BY a ASC) TO STDOUT WITH DELIMITER ','; --fail - insufficient to bypass rls
+SET row_security TO ON;
+COPY (SELECT * FROM copy_t ORDER BY a ASC) TO STDOUT WITH DELIMITER ','; --fail - permission denied
+SET row_security TO FORCE;
+COPY (SELECT * FROM copy_t ORDER BY a ASC) TO STDOUT WITH DELIMITER ','; --fail - permission denied
+
+-- Check COPY FROM as Superuser/owner.
+RESET SESSION AUTHORIZATION;
+SET row_security TO OFF;
+COPY copy_t FROM STDIN; --ok
+1	abc
+2	bcd
+3	cde
+4	def
+\.
+SET row_security TO ON;
+COPY copy_t FROM STDIN; --ok
+1	abc
+2	bcd
+3	cde
+4	def
+\.
+SET row_security TO FORCE;
+COPY copy_t FROM STDIN; --fail - COPY FROM not supported by RLS.
+
+-- Check COPY FROM as user with permissions.
+SET SESSION AUTHORIZATION rls_regress_user1;
+SET row_security TO OFF;
+COPY copy_t FROM STDIN; --fail - insufficient privilege to bypass rls.
+SET row_security TO ON;
+COPY copy_t FROM STDIN; --fail - COPY FROM not supported by RLS.
+SET row_security TO FORCE;
+COPY copy_t FROM STDIN; --fail - COPY FROM not supported by RLS.
+
+-- Check COPY TO as user with permissions and BYPASSRLS
+SET SESSION AUTHORIZATION rls_regress_exempt_user;
+SET row_security TO OFF;
+COPY copy_t FROM STDIN; --ok
+1	abc
+2	bcd
+3	cde
+4	def
+\.
+SET row_security TO ON;
+COPY copy_t FROM STDIN; --fail - COPY FROM not supported by RLS.
+SET row_security TO FORCE;
+COPY copy_t FROM STDIN; --fail - COPY FROM not supported by RLS.
+
+-- Check COPY FROM as user without permissions.
+SET SESSION AUTHORIZATION rls_regress_user2;
+SET row_security TO OFF;
+COPY copy_t FROM STDIN; --fail - permission denied.
+SET row_security TO ON;
+COPY copy_t FROM STDIN; --fail - permission denied.
+SET row_security TO FORCE;
+COPY copy_t FROM STDIN; --fail - permission denied.
+
+RESET SESSION AUTHORIZATION;
+DROP TABLE copy_t;
+
+--
+-- Clean up objects
+--
+RESET SESSION AUTHORIZATION;
+
+DROP SCHEMA rls_regress_schema CASCADE;
+
+DROP USER rls_regress_user0;
+DROP USER rls_regress_user1;
+DROP USER rls_regress_user2;